* [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls)
@ 2026-10-18 18:01 Jan Koester
2026-10-18 18:01 ` [RFC PATCH 01/41] uuid: move uuid_t and guid_t into <linux/uuid_types.h> Jan Koester
` (10 more replies)
0 siblings, 11 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
Hi,
this RFC series lets a user or group id be a UUID (RFC 9562, version 4)
or a Microsoft GUID as well as a classic 32-bit number. Processes can run
as such a user, files can be owned by one, and credentials, ACLs, keys,
System V IPC, sockets, signals, audit and procfs carry the whole id.
Why
---
Directory services name users by 128-bit ids: Active Directory by
objectGUID (and SIDs), LDAP directories often by entryUUID. Today every
system that serves such users (winbind, sssd, nfsidmap, ksmbd user
space) maps them to 32-bit numbers through ranges, hashes or a local
database. The numbers differ between machines unless the mapping is
configured the same everywhere, mappings can collide, and ownership
written to shared storage or a backup depends on the machine that wrote
it. With the 128-bit id in the kernel, the id the directory hands out is
also the owner on disk and in every credential, and no mapping table is
needed.
Design
------
- kuid_t and kgid_t become a tagged union (legacy number, UUID, GUID)
instead of a u32. There is no table that maps UUIDs to numbers: the
kernel compares, hashes and stores the whole id. uid_eq(), uid_lt()
and the other helpers keep their meaning. Patches 1-3 add the UAPI
type and helpers. Patches 4-8 fix places that relied on kuid_t being
a u32: sk_uid is now read through a seqcount, and af_unix, netlink and
Bluetooth keep only classic ids in skb->cb. Patch 9 changes the type.
- UUIDs and GUIDs exist only in the initial user namespace. They are not
part of any uid_map or gid_map, show as the overflow id in other
namespaces, and no capability in a child namespace gives privilege
over them. Idmapped mounts pass them through unchanged.
- The classic system calls keep their 32-bit ids and show a UUID or GUID
as the overflow id, as the 16-bit calls do for ids above 65535. A new
family, named after the old calls with an _r suffix (getuid_r,
setresuid_r, setgroups_r, fchownat_r, ...), takes a 20-byte
__kernel_uid_r_t:
typedef struct __kernel_uid_r {
__u32 type; /* UID_R_NONE, _LEGACY, _UUID, _GUID */
union {
__u32 legacy;
__u8 uuid[16];
__u8 guid[16];
};
} __kernel_uid_r_t;
It has the same layout on every ABI, so none of the calls needs a
compat entry.
- Existing interfaces get _R variants where an id is in the ABI:
STATX_UID_R/STATX_GID_R, SO_PEERCRED_R, SO_PEERGROUPS_R,
SCM_CREDENTIALS_R, si_uid_r in siginfo (offset 96 in every layout),
IPC_STAT_R/IPC_SET_R, KEYCTL_CHOWN_R, and version 3 of the POSIX ACL
xattr. procfs, audit records, key descriptions and the RPCSEC_GSS
upcall use a text form (decimal, UUID, or GUID in braces).
- A filesystem has to declare that it can store such owners
(SB_I_UID_R). Everywhere else, chown and creating a file as such a
user fail with EOVERFLOW, so a UUID never turns into a wrong number
on disk. tmpfs, ramfs and devpts declare it, overlayfs does when its
upper layer does, and ext4 does with a new read-only compatible
feature, uid_r (patches 31, 32 and 34; 256-byte inodes). NFSv4 and
ksmbd carry the ids as text or as SIDs (S-1-22-3/4/5/6-...).
- Things that can't carry a 128-bit id fail closed instead of mapping:
quotas don't account such owners, AUTH_SYS sends the overflow id,
unprivileged ublk devices are refused, and gssd versions that don't
know uid_r= refuse the upcall.
Documentation/userspace-api/uid_r.rst (patches 35 and 41) describes the
whole ABI and its limitations.
Testing
-------
- tools/testing/selftests/uid_r (patch 36): 14/14 pass.
- A boot test suite (nolibc init on QEMU, ext4 images with and without
uid_r): 88/88 pass, on a kernel with USER_NS, overlayfs, tmpfs, POSIX
ACLs and audit enabled.
- End to end on a full distribution (FenrirOS: patched libc, e2fsprogs,
OpenSSH, rsync, libarchive, a ksmbd and NFS server): UUID users log in
over ssh, own files on ext4, and reach ksmbd and NFSv4 shares.
- The runtime tests above ran on the same series on top of v7.2.9 and
v7.2.11. On v7.3-rc7, the series has so far been built for x86_64
(bzImage and modules, with USER_NS, audit, ecryptfs, fscrypt, quota,
ublk, NFS, Bluetooth and the LSMs enabled), not yet booted.
- The syscall tables of the other architectures are wired up (patch 22)
but haven't been built.
The e2fsprogs changes (mke2fs/tune2fs -O uid_r, e2fsck, debugfs, ACL
conversion in libext2fs) will be sent to linux-ext4 separately.
Open questions
--------------
- Is a tagged union in kuid_t acceptable, or would you rather see the
128-bit id kept beside a u32 kuid_t?
- The new calls copy the classic get/set*id() and chown() families
one by one (20 entries). A single setid_r(which, ...) call would need
fewer syscall numbers. Which do you prefer?
- Should the series be split per subsystem (core/uidgid, fs and VFS,
net, ext4, nfsd/ksmbd) after this RFC?
The series is based on v7.3-rc7. The syscall numbers (473-492 on most
architectures) are placeholders and will follow whatever is merged first.
Jan Koester (41):
uuid: move uuid_t and guid_t into <linux/uuid_types.h>
uapi: add <linux/uidgid_r.h> for 128 bit capable user and group ids
uidgid: add uid_is_legacy() and gid_is_legacy()
net: sock: read sk_uid through a seqcount
af_unix: keep sender ids in skb->cb only when they are classic ids
netlink: keep classic sender ids in skb->cb
Bluetooth: keep classic sender ids in skb->cb
smb: client: use __kuid_val() for the unix SIDs of the caller
uidgid: let kuid_t and kgid_t hold a UUID or a GUID
uidgid: convert between kernel ids and the ids of the *_r system calls
fscrypt: show the whole uid in the description of user key claims
keys: name per-user keyrings of UUID and GUID users by the whole uid
ublk: refuse unprivileged devices to UUID and GUID users
nfsd: don't hand out uid 0 for read layouts of UUID-owned files
SUNRPC: name UUID and GUID users to gssd as uid_r=
ecryptfs: hash daemons by kuid_hash()
quota: don't account UUID and GUID owners
fs: keep UUID and GUID owners off filesystems that can't store them
cred: add the *_r variants of the get/set*id() system calls
groups: add getgroups_r() and setgroups_r()
fs: add chown_r(), lchown_r(), fchown_r() and fchownat_r()
arch: wire up the *_r uid and gid system calls
statx: report UUID and GUID owners
shmem, ramfs: allow UUID and GUID owners
uidgid: parse and print ids in their text form
proc: show UUID and GUID ids in status, accept them for loginuid
audit: log UUID and GUID ids in full
net: add SO_PEERCRED_R, SO_PEERGROUPS_R and SCM_CREDENTIALS_R
signal: report the sender's UUID or GUID uid in si_uid_r
ipc: add IPC_STAT_R and IPC_SET_R
ext4: add the uid_r feature for UUID and GUID owners
ext4: let uid_r be turned on while the filesystem is mounted
posix_acl: support UUID and GUID entries
ext4: store ACLs with UUID and GUID entries on uid_r filesystems
Documentation: describe UUID and GUID user and group ids
selftests: add tests for UUID and GUID ids
ovl: allow UUID and GUID owners when the upper layer does
devpts: allow UUID and GUID owners
ksmbd: log in UUID and GUID users and map them to SIDs
nfsd, nfs: carry UUID and GUID owners as their text form
Documentation: uid_r: describe SMB and NFSv4
Documentation/userspace-api/index.rst | 1 +
Documentation/userspace-api/uid_r.rst | 186 +++++++
arch/alpha/include/uapi/asm/socket.h | 5 +
arch/alpha/kernel/syscalls/syscall.tbl | 20 +
arch/arm/tools/syscall.tbl | 20 +
arch/arm64/tools/syscall_32.tbl | 20 +
arch/m68k/kernel/syscalls/syscall.tbl | 20 +
arch/microblaze/kernel/syscalls/syscall.tbl | 20 +
arch/mips/include/uapi/asm/socket.h | 5 +
arch/mips/kernel/syscalls/syscall_n32.tbl | 20 +
arch/mips/kernel/syscalls/syscall_n64.tbl | 20 +
arch/mips/kernel/syscalls/syscall_o32.tbl | 20 +
arch/parisc/include/uapi/asm/socket.h | 5 +
arch/parisc/kernel/syscalls/syscall.tbl | 20 +
arch/powerpc/kernel/syscalls/syscall.tbl | 20 +
arch/s390/kernel/syscalls/syscall.tbl | 20 +
arch/sh/kernel/syscalls/syscall.tbl | 20 +
arch/sparc/include/uapi/asm/socket.h | 5 +
arch/sparc/kernel/syscalls/syscall.tbl | 20 +
arch/x86/entry/syscalls/syscall_32.tbl | 20 +
arch/x86/entry/syscalls/syscall_64.tbl | 20 +
arch/xtensa/kernel/syscalls/syscall.tbl | 20 +
drivers/block/ublk_drv.c | 17 +-
drivers/tty/tty_audit.c | 7 +-
fs/attr.c | 7 +
fs/crypto/keyring.c | 7 +-
fs/devpts/inode.c | 2 +
fs/ecryptfs/messaging.c | 2 +-
fs/ext4/acl.c | 109 +++-
fs/ext4/acl.h | 34 +-
fs/ext4/ext4.h | 27 +-
fs/ext4/inode.c | 115 ++++-
fs/ext4/ioctl.c | 30 +-
fs/ext4/super.c | 45 +-
fs/mnt_idmapping.c | 14 +
fs/nfs/nfs4idmap.c | 53 ++
fs/nfs/super.c | 2 +
fs/nfsd/flexfilelayout.c | 6 +-
fs/nfsd/nfs4idmap.c | 68 ++-
fs/open.c | 144 +++++-
fs/overlayfs/super.c | 3 +
fs/posix_acl.c | 123 ++++-
fs/proc/array.c | 17 +-
fs/proc/base.c | 38 +-
fs/quota/dquot.c | 3 +
fs/ramfs/inode.c | 1 +
fs/signalfd.c | 3 +
fs/smb/client/cifsacl.c | 2 +-
fs/smb/client/smb2pdu.c | 7 +-
fs/smb/server/ksmbd_netlink.h | 15 +-
fs/smb/server/mgmt/user_config.c | 35 +-
fs/smb/server/mgmt/user_config.h | 4 +-
fs/smb/server/smb2pdu.c | 10 +-
fs/smb/server/smb_common.c | 18 +-
fs/smb/server/smbacl.c | 178 +++++--
fs/smb/server/smbacl.h | 19 +-
fs/smb/server/transport_ipc.c | 4 +-
fs/smb/server/vfs_cache.c | 9 +-
fs/smb/server/vfs_cache.h | 4 +-
fs/stat.c | 24 +-
include/linux/audit.h | 8 +-
include/linux/compat.h | 6 +
include/linux/fs.h | 29 +-
include/linux/fs/super_types.h | 1 +
include/linux/mnt_idmapping.h | 74 ++-
include/linux/netlink.h | 2 +-
include/linux/posix_acl_xattr.h | 31 ++
include/linux/ptrace.h | 2 +-
include/linux/quota.h | 18 +
include/linux/signal.h | 22 +
include/linux/signal_types.h | 5 +-
include/linux/socket.h | 1 +
include/linux/syscalls.h | 44 ++
include/linux/uidgid.h | 165 +++++-
include/linux/uidgid_types.h | 32 +-
include/linux/uuid.h | 11 +-
include/linux/uuid_types.h | 21 +
include/net/bluetooth/bluetooth.h | 2 +-
include/net/scm.h | 28 +
include/net/sock.h | 32 +-
include/uapi/asm-generic/siginfo.h | 17 +
include/uapi/asm-generic/socket.h | 5 +
include/uapi/asm-generic/unistd.h | 43 +-
include/uapi/linux/ipc.h | 17 +
include/uapi/linux/keyctl.h | 1 +
include/uapi/linux/posix_acl_xattr.h | 19 +
include/uapi/linux/signalfd.h | 4 +-
include/uapi/linux/socket.h | 9 +
include/uapi/linux/stat.h | 10 +-
include/uapi/linux/uidgid_r.h | 45 ++
io_uring/waitid.c | 32 +-
io_uring/waitid.h | 1 +
ipc/mqueue.c | 4 +-
ipc/msg.c | 40 +-
ipc/sem.c | 31 +-
ipc/shm.c | 32 +-
ipc/util.c | 112 +++-
ipc/util.h | 23 +-
kernel/Makefile | 1 +
kernel/audit.c | 50 +-
kernel/audit.h | 4 +-
kernel/auditsc.c | 63 ++-
kernel/exit.c | 18 +-
kernel/exit.h | 3 +
kernel/groups.c | 94 +++-
kernel/signal.c | 48 +-
kernel/sys.c | 472 ++++++++++++-----
kernel/sys_ni.c | 12 +
kernel/ucount.c | 2 +-
kernel/uidgid_r.c | 467 +++++++++++++++++
kernel/user.c | 2 +-
kernel/user_namespace.c | 10 +-
lib/kobject_uevent.c | 8 +-
mm/oom_kill.c | 2 +-
mm/shmem.c | 2 +
net/bluetooth/hci_sock.c | 8 +-
net/core/dev.c | 8 +-
net/core/scm.c | 94 ++--
net/core/sock.c | 90 +++-
net/netlabel/netlabel_user.c | 4 +-
net/netlink/af_netlink.c | 4 +-
net/socket.c | 5 +-
net/sunrpc/auth_gss/auth_gss.c | 44 +-
net/unix/af_unix.c | 53 +-
net/unix/af_unix.h | 11 +-
scripts/syscall.tbl | 20 +
security/apparmor/file.c | 8 +-
security/integrity/ima/ima_policy.c | 8 +-
security/integrity/integrity_audit.c | 6 +-
security/ipe/audit.c | 12 +-
security/keys/compat.c | 3 +
security/keys/internal.h | 3 +
security/keys/keyctl.c | 61 ++-
security/keys/persistent.c | 11 +-
security/keys/process_keys.c | 36 +-
security/keys/request_key.c | 12 +-
security/selinux/selinuxfs.c | 8 +-
security/selinux/ss/services.c | 4 +-
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/uid_r/.gitignore | 1 +
tools/testing/selftests/uid_r/Makefile | 6 +
tools/testing/selftests/uid_r/config | 4 +
tools/testing/selftests/uid_r/uid_r_test.c | 535 ++++++++++++++++++++
143 files changed, 4425 insertions(+), 605 deletions(-)
create mode 100644 Documentation/userspace-api/uid_r.rst
create mode 100644 include/linux/uuid_types.h
create mode 100644 include/uapi/linux/uidgid_r.h
create mode 100644 kernel/uidgid_r.c
create mode 100644 tools/testing/selftests/uid_r/.gitignore
create mode 100644 tools/testing/selftests/uid_r/Makefile
create mode 100644 tools/testing/selftests/uid_r/config
create mode 100644 tools/testing/selftests/uid_r/uid_r_test.c
base-commit: 32c52ec87d6245ee2d12ed5dd509e40062939b50
--
2.56.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH 01/41] uuid: move uuid_t and guid_t into <linux/uuid_types.h>
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 02/41] uapi: add <linux/uidgid_r.h> for 128 bit capable user and group ids Jan Koester
` (9 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
<linux/uuid.h> pulls in <linux/string.h> for its helpers, which makes it
unusable from headers low in the include chain. The next patches embed
UUIDs and GUIDs in kuid_t and kgid_t, whose <linux/uidgid_types.h> is
included by <linux/sched.h>.
Move the two types and UUID_SIZE into a header of their own that only
needs <linux/types.h>. <linux/uuid.h> includes it, so no user changes.
No functional change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/linux/uuid.h | 11 +----------
include/linux/uuid_types.h | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+), 10 deletions(-)
create mode 100644 include/linux/uuid_types.h
diff --git a/include/linux/uuid.h b/include/linux/uuid.h
index 43d4a79b27..cbdaf0ca45 100644
--- a/include/linux/uuid.h
+++ b/include/linux/uuid.h
@@ -9,16 +9,7 @@
#define _LINUX_UUID_H_
#include <linux/string.h>
-
-#define UUID_SIZE 16
-
-typedef struct {
- __u8 b[UUID_SIZE];
-} guid_t;
-
-typedef struct {
- __u8 b[UUID_SIZE];
-} uuid_t;
+#include <linux/uuid_types.h>
#define GUID_INIT(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \
((guid_t) \
diff --git a/include/linux/uuid_types.h b/include/linux/uuid_types.h
new file mode 100644
index 0000000000..1e45b69571
--- /dev/null
+++ b/include/linux/uuid_types.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UUID/GUID types, without the helpers of <linux/uuid.h>, for headers
+ * low in the include chain (such as <linux/uidgid_types.h>)
+ */
+#ifndef _LINUX_UUID_TYPES_H_
+#define _LINUX_UUID_TYPES_H_
+
+#include <linux/types.h>
+
+#define UUID_SIZE 16
+
+typedef struct {
+ __u8 b[UUID_SIZE];
+} guid_t;
+
+typedef struct {
+ __u8 b[UUID_SIZE];
+} uuid_t;
+
+#endif /* _LINUX_UUID_TYPES_H_ */
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 02/41] uapi: add <linux/uidgid_r.h> for 128 bit capable user and group ids
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
2026-10-18 18:01 ` [RFC PATCH 01/41] uuid: move uuid_t and guid_t into <linux/uuid_types.h> Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 03/41] uidgid: add uid_is_legacy() and gid_is_legacy() Jan Koester
` (8 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
User and group ids are 32 bit numbers. Identities that come from
elsewhere -- UUIDs handed out by a directory service, or the GUIDs of a
Windows domain -- have to be squeezed into that range through mapping
tables kept in userspace, and the kernel never sees who a process really
is.
Add a tagged id for a new family of system calls (named after the
classic ones with an _r suffix): a classic 32 bit id, an RFC 9562
version 4 UUID, or a Microsoft GUID in the byte order Windows stores it
in. Type 0 is "no id", so a zero-initialized id is never root, and lets
the set*id_r() calls express "unchanged" without a magic number.
The struct has no 64 bit members and the same layout on all ABIs, so
none of the new calls needs a compat entry point.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/uapi/linux/uidgid_r.h | 45 +++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 include/uapi/linux/uidgid_r.h
diff --git a/include/uapi/linux/uidgid_r.h b/include/uapi/linux/uidgid_r.h
new file mode 100644
index 0000000000..c3b1cb2ae2
--- /dev/null
+++ b/include/uapi/linux/uidgid_r.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * User and group ids for the *_r system calls: a classic 32 bit id, a
+ * UUID or a Microsoft GUID.
+ *
+ * The classic system calls (getuid(), chown(), ...) keep their 32 bit
+ * ids; for an id that has no 32 bit form they see the overflow id
+ * (/proc/sys/kernel/overflowuid and overflowgid), like the 16 bit system
+ * calls do for ids above 65535.
+ */
+#ifndef _UAPI_LINUX_UIDGID_R_H
+#define _UAPI_LINUX_UIDGID_R_H
+
+#include <linux/types.h>
+
+/*
+ * Kinds of id (__kernel_uid_r_t.type). An all-zero id is UID_R_NONE, so a
+ * zero-initialized id never stands for root.
+ */
+#define UID_R_NONE 0 /* no id: "unchanged" or "not set" */
+#define UID_R_LEGACY 1 /* classic 32 bit id in .legacy */
+#define UID_R_UUID 2 /* RFC 9562 UUID, version 4, in .uuid */
+#define UID_R_GUID 3 /* Microsoft GUID in .guid */
+
+#define UID_R_SIZE 16 /* bytes of .uuid and .guid */
+
+/*
+ * .uuid holds the UUID in network byte order, as RFC 9562 lays it out.
+ * .guid holds the GUID as Windows stores it: Data1, Data2 and Data3 little
+ * endian, Data4 as is.
+ *
+ * Bytes past the member .type selects are reserved and must be zero.
+ */
+typedef struct __kernel_uid_r {
+ __u32 type;
+ union {
+ __u32 legacy;
+ __u8 uuid[UID_R_SIZE];
+ __u8 guid[UID_R_SIZE];
+ };
+} __kernel_uid_r_t;
+
+typedef __kernel_uid_r_t __kernel_gid_r_t;
+
+#endif /* _UAPI_LINUX_UIDGID_R_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 03/41] uidgid: add uid_is_legacy() and gid_is_legacy()
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
2026-10-18 18:01 ` [RFC PATCH 01/41] uuid: move uuid_t and guid_t into <linux/uuid_types.h> Jan Koester
2026-10-18 18:01 ` [RFC PATCH 02/41] uapi: add <linux/uidgid_r.h> for 128 bit capable user and group ids Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 04/41] net: sock: read sk_uid through a seqcount Jan Koester
` (7 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
User and group ids are about to become able to hold a UUID or a GUID
besides a classic 32 bit id. Add the predicates code that can only keep
a classic id (in 32 bits of a buffer, for instance) tests with, so it
can be converted before the types change. For now every id is a classic
one.
No functional change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/linux/uidgid.h | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index 2dc767e08f..182c682458 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -22,6 +22,20 @@ struct uid_gid_map;
#define KUIDT_INIT(value) (kuid_t){ value }
#define KGIDT_INIT(value) (kgid_t){ value }
+/*
+ * Whether an id is a classic 32 bit id. All of them are for now; code that
+ * needs one (to store it in 32 bits, for example) checks with these.
+ */
+static inline bool uid_is_legacy(kuid_t uid)
+{
+ return true;
+}
+
+static inline bool gid_is_legacy(kgid_t gid)
+{
+ return true;
+}
+
#ifdef CONFIG_MULTIUSER
static inline uid_t __kuid_val(kuid_t uid)
{
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 04/41] net: sock: read sk_uid through a seqcount
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (2 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 03/41] uidgid: add uid_is_legacy() and gid_is_legacy() Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 05/41] af_unix: keep sender ids in skb->cb only when they are classic ids Jan Koester
` (6 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
sk_uid is read without a lock, with READ_ONCE(), by the routing lookups
and the reuseport checks, while sockfs_setattr() may change it. That
only works for as long as a kuid_t is a single word, which it won't be
once ids can be UUIDs and GUIDs.
Add sk_uid_seq next to it: writers bump it around the store and hold
sk_callback_lock (sock_graft(), sockfs_setattr()) or own the socket
alone (sock_init_data_uid()); sk_uid() retries until it sees a whole
id. sk_clone() reads the parent's id the same way rather than relying
on the copy made by sock_copy().
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/net/sock.h | 28 +++++++++++++++++++++++++---
net/core/sock.c | 6 +++++-
net/socket.c | 5 +++--
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/include/net/sock.h b/include/net/sock.h
index 60ea55dc18..0986d94665 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -294,6 +294,8 @@ struct sk_filter;
* @sk_ack_backlog: current listen backlog
* @sk_max_ack_backlog: listen backlog set in listen()
* @sk_uid: user id of owner
+ * @sk_uid_seq: makes lockless reads of @sk_uid see a whole id; writers
+ * hold @sk_callback_lock
* @sk_ino: inode number (zero if orphaned)
* @sk_prefer_busy_poll: prefer busypolling over softirq processing
* @sk_busy_poll_budget: napi processing budget when busypolling
@@ -509,6 +511,7 @@ struct sock {
u32 sk_priority;
u32 sk_mark;
kuid_t sk_uid;
+ seqcount_t sk_uid_seq;
u16 sk_protocol;
u16 sk_type;
struct dst_entry __rcu *sk_dst_cache;
@@ -2109,11 +2112,23 @@ static inline int sk_rx_queue_get(const struct sock *sk)
return -1;
}
+/*
+ * sk_uid is too big for WRITE_ONCE(): writers hold sk_callback_lock (or own
+ * the socket alone, before it is published) and bump sk_uid_seq around the
+ * store, so sk_uid() can read it without a lock.
+ */
+static inline void __sk_set_uid(struct sock *sk, kuid_t uid)
+{
+ write_seqcount_begin(&sk->sk_uid_seq);
+ sk->sk_uid = uid;
+ write_seqcount_end(&sk->sk_uid_seq);
+}
+
static inline void sk_set_socket(struct sock *sk, struct socket *sock)
{
WRITE_ONCE(sk->sk_socket, sock);
if (sock) {
- WRITE_ONCE(sk->sk_uid, SOCK_INODE(sock)->i_uid);
+ __sk_set_uid(sk, SOCK_INODE(sock)->i_uid);
WRITE_ONCE(sk->sk_ino, SOCK_INODE(sock)->i_ino);
} else {
/* Note: sk_uid is unchanged. */
@@ -2161,8 +2176,15 @@ static inline u64 sock_i_ino(const struct sock *sk)
static inline kuid_t sk_uid(const struct sock *sk)
{
- /* Paired with WRITE_ONCE() in sockfs_setattr() */
- return READ_ONCE(sk->sk_uid);
+ unsigned int seq;
+ kuid_t uid;
+
+ /* Paired with __sk_set_uid() */
+ do {
+ seq = read_seqcount_begin(&sk->sk_uid_seq);
+ uid = sk->sk_uid;
+ } while (read_seqcount_retry(&sk->sk_uid_seq, seq));
+ return uid;
}
static inline kuid_t sock_net_uid(const struct net *net, const struct sock *sk)
diff --git a/net/core/sock.c b/net/core/sock.c
index e8551df833..300bc75ce1 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -2449,6 +2449,7 @@ static void sk_init_common(struct sock *sk)
skb_queue_head_init(&sk->sk_error_queue);
rwlock_init(&sk->sk_callback_lock);
+ seqcount_init(&sk->sk_uid_seq);
lockdep_set_class_and_name(&sk->sk_receive_queue.lock,
af_rlock_keys + sk->sk_family,
af_family_rlock_key_strings[sk->sk_family]);
@@ -2529,6 +2530,8 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
atomic_set(&newsk->sk_omem_alloc, 0);
sk_init_common(newsk);
+ /* sock_copy() may have caught a half written sk_uid */
+ newsk->sk_uid = sk_uid(sk);
newsk->sk_dst_cache = NULL;
newsk->sk_dst_pending_confirm = 0;
@@ -3773,7 +3776,7 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
} else {
RCU_INIT_POINTER(sk->sk_wq, NULL);
}
- sk->sk_uid = uid;
+ __sk_set_uid(sk, uid);
sk->sk_state_change = sock_def_wakeup;
sk->sk_data_ready = sock_def_readable;
@@ -4614,6 +4617,7 @@ static int __init sock_struct_check(void)
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_priority);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_mark);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_uid);
+ CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_uid_seq);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_protocol);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_dst_cache);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_route_caps);
diff --git a/net/socket.c b/net/socket.c
index c05d86e63a..09658f3958 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -679,8 +679,9 @@ static int sockfs_setattr(struct mnt_idmap *idmap,
struct socket *sock = SOCKET_I(d_inode(dentry));
if (sock->sk) {
- /* Paired with READ_ONCE() in sk_uid() */
- WRITE_ONCE(sock->sk->sk_uid, iattr->ia_uid);
+ write_lock_bh(&sock->sk->sk_callback_lock);
+ __sk_set_uid(sock->sk, iattr->ia_uid);
+ write_unlock_bh(&sock->sk->sk_callback_lock);
} else {
err = -ENOENT;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 05/41] af_unix: keep sender ids in skb->cb only when they are classic ids
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (3 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 04/41] net: sock: read sk_uid through a seqcount Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 06/41] netlink: keep classic sender ids in skb->cb Jan Koester
` (5 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
unix_skb_parms has to fit into the 48 bytes of skb->cb. With kuid_t and
kgid_t growing to hold UUIDs and GUIDs the two of them alone would take
40 bytes.
Store classic ids as 32 bit values as before, and only for a UUID or
GUID allocate a small struct unix_skb_ids for both, freed together with
the pid reference in unix_destruct_scm(). AF_UNIX skbs carrying a pid
reference are never cloned, so the pointer has a single owner.
Senders with classic ids see no change and no allocation.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
net/unix/af_unix.c | 50 +++++++++++++++++++++++++++++++++++++++-------
net/unix/af_unix.h | 11 ++++++++--
2 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 42cffeafc8..2b17310534 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1969,11 +1969,46 @@ static void unix_peek_fds(struct scm_cookie *scm, struct sk_buff *skb)
unix_peek_fpl(scm->fp);
}
+/*
+ * The sender's ids, in the skb itself for legacy ids; UUIDs and GUIDs don't
+ * fit in skb->cb and come from an allocation, freed with the pid reference
+ * in unix_destruct_scm().
+ */
+static int unix_skb_set_ids(struct sk_buff *skb, kuid_t uid, kgid_t gid)
+{
+ struct unix_skb_ids *ids = NULL;
+
+ if (!uid_is_legacy(uid) || !gid_is_legacy(gid)) {
+ ids = kmalloc_obj(*ids, GFP_KERNEL_ACCOUNT);
+ if (!ids)
+ return -ENOMEM;
+ ids->uid = uid;
+ ids->gid = gid;
+ }
+ kfree(UNIXCB(skb).ids);
+ UNIXCB(skb).ids = ids;
+ UNIXCB(skb).uid = __kuid_val(uid);
+ UNIXCB(skb).gid = __kgid_val(gid);
+ return 0;
+}
+
+static kuid_t unix_skb_uid(const struct sk_buff *skb)
+{
+ return UNIXCB(skb).ids ? UNIXCB(skb).ids->uid : KUIDT_INIT(UNIXCB(skb).uid);
+}
+
+static kgid_t unix_skb_gid(const struct sk_buff *skb)
+{
+ return UNIXCB(skb).ids ? UNIXCB(skb).ids->gid : KGIDT_INIT(UNIXCB(skb).gid);
+}
+
static void unix_destruct_scm(struct sk_buff *skb)
{
struct scm_cookie scm = {};
swap(scm.pid, UNIXCB(skb).pid);
+ kfree(UNIXCB(skb).ids);
+ UNIXCB(skb).ids = NULL;
if (UNIXCB(skb).fp)
unix_detach_fds(&scm, skb);
@@ -1992,20 +2027,21 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
int err = 0;
UNIXCB(skb).pid = get_pid(scm->pid);
- UNIXCB(skb).uid = scm->creds.uid;
- UNIXCB(skb).gid = scm->creds.gid;
UNIXCB(skb).fp = NULL;
+ skb->destructor = unix_wfree;
+ err = unix_skb_set_ids(skb, scm->creds.uid, scm->creds.gid);
+ if (err)
+ return err;
unix_get_secdata(scm, skb);
if (scm->fp && send_fds)
err = unix_attach_fds(scm, skb);
- skb->destructor = unix_wfree;
return err;
}
static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)
{
- scm_set_cred(scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid);
+ scm_set_cred(scm, UNIXCB(skb).pid, unix_skb_uid(skb), unix_skb_gid(skb));
unix_set_secdata(scm, skb);
}
@@ -2039,7 +2075,7 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
return err;
UNIXCB(skb).pid = get_pid(pid);
- current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid);
+ return unix_skb_set_ids(skb, current_uid(), current_gid());
}
return 0;
@@ -2049,8 +2085,8 @@ static bool unix_skb_scm_eq(struct sk_buff *skb,
struct scm_cookie *scm)
{
return UNIXCB(skb).pid == scm->pid &&
- uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
- gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
+ uid_eq(unix_skb_uid(skb), scm->creds.uid) &&
+ gid_eq(unix_skb_gid(skb), scm->creds.gid) &&
unix_secdata_eq(scm, skb);
}
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef3..e861a430cf 100644
--- a/net/unix/af_unix.h
+++ b/net/unix/af_unix.h
@@ -10,10 +10,17 @@
struct sock *unix_peer_get(struct sock *sk);
-struct unix_skb_parms {
- struct pid *pid; /* skb credentials */
+/* The sender's ids, when either is a UUID or GUID */
+struct unix_skb_ids {
kuid_t uid;
kgid_t gid;
+};
+
+struct unix_skb_parms {
+ struct pid *pid; /* skb credentials */
+ struct unix_skb_ids *ids; /* uid and gid, or NULL */
+ uid_t uid; /* for ids NULL: legacy */
+ gid_t gid; /* uid and gid */
struct scm_fp_list *fp; /* Passed files */
#ifdef CONFIG_SECURITY_NETWORK
u32 secid; /* Security ID */
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 06/41] netlink: keep classic sender ids in skb->cb
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (4 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 05/41] af_unix: keep sender ids in skb->cb only when they are classic ids Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 07/41] Bluetooth: " Jan Koester
` (4 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
netlink_skb_parms has to fit into skb->cb, and netlink skbs are cloned
together with it for broadcasts, so there is neither room for kuid_t
and kgid_t holding UUIDs nor a single owner for an allocation.
Store the sender ids as classic kernel ids: a UUID or GUID becomes -1,
which reaches the receiver of SCM_CREDENTIALS as the overflow id. The
kernel's own permission checks on netlink messages use the socket's
file credentials, not these.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/linux/netlink.h | 2 +-
include/net/scm.h | 28 ++++++++++++++++++++++++++++
lib/kobject_uevent.c | 8 ++++----
net/netlink/af_netlink.c | 4 ++--
4 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 882e9c1b6c..999c7a352e 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -23,7 +23,7 @@ enum netlink_skb_flags {
};
struct netlink_skb_parms {
- struct scm_creds creds; /* Skb credentials */
+ struct scm_creds_classic creds; /* Skb credentials */
__u32 portid;
__u32 dst_group;
__u32 flags;
diff --git a/include/net/scm.h b/include/net/scm.h
index 86ae6bc109..daa52e3d44 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -23,6 +23,34 @@ struct scm_creds {
kgid_t gid;
};
+/*
+ * Sender credentials for skb->cb of skbs that get cloned along with it
+ * (netlink, bluetooth): no room for kuid_t and kgid_t, and no single owner
+ * for an allocation. The ids are classic kernel ids; a UUID or GUID becomes
+ * -1, which the receiver of SCM_CREDENTIALS sees as the overflow id.
+ */
+struct scm_creds_classic {
+ u32 pid;
+ uid_t uid;
+ gid_t gid;
+};
+
+static inline void scm_creds_to_classic(struct scm_creds_classic *classic,
+ const struct scm_creds *creds)
+{
+ classic->pid = creds->pid;
+ classic->uid = __kuid_val(creds->uid);
+ classic->gid = __kgid_val(creds->gid);
+}
+
+static inline void scm_creds_from_classic(struct scm_creds *creds,
+ const struct scm_creds_classic *classic)
+{
+ creds->pid = classic->pid;
+ creds->uid = KUIDT_INIT(classic->uid);
+ creds->gid = KGIDT_INIT(classic->gid);
+}
+
#ifdef CONFIG_UNIX
struct unix_edge;
#endif
diff --git a/lib/kobject_uevent.c b/lib/kobject_uevent.c
index ddbc4d7482..8b63044881 100644
--- a/lib/kobject_uevent.c
+++ b/lib/kobject_uevent.c
@@ -297,8 +297,8 @@ static struct sk_buff *alloc_uevent_skb(struct kobj_uevent_env *env,
skb_put_data(skb, env->buf, env->buflen);
parms = &NETLINK_CB(skb);
- parms->creds.uid = GLOBAL_ROOT_UID;
- parms->creds.gid = GLOBAL_ROOT_GID;
+ parms->creds.uid = __kuid_val(GLOBAL_ROOT_UID);
+ parms->creds.gid = __kgid_val(GLOBAL_ROOT_GID);
parms->dst_group = 1;
parms->portid = 0;
@@ -362,12 +362,12 @@ static int uevent_net_broadcast_tagged(struct sock *usk,
/* fix uid */
root_uid = make_kuid(owning_user_ns, 0);
if (uid_valid(root_uid))
- parms->creds.uid = root_uid;
+ parms->creds.uid = __kuid_val(root_uid);
/* fix gid */
root_gid = make_kgid(owning_user_ns, 0);
if (gid_valid(root_gid))
- parms->creds.gid = root_gid;
+ parms->creds.gid = __kgid_val(root_gid);
}
ret = netlink_broadcast(usk, skb, 0, 1, GFP_KERNEL);
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
index 3029894eda..282a5d0da4 100644
--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -1901,7 +1901,7 @@ static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
NETLINK_CB(skb).portid = nlk->portid;
NETLINK_CB(skb).dst_group = dst_group;
- NETLINK_CB(skb).creds = scm.creds;
+ scm_creds_to_classic(&NETLINK_CB(skb).creds, &scm.creds);
NETLINK_CB(skb).flags = netlink_skb_flags;
err = -EFAULT;
@@ -1994,7 +1994,7 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
netlink_cmsg_listen_all_nsid(sk, msg, skb);
memset(&scm, 0, sizeof(scm));
- scm.creds = *NETLINK_CREDS(skb);
+ scm_creds_from_classic(&scm.creds, NETLINK_CREDS(skb));
if (flags & MSG_TRUNC)
copied = data_skb->len;
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 07/41] Bluetooth: keep classic sender ids in skb->cb
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (5 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 06/41] netlink: keep classic sender ids in skb->cb Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 08/41] smb: client: use __kuid_val() for the unix SIDs of the caller Jan Koester
` (3 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
struct bt_skb_cb holds the sender's struct scm_creds for HCI sockets,
which no longer fits into skb->cb now that kuid_t and kgid_t can hold
UUIDs and GUIDs. Like netlink, keep them as struct scm_creds_classic:
classic ids as before, the overflow id for UUID and GUID senders.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/net/bluetooth/bluetooth.h | 2 +-
net/bluetooth/hci_sock.c | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index b624da5026..2c8fe50f4d 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -497,7 +497,7 @@ struct bt_skb_cb {
struct l2cap_ctrl l2cap;
struct hci_ctrl hci;
struct mgmt_ctrl mgmt;
- struct scm_creds creds;
+ struct scm_creds_classic creds;
};
};
#define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb))
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 81068b5857..9f422ae5fd 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -276,7 +276,7 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb)
static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)
{
- struct scm_creds *creds;
+ struct scm_creds_classic *creds;
if (!sk || WARN_ON(!skb))
return;
@@ -300,8 +300,8 @@ static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)
creds->pid = pid_vnr(sk->sk_peer_pid);
if (sk->sk_peer_cred) {
- creds->uid = sk->sk_peer_cred->uid;
- creds->gid = sk->sk_peer_cred->gid;
+ creds->uid = __kuid_val(sk->sk_peer_cred->uid);
+ creds->gid = __kgid_val(sk->sk_peer_cred->gid);
}
}
@@ -1615,7 +1615,7 @@ static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg,
}
memset(&scm, 0, sizeof(scm));
- scm.creds = bt_cb(skb)->creds;
+ scm_creds_from_classic(&scm.creds, &bt_cb(skb)->creds);
skb_free_datagram(sk, skb);
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 08/41] smb: client: use __kuid_val() for the unix SIDs of the caller
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (6 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 07/41] Bluetooth: " Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID Jan Koester
` (2 subsequent siblings)
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
The S-1-5-88-1-<uid> and S-1-5-88-2-<gid> SIDs sent with a create
request take the raw kernel ids from kuid_t.val, which is gone now that
the ids can be UUIDs and GUIDs. Use __kuid_val()/__kgid_val(), the same
value for classic ids and -1 for the others, which have no unix SID.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
fs/smb/client/cifsacl.c | 2 +-
fs/smb/client/smb2pdu.c | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index c5e47a835f..78ed25b1f7 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -1086,7 +1086,7 @@ unsigned int setup_special_user_owner_ACE(struct smb_ace *pntace)
pntace->sid.sub_auth[0] = sid_unix_NFS_users.sub_auth[0];
pntace->sid.sub_auth[1] = sid_unix_NFS_users.sub_auth[1];
- pntace->sid.sub_auth[2] = cpu_to_le32(current_fsgid().val);
+ pntace->sid.sub_auth[2] = cpu_to_le32(__kgid_val(current_fsgid()));
/* size = 1 + 1 + 2 + 4 + 1 + 1 + 6 + (psid->num_subauth*4) */
pntace->size = cpu_to_le16(ace_size);
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index cfe3c4b4ab..941ca8a3d6 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2731,7 +2731,7 @@ static void setup_owner_group_sids(char *buf)
sids->owner.Authority[5] = 5;
sids->owner.SubAuthorities[0] = cpu_to_le32(88);
sids->owner.SubAuthorities[1] = cpu_to_le32(1);
- sids->owner.SubAuthorities[2] = cpu_to_le32(current_fsuid().val);
+ sids->owner.SubAuthorities[2] = cpu_to_le32(__kuid_val(current_fsuid()));
/* Populate the group ownership fields S-1-5-88-2 */
sids->group.Revision = 1;
@@ -2739,9 +2739,10 @@ static void setup_owner_group_sids(char *buf)
sids->group.Authority[5] = 5;
sids->group.SubAuthorities[0] = cpu_to_le32(88);
sids->group.SubAuthorities[1] = cpu_to_le32(2);
- sids->group.SubAuthorities[2] = cpu_to_le32(current_fsgid().val);
+ sids->group.SubAuthorities[2] = cpu_to_le32(__kgid_val(current_fsgid()));
- cifs_dbg(FYI, "owner S-1-5-88-1-%d, group S-1-5-88-2-%d\n", current_fsuid().val, current_fsgid().val);
+ cifs_dbg(FYI, "owner S-1-5-88-1-%d, group S-1-5-88-2-%d\n",
+ __kuid_val(current_fsuid()), __kgid_val(current_fsgid()));
}
/* See MS-SMB2 2.2.13.2.2 and MS-DTYP 2.4.6 */
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (7 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 08/41] smb: client: use __kuid_val() for the unix SIDs of the caller Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 19:47 ` Andy Lutomirski
2026-10-18 21:02 ` Eric Biggers
2026-10-18 18:01 ` [RFC PATCH 10/41] uidgid: convert between kernel ids and the ids of the *_r system calls Jan Koester
2026-10-18 18:01 ` [RFC PATCH 11/41] fscrypt: show the whole uid in the description of user key claims Jan Koester
10 siblings, 2 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
Make kuid_t and kgid_t (and vfsuid_t and vfsgid_t, which share their
layout) tagged unions: a classic 32 bit id, an RFC 9562 UUID, or a
Microsoft GUID. KID_LEGACY is 0, so zeroed memory still holds the root
ids, and KUIDT_INIT()/KGIDT_INIT() keep building classic ids, so the
bulk of the kernel needs no change. The member is renamed from val to
legacy so the compiler finds code that peeks at it directly.
Semantics of the new ids:
- They are not in any uid_map or gid_map: they exist in the initial
user namespace only. kuid_has_mapping() is false for them in every
other namespace, so no capability in a child namespace gives
privilege over them, and from_kuid() and friends give -1 (the
overflow id when munged), exactly as for any other id without a
mapping. Anything that can only store or report a 32 bit id thereby
already treats them safely.
- Idmapped mounts map classic ids; UUIDs and GUIDs pass through them
unchanged, as long as the filesystem itself is in the initial user
namespace.
- Ids are ordered by kind first and value second, so sorted group
lists, bsearch() and range checks between classic ids keep working:
a UUID never falls into a range of classic ids.
- __kuid_val() and __kgid_val() stay the classic value of a classic id
and are -1 for the others; hash tables keyed by ids (user_struct,
ucounts) use the new kuid_hash() instead, and the IMA policy
comparators the new ordering.
The structs grow from 4 to 20 bytes: struct cred by 128 bytes, struct
inode by 32 bytes.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
fs/mnt_idmapping.c | 14 ++++
include/linux/mnt_idmapping.h | 74 ++++++++++++++-----
include/linux/uidgid.h | 109 ++++++++++++++++++++++------
include/linux/uidgid_types.h | 32 +++++++-
kernel/groups.c | 15 ++--
kernel/ucount.c | 2 +-
kernel/user.c | 2 +-
kernel/user_namespace.c | 10 ++-
mm/oom_kill.c | 2 +-
security/integrity/ima/ima_policy.c | 8 +-
10 files changed, 209 insertions(+), 59 deletions(-)
diff --git a/fs/mnt_idmapping.c b/fs/mnt_idmapping.c
index cb61fbdb52..c36e74a371 100644
--- a/fs/mnt_idmapping.c
+++ b/fs/mnt_idmapping.c
@@ -87,6 +87,10 @@ vfsuid_t make_vfsuid(struct mnt_idmap *idmap,
return VFSUIDT_INIT(kuid);
if (idmap == &invalid_mnt_idmap)
return INVALID_VFSUID;
+ /* idmappings map classic ids only; UUIDs and GUIDs pass unchanged */
+ if (!uid_is_legacy(kuid))
+ return kuid_has_mapping(fs_userns, kuid) ?
+ VFSUIDT_INIT(kuid) : INVALID_VFSUID;
if (initial_idmapping(fs_userns))
uid = __kuid_val(kuid);
else
@@ -126,6 +130,10 @@ vfsgid_t make_vfsgid(struct mnt_idmap *idmap,
return VFSGIDT_INIT(kgid);
if (idmap == &invalid_mnt_idmap)
return INVALID_VFSGID;
+ /* idmappings map classic ids only; UUIDs and GUIDs pass unchanged */
+ if (!gid_is_legacy(kgid))
+ return kgid_has_mapping(fs_userns, kgid) ?
+ VFSGIDT_INIT(kgid) : INVALID_VFSGID;
if (initial_idmapping(fs_userns))
gid = __kgid_val(kgid);
else
@@ -156,6 +164,9 @@ kuid_t from_vfsuid(struct mnt_idmap *idmap,
return AS_KUIDT(vfsuid);
if (idmap == &invalid_mnt_idmap)
return INVALID_UID;
+ if (!vfsuid_is_legacy(vfsuid))
+ return kuid_has_mapping(fs_userns, AS_KUIDT(vfsuid)) ?
+ AS_KUIDT(vfsuid) : INVALID_UID;
uid = map_id_up(&idmap->uid_map, __vfsuid_val(vfsuid));
if (uid == (uid_t)-1)
return INVALID_UID;
@@ -185,6 +196,9 @@ kgid_t from_vfsgid(struct mnt_idmap *idmap,
return AS_KGIDT(vfsgid);
if (idmap == &invalid_mnt_idmap)
return INVALID_GID;
+ if (!vfsgid_is_legacy(vfsgid))
+ return kgid_has_mapping(fs_userns, AS_KGIDT(vfsgid)) ?
+ AS_KGIDT(vfsgid) : INVALID_GID;
gid = map_id_up(&idmap->gid_map, __vfsgid_val(vfsgid));
if (gid == (gid_t)-1)
return INVALID_GID;
diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h
index e71a6070a8..40f0f994ad 100644
--- a/include/linux/mnt_idmapping.h
+++ b/include/linux/mnt_idmapping.h
@@ -12,33 +12,55 @@ extern struct mnt_idmap nop_mnt_idmap;
extern struct mnt_idmap invalid_mnt_idmap;
extern struct user_namespace init_user_ns;
+/* Like kuid_t and kgid_t: a classic id, a UUID or a GUID */
typedef struct {
- uid_t val;
+ u32 type;
+ union {
+ uid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} vfsuid_t;
typedef struct {
- gid_t val;
+ u32 type;
+ union {
+ gid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} vfsgid_t;
static_assert(sizeof(vfsuid_t) == sizeof(kuid_t));
static_assert(sizeof(vfsgid_t) == sizeof(kgid_t));
-static_assert(offsetof(vfsuid_t, val) == offsetof(kuid_t, val));
-static_assert(offsetof(vfsgid_t, val) == offsetof(kgid_t, val));
+static_assert(offsetof(vfsuid_t, uuid) == offsetof(kuid_t, uuid));
+static_assert(offsetof(vfsgid_t, uuid) == offsetof(kgid_t, uuid));
static inline bool is_valid_mnt_idmap(const struct mnt_idmap *idmap)
{
return idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap;
}
+static inline bool vfsuid_is_legacy(vfsuid_t uid)
+{
+ return uid.type == KID_LEGACY;
+}
+
+static inline bool vfsgid_is_legacy(vfsgid_t gid)
+{
+ return gid.type == KID_LEGACY;
+}
+
+/* The classic value of a legacy id, -1 for a UUID or GUID */
#ifdef CONFIG_MULTIUSER
static inline uid_t __vfsuid_val(vfsuid_t uid)
{
- return uid.val;
+ return vfsuid_is_legacy(uid) ? uid.legacy : (uid_t)-1;
}
static inline gid_t __vfsgid_val(vfsgid_t gid)
{
- return gid.val;
+ return vfsgid_is_legacy(gid) ? gid.legacy : (gid_t)-1;
}
#else
static inline uid_t __vfsuid_val(vfsuid_t uid)
@@ -54,22 +76,34 @@ static inline gid_t __vfsgid_val(vfsgid_t gid)
static inline bool vfsuid_valid(vfsuid_t uid)
{
- return __vfsuid_val(uid) != (uid_t)-1;
+ return !vfsuid_is_legacy(uid) || __vfsuid_val(uid) != (uid_t)-1;
}
static inline bool vfsgid_valid(vfsgid_t gid)
{
- return __vfsgid_val(gid) != (gid_t)-1;
+ return !vfsgid_is_legacy(gid) || __vfsgid_val(gid) != (gid_t)-1;
+}
+
+static inline int vfsuid_cmp(vfsuid_t left, vfsuid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
+}
+
+static inline int vfsgid_cmp(vfsgid_t left, vfsgid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
}
static inline bool vfsuid_eq(vfsuid_t left, vfsuid_t right)
{
- return vfsuid_valid(left) && __vfsuid_val(left) == __vfsuid_val(right);
+ return vfsuid_valid(left) && vfsuid_cmp(left, right) == 0;
}
static inline bool vfsgid_eq(vfsgid_t left, vfsgid_t right)
{
- return vfsgid_valid(left) && __vfsgid_val(left) == __vfsgid_val(right);
+ return vfsgid_valid(left) && vfsgid_cmp(left, right) == 0;
}
/**
@@ -84,7 +118,9 @@ static inline bool vfsgid_eq(vfsgid_t left, vfsgid_t right)
*/
static inline bool vfsuid_eq_kuid(vfsuid_t vfsuid, kuid_t kuid)
{
- return vfsuid_valid(vfsuid) && __vfsuid_val(vfsuid) == __kuid_val(kuid);
+ return vfsuid_valid(vfsuid) &&
+ __kid_cmp(vfsuid.type, vfsuid.legacy, vfsuid.uuid.b,
+ kuid.type, kuid.legacy, kuid.uuid.b) == 0;
}
/**
@@ -99,15 +135,17 @@ static inline bool vfsuid_eq_kuid(vfsuid_t vfsuid, kuid_t kuid)
*/
static inline bool vfsgid_eq_kgid(vfsgid_t vfsgid, kgid_t kgid)
{
- return vfsgid_valid(vfsgid) && __vfsgid_val(vfsgid) == __kgid_val(kgid);
+ return vfsgid_valid(vfsgid) &&
+ __kid_cmp(vfsgid.type, vfsgid.legacy, vfsgid.uuid.b,
+ kgid.type, kgid.legacy, kgid.uuid.b) == 0;
}
/*
* vfs{g,u}ids are created from k{g,u}ids.
* We don't allow them to be created from regular {u,g}id.
*/
-#define VFSUIDT_INIT(val) (vfsuid_t){ __kuid_val(val) }
-#define VFSGIDT_INIT(val) (vfsgid_t){ __kgid_val(val) }
+#define VFSUIDT_INIT(val) (vfsuid_t){ .type = (val).type, .uuid = (val).uuid }
+#define VFSGIDT_INIT(val) (vfsgid_t){ .type = (val).type, .uuid = (val).uuid }
#define INVALID_VFSUID VFSUIDT_INIT(INVALID_UID)
#define INVALID_VFSGID VFSGIDT_INIT(INVALID_GID)
@@ -116,8 +154,8 @@ static inline bool vfsgid_eq_kgid(vfsgid_t vfsgid, kgid_t kgid)
* Allow a vfs{g,u}id to be used as a k{g,u}id where we want to compare
* whether the mapped value is identical to value of a k{g,u}id.
*/
-#define AS_KUIDT(val) (kuid_t){ __vfsuid_val(val) }
-#define AS_KGIDT(val) (kgid_t){ __vfsgid_val(val) }
+#define AS_KUIDT(val) (kuid_t){ .type = (val).type, .uuid = (val).uuid }
+#define AS_KGIDT(val) (kgid_t){ .type = (val).type, .uuid = (val).uuid }
int vfsgid_in_group_p(vfsgid_t vfsgid);
@@ -158,7 +196,7 @@ static inline bool vfsuid_has_fsmapping(struct mnt_idmap *idmap,
static inline bool vfsuid_has_mapping(struct user_namespace *userns,
vfsuid_t vfsuid)
{
- return from_kuid(userns, AS_KUIDT(vfsuid)) != (uid_t)-1;
+ return kuid_has_mapping(userns, AS_KUIDT(vfsuid));
}
/**
@@ -196,7 +234,7 @@ static inline bool vfsgid_has_fsmapping(struct mnt_idmap *idmap,
static inline bool vfsgid_has_mapping(struct user_namespace *userns,
vfsgid_t vfsgid)
{
- return from_kgid(userns, AS_KGIDT(vfsgid)) != (gid_t)-1;
+ return kgid_has_mapping(userns, AS_KGIDT(vfsgid));
}
/**
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index 182c682458..e1adb50d5d 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -19,32 +19,37 @@ struct user_namespace;
extern struct user_namespace init_user_ns;
struct uid_gid_map;
-#define KUIDT_INIT(value) (kuid_t){ value }
-#define KGIDT_INIT(value) (kgid_t){ value }
+#define KUIDT_INIT(value) (kuid_t){ .type = KID_LEGACY, .legacy = (value) }
+#define KGIDT_INIT(value) (kgid_t){ .type = KID_LEGACY, .legacy = (value) }
+
+#define KUIDT_INIT_UUID(value) (kuid_t){ .type = KID_UUID, .uuid = (value) }
+#define KGIDT_INIT_UUID(value) (kgid_t){ .type = KID_UUID, .uuid = (value) }
+#define KUIDT_INIT_GUID(value) (kuid_t){ .type = KID_GUID, .guid = (value) }
+#define KGIDT_INIT_GUID(value) (kgid_t){ .type = KID_GUID, .guid = (value) }
-/*
- * Whether an id is a classic 32 bit id. All of them are for now; code that
- * needs one (to store it in 32 bits, for example) checks with these.
- */
static inline bool uid_is_legacy(kuid_t uid)
{
- return true;
+ return uid.type == KID_LEGACY;
}
static inline bool gid_is_legacy(kgid_t gid)
{
- return true;
+ return gid.type == KID_LEGACY;
}
+/*
+ * __kuid_val() and __kgid_val() are the classic value of a legacy id;
+ * a UUID or GUID has none and gives -1, which is never a valid id.
+ */
#ifdef CONFIG_MULTIUSER
static inline uid_t __kuid_val(kuid_t uid)
{
- return uid.val;
+ return uid_is_legacy(uid) ? uid.legacy : (uid_t)-1;
}
static inline gid_t __kgid_val(kgid_t gid)
{
- return gid.val;
+ return gid_is_legacy(gid) ? gid.legacy : (gid_t)-1;
}
#else
static inline uid_t __kuid_val(kuid_t uid)
@@ -64,64 +69,115 @@ static inline gid_t __kgid_val(kgid_t gid)
#define INVALID_UID KUIDT_INIT(-1)
#define INVALID_GID KGIDT_INIT(-1)
+/*
+ * Ids are ordered by kind first (legacy ids before UUIDs before GUIDs),
+ * then by value: numerically for legacy ids, bytewise for the others.
+ * That keeps sorted id arrays and range checks between legacy ids
+ * working: a UUID is never inside a range of legacy ids.
+ */
+static inline int __kid_cmp(u32 ltype, u32 llegacy, const u8 *lb,
+ u32 rtype, u32 rlegacy, const u8 *rb)
+{
+ if (!IS_ENABLED(CONFIG_MULTIUSER))
+ return 0;
+ if (ltype != rtype)
+ return ltype < rtype ? -1 : 1;
+ if (ltype == KID_LEGACY)
+ return llegacy == rlegacy ? 0 : llegacy < rlegacy ? -1 : 1;
+ return __builtin_memcmp(lb, rb, UUID_SIZE);
+}
+
+static inline int uid_cmp(kuid_t left, kuid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
+}
+
+static inline int gid_cmp(kgid_t left, kgid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
+}
+
static inline bool uid_eq(kuid_t left, kuid_t right)
{
- return __kuid_val(left) == __kuid_val(right);
+ return uid_cmp(left, right) == 0;
}
static inline bool gid_eq(kgid_t left, kgid_t right)
{
- return __kgid_val(left) == __kgid_val(right);
+ return gid_cmp(left, right) == 0;
}
static inline bool uid_gt(kuid_t left, kuid_t right)
{
- return __kuid_val(left) > __kuid_val(right);
+ return uid_cmp(left, right) > 0;
}
static inline bool gid_gt(kgid_t left, kgid_t right)
{
- return __kgid_val(left) > __kgid_val(right);
+ return gid_cmp(left, right) > 0;
}
static inline bool uid_gte(kuid_t left, kuid_t right)
{
- return __kuid_val(left) >= __kuid_val(right);
+ return uid_cmp(left, right) >= 0;
}
static inline bool gid_gte(kgid_t left, kgid_t right)
{
- return __kgid_val(left) >= __kgid_val(right);
+ return gid_cmp(left, right) >= 0;
}
static inline bool uid_lt(kuid_t left, kuid_t right)
{
- return __kuid_val(left) < __kuid_val(right);
+ return uid_cmp(left, right) < 0;
}
static inline bool gid_lt(kgid_t left, kgid_t right)
{
- return __kgid_val(left) < __kgid_val(right);
+ return gid_cmp(left, right) < 0;
}
static inline bool uid_lte(kuid_t left, kuid_t right)
{
- return __kuid_val(left) <= __kuid_val(right);
+ return uid_cmp(left, right) <= 0;
}
static inline bool gid_lte(kgid_t left, kgid_t right)
{
- return __kgid_val(left) <= __kgid_val(right);
+ return gid_cmp(left, right) <= 0;
}
static inline bool uid_valid(kuid_t uid)
{
- return __kuid_val(uid) != (uid_t) -1;
+ return !uid_is_legacy(uid) || __kuid_val(uid) != (uid_t) -1;
}
static inline bool gid_valid(kgid_t gid)
{
- return __kgid_val(gid) != (gid_t) -1;
+ return !gid_is_legacy(gid) || __kgid_val(gid) != (gid_t) -1;
+}
+
+/* A hash of an id, for hash tables keyed by kuid_t or kgid_t */
+static inline u32 __kid_hash(u32 type, u32 legacy, const u8 *b)
+{
+ u32 w[4];
+
+ if (type == KID_LEGACY)
+ return legacy;
+ __builtin_memcpy(w, b, sizeof(w));
+ return w[0] ^ w[1] ^ w[2] ^ w[3] ^ type;
+}
+
+static inline u32 kuid_hash(kuid_t uid)
+{
+ return __kid_hash(uid.type, __kuid_val(uid), uid.uuid.b);
+}
+
+static inline u32 kgid_hash(kgid_t gid)
+{
+ return __kid_hash(gid.type, __kgid_val(gid), gid.uuid.b);
}
#ifdef CONFIG_USER_NS
@@ -134,13 +190,22 @@ extern gid_t from_kgid(struct user_namespace *to, kgid_t gid);
extern uid_t from_kuid_munged(struct user_namespace *to, kuid_t uid);
extern gid_t from_kgid_munged(struct user_namespace *to, kgid_t gid);
+/*
+ * UUIDs and GUIDs aren't in any namespace's uid_map or gid_map: they
+ * exist in the initial user namespace only, and nobody has privilege
+ * over them through a capability in another one.
+ */
static inline bool kuid_has_mapping(struct user_namespace *ns, kuid_t uid)
{
+ if (!uid_is_legacy(uid))
+ return ns == &init_user_ns;
return from_kuid(ns, uid) != (uid_t) -1;
}
static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid)
{
+ if (!gid_is_legacy(gid))
+ return ns == &init_user_ns;
return from_kgid(ns, gid) != (gid_t) -1;
}
diff --git a/include/linux/uidgid_types.h b/include/linux/uidgid_types.h
index b35ac4955a..0f595d075b 100644
--- a/include/linux/uidgid_types.h
+++ b/include/linux/uidgid_types.h
@@ -3,13 +3,41 @@
#define _LINUX_UIDGID_TYPES_H
#include <linux/types.h>
+#include <linux/uuid_types.h>
+/*
+ * Kinds of kuid_t and kgid_t. KID_LEGACY is 0, so zeroed memory still
+ * holds GLOBAL_ROOT_UID and GLOBAL_ROOT_GID as it did when the ids were
+ * plain numbers. Userspace has its own numbering (UID_R_* in
+ * <uapi/linux/uidgid_r.h>), in which zero is "no id".
+ */
+enum kid_type {
+ KID_LEGACY = 0, /* classic 32 bit id in .legacy */
+ KID_UUID, /* RFC 9562 UUID in .uuid */
+ KID_GUID, /* Microsoft GUID in .guid */
+};
+
+/*
+ * Only the member .type selects is meaningful; the bytes of .uuid past
+ * .legacy are undefined for a KID_LEGACY id. Copy the whole struct and
+ * use the helpers of <linux/uidgid.h> to look at it.
+ */
typedef struct {
- uid_t val;
+ u32 type;
+ union {
+ uid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} kuid_t;
typedef struct {
- gid_t val;
+ u32 type;
+ union {
+ gid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} kgid_t;
#endif /* _LINUX_UIDGID_TYPES_H */
diff --git a/kernel/groups.c b/kernel/groups.c
index b5e3be6a6b..3aa0651d7a 100644
--- a/kernel/groups.c
+++ b/kernel/groups.c
@@ -73,18 +73,15 @@ static int groups_from_user(struct group_info *group_info,
return 0;
}
-static int gid_cmp(const void *_a, const void *_b)
+static int groups_cmp(const void *_a, const void *_b)
{
- kgid_t a = *(kgid_t *)_a;
- kgid_t b = *(kgid_t *)_b;
-
- return gid_gt(a, b) - gid_lt(a, b);
+ return gid_cmp(*(kgid_t *)_a, *(kgid_t *)_b);
}
void groups_sort(struct group_info *group_info)
{
sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid),
- gid_cmp, NULL);
+ groups_cmp, NULL);
}
EXPORT_SYMBOL(groups_sort);
@@ -100,9 +97,11 @@ int groups_search(const struct group_info *group_info, kgid_t grp)
right = group_info->ngroups;
while (left < right) {
unsigned int mid = (left+right)/2;
- if (gid_gt(grp, group_info->gid[mid]))
+ int cmp = gid_cmp(grp, group_info->gid[mid]);
+
+ if (cmp > 0)
left = mid + 1;
- else if (gid_lt(grp, group_info->gid[mid]))
+ else if (cmp < 0)
right = mid;
else
return 1;
diff --git a/kernel/ucount.c b/kernel/ucount.c
index ec8b1445e2..d34bc9ea9d 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -23,7 +23,7 @@ static struct hlist_nulls_head ucounts_hashtable[UCOUNTS_HASHTABLE_ENTRIES] = {
static DEFINE_SPINLOCK(ucounts_lock);
#define ucounts_hashfn(ns, uid) \
- hash_long((unsigned long)__kuid_val(uid) + (unsigned long)(ns), \
+ hash_long((unsigned long)kuid_hash(uid) + (unsigned long)(ns), \
UCOUNTS_HASHTABLE_BITS)
#define ucounts_hashentry(ns, uid) \
(ucounts_hashtable + ucounts_hashfn(ns, uid))
diff --git a/kernel/user.c b/kernel/user.c
index 21bafdc113..e41806bb70 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -88,7 +88,7 @@ EXPORT_SYMBOL_GPL(init_user_ns);
#define UIDHASH_SZ (1 << UIDHASH_BITS)
#define UIDHASH_MASK (UIDHASH_SZ - 1)
#define __uidhashfn(uid) (((uid >> UIDHASH_BITS) + uid) & UIDHASH_MASK)
-#define uidhashentry(uid) (uidhash_table + __uidhashfn((__kuid_val(uid))))
+#define uidhashentry(uid) (uidhash_table + __uidhashfn(kuid_hash(uid)))
static struct kmem_cache *uid_cachep;
static struct hlist_head uidhash_table[UIDHASH_SZ];
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 0bed462e9b..88fc8ba586 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -436,10 +436,13 @@ EXPORT_SYMBOL(make_kuid);
*
* There is always a mapping into the initial user_namespace.
*
- * If @kuid has no mapping in @targ (uid_t)-1 is returned.
+ * If @kuid has no mapping in @targ (uid_t)-1 is returned. A UUID or
+ * GUID has no classic uid in any user namespace.
*/
uid_t from_kuid(struct user_namespace *targ, kuid_t kuid)
{
+ if (!uid_is_legacy(kuid))
+ return (uid_t) -1;
/* Map the uid from a global kernel uid */
return map_id_up(&targ->uid_map, __kuid_val(kuid));
}
@@ -504,10 +507,13 @@ EXPORT_SYMBOL(make_kgid);
*
* There is always a mapping into the initial user_namespace.
*
- * If @kgid has no mapping in @targ (gid_t)-1 is returned.
+ * If @kgid has no mapping in @targ (gid_t)-1 is returned. A UUID or
+ * GUID has no classic gid in any user namespace.
*/
gid_t from_kgid(struct user_namespace *targ, kgid_t kgid)
{
+ if (!gid_is_legacy(kgid))
+ return (gid_t) -1;
/* Map the gid from a global kernel gid */
return map_id_up(&targ->gid_map, __kgid_val(kgid));
}
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index 5f372f6e26..2ce3072931 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -771,7 +771,7 @@ static void mark_oom_victim(struct task_struct *tsk)
thaw_process(tsk);
atomic_inc(&oom_victims);
cred = get_task_cred(tsk);
- trace_mark_victim(tsk, cred->uid.val);
+ trace_mark_victim(tsk, __kuid_val(cred->uid));
put_cred(cred);
}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 68d9a5e6c2..839b88ea20 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -81,22 +81,22 @@ struct ima_rule_opt_list {
*/
static inline bool vfsuid_gt_kuid(vfsuid_t vfsuid, kuid_t kuid)
{
- return __vfsuid_val(vfsuid) > __kuid_val(kuid);
+ return vfsuid_cmp(vfsuid, VFSUIDT_INIT(kuid)) > 0;
}
static inline bool vfsgid_gt_kgid(vfsgid_t vfsgid, kgid_t kgid)
{
- return __vfsgid_val(vfsgid) > __kgid_val(kgid);
+ return vfsgid_cmp(vfsgid, VFSGIDT_INIT(kgid)) > 0;
}
static inline bool vfsuid_lt_kuid(vfsuid_t vfsuid, kuid_t kuid)
{
- return __vfsuid_val(vfsuid) < __kuid_val(kuid);
+ return vfsuid_cmp(vfsuid, VFSUIDT_INIT(kuid)) < 0;
}
static inline bool vfsgid_lt_kgid(vfsgid_t vfsgid, kgid_t kgid)
{
- return __vfsgid_val(vfsgid) < __kgid_val(kgid);
+ return vfsgid_cmp(vfsgid, VFSGIDT_INIT(kgid)) < 0;
}
struct ima_rule_entry {
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 10/41] uidgid: convert between kernel ids and the ids of the *_r system calls
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (8 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 11/41] fscrypt: show the whole uid in the description of user key claims Jan Koester
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
Add the conversions between kuid_t/kgid_t and __kernel_uid_r_t the new
system calls need: make_kuid_r(), from_kuid_r() and the never failing
from_kuid_r_munged() (and the gid variants), and get_kuid_r() and
put_kuid_r() to copy them from and to userspace.
get_kuid_r() returns INVALID_UID for "no id" -- a NULL pointer,
UID_R_NONE, or a classic -1 -- which the set*id_r() and chown_r() calls
take as "unchanged", like the classic calls take -1. It rejects
malformed ids: unknown types, nonzero reserved bytes, UUIDs other than
RFC 9562 version 4, the nil GUID, and UUIDs or GUIDs given from outside
the initial user namespace.
Also add kuid_str() and kgid_str(), the text form of an id for logs and
procfs: a classic id in decimal, a UUID as usual, a GUID in braces.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
include/linux/uidgid.h | 35 +++++
kernel/Makefile | 1 +
kernel/uidgid_r.c | 323 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 359 insertions(+)
create mode 100644 kernel/uidgid_r.c
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index e1adb50d5d..ac7786f760 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -180,6 +180,17 @@ static inline u32 kgid_hash(kgid_t gid)
return __kid_hash(gid.type, __kgid_val(gid), gid.uuid.b);
}
+/*
+ * The text form of an id, for logs and procfs: a legacy id as a decimal
+ * number, a UUID as "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" and a GUID in
+ * braces, "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}". KID_STR_LEN includes
+ * the terminating NUL.
+ */
+#define KID_STR_LEN 39
+
+char *kuid_str(kuid_t uid, char buf[KID_STR_LEN]);
+char *kgid_str(kgid_t gid, char buf[KID_STR_LEN]);
+
#ifdef CONFIG_USER_NS
extern kuid_t make_kuid(struct user_namespace *from, uid_t uid);
@@ -277,4 +288,28 @@ static inline u32 map_id_up(struct uid_gid_map *map, u32 id)
}
#endif /* CONFIG_USER_NS */
+/*
+ * Conversion between kuid_t/kgid_t and the ids of the *_r system calls
+ * (<uapi/linux/uidgid_r.h>), in kernel/uidgid_r.c.
+ */
+struct __kernel_uid_r;
+
+kuid_t make_kuid_r(struct user_namespace *from, const struct __kernel_uid_r *id);
+kgid_t make_kgid_r(struct user_namespace *from, const struct __kernel_uid_r *id);
+bool from_kuid_r(struct user_namespace *to, kuid_t uid, struct __kernel_uid_r *id);
+bool from_kgid_r(struct user_namespace *to, kgid_t gid, struct __kernel_uid_r *id);
+void from_kuid_r_munged(struct user_namespace *to, kuid_t uid,
+ struct __kernel_uid_r *id);
+void from_kgid_r_munged(struct user_namespace *to, kgid_t gid,
+ struct __kernel_uid_r *id);
+
+int get_kuid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *uid, kuid_t *kuid);
+int get_kgid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *gid, kgid_t *kgid);
+int put_kuid_r(struct user_namespace *to, kuid_t kuid,
+ struct __kernel_uid_r __user *uid);
+int put_kgid_r(struct user_namespace *to, kgid_t kgid,
+ struct __kernel_uid_r __user *gid);
+
#endif /* _LINUX_UIDGID_H */
diff --git a/kernel/Makefile b/kernel/Makefile
index 1e1a316735..813ddbf115 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_BACKTRACE_SELF_TEST) += backtracetest.o
obj-$(CONFIG_COMPAT) += compat.o
obj-$(CONFIG_CGROUPS) += cgroup/
obj-$(CONFIG_UTS_NS) += utsname.o
+obj-y += uidgid_r.o
obj-$(CONFIG_USER_NS) += user_namespace.o
obj-$(CONFIG_PID_NS) += pid_namespace.o
obj-$(CONFIG_IKCONFIG) += configs.o
diff --git a/kernel/uidgid_r.c b/kernel/uidgid_r.c
new file mode 100644
index 0000000000..d71aecddb2
--- /dev/null
+++ b/kernel/uidgid_r.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Conversion between kuid_t/kgid_t and the ids of the *_r system calls
+ * (<uapi/linux/uidgid_r.h>).
+ */
+
+#include <linux/export.h>
+#include <linux/highuid.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+#include <linux/uidgid_r.h>
+#include <linux/user_namespace.h>
+
+/*
+ * Whether @id is well formed: a known type, reserved bytes zero, a UUID of
+ * version 4 with the RFC 9562 variant, a GUID other than the nil GUID.
+ */
+static bool kid_r_valid(const struct __kernel_uid_r *id)
+{
+ switch (id->type) {
+ case UID_R_LEGACY:
+ return !memchr_inv(id->uuid + sizeof(id->legacy), 0,
+ UID_R_SIZE - sizeof(id->legacy));
+ case UID_R_UUID:
+ return (id->uuid[6] >> 4) == 4 && (id->uuid[8] & 0xc0) == 0x80;
+ case UID_R_GUID:
+ return memchr_inv(id->guid, 0, UID_R_SIZE);
+ }
+ return false;
+}
+
+/*
+ * The kind and 128 bit value of @id for a kuid_t or kgid_t; false if it is
+ * no UUID or GUID or has no mapping in @ns.
+ */
+static bool kid_r_to_128(struct user_namespace *ns,
+ const struct __kernel_uid_r *id, u32 *type, uuid_t *b)
+{
+ if (!kid_r_valid(id) || ns != &init_user_ns)
+ return false;
+ if (id->type == UID_R_UUID)
+ *type = KID_UUID;
+ else if (id->type == UID_R_GUID)
+ *type = KID_GUID;
+ else
+ return false;
+ memcpy(b->b, id->uuid, UID_R_SIZE);
+ return true;
+}
+
+static bool kid_128_to_r(struct user_namespace *ns, u32 type, const uuid_t *b,
+ struct __kernel_uid_r *id)
+{
+ if (ns != &init_user_ns)
+ return false;
+ id->type = type == KID_UUID ? UID_R_UUID : UID_R_GUID;
+ memcpy(id->uuid, b->b, UID_R_SIZE);
+ return true;
+}
+
+/**
+ * make_kuid_r - map a *_r system call uid into a kuid
+ * @from: user namespace the uid is in
+ * @id: the uid
+ *
+ * Return: the kuid, INVALID_UID if @id is malformed, UID_R_NONE or has no
+ * mapping in @from.
+ */
+kuid_t make_kuid_r(struct user_namespace *from, const struct __kernel_uid_r *id)
+{
+ kuid_t uid = INVALID_UID;
+
+ if (id->type == UID_R_LEGACY && kid_r_valid(id))
+ return make_kuid(from, id->legacy);
+ if (!kid_r_to_128(from, id, &uid.type, &uid.uuid))
+ return INVALID_UID;
+ return uid;
+}
+EXPORT_SYMBOL(make_kuid_r);
+
+/**
+ * make_kgid_r - map a *_r system call gid into a kgid
+ * @from: user namespace the gid is in
+ * @id: the gid
+ *
+ * Return: the kgid, INVALID_GID if @id is malformed, UID_R_NONE or has no
+ * mapping in @from.
+ */
+kgid_t make_kgid_r(struct user_namespace *from, const struct __kernel_uid_r *id)
+{
+ kgid_t gid = INVALID_GID;
+
+ if (id->type == UID_R_LEGACY && kid_r_valid(id))
+ return make_kgid(from, id->legacy);
+ if (!kid_r_to_128(from, id, &gid.type, &gid.uuid))
+ return INVALID_GID;
+ return gid;
+}
+EXPORT_SYMBOL(make_kgid_r);
+
+/**
+ * from_kuid_r - map a kuid into a *_r system call uid
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @id: the uid, all zero (UID_R_NONE) if there is no mapping
+ *
+ * Return: whether @uid has a mapping in @to.
+ */
+bool from_kuid_r(struct user_namespace *to, kuid_t uid, struct __kernel_uid_r *id)
+{
+ memset(id, 0, sizeof(*id));
+ if (uid_is_legacy(uid)) {
+ uid_t v = from_kuid(to, uid);
+
+ if (v == (uid_t)-1)
+ return false;
+ id->type = UID_R_LEGACY;
+ id->legacy = v;
+ return true;
+ }
+ return kid_128_to_r(to, uid.type, &uid.uuid, id);
+}
+EXPORT_SYMBOL(from_kuid_r);
+
+/**
+ * from_kgid_r - map a kgid into a *_r system call gid
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @id: the gid, all zero (UID_R_NONE) if there is no mapping
+ *
+ * Return: whether @gid has a mapping in @to.
+ */
+bool from_kgid_r(struct user_namespace *to, kgid_t gid, struct __kernel_uid_r *id)
+{
+ memset(id, 0, sizeof(*id));
+ if (gid_is_legacy(gid)) {
+ gid_t v = from_kgid(to, gid);
+
+ if (v == (gid_t)-1)
+ return false;
+ id->type = UID_R_LEGACY;
+ id->legacy = v;
+ return true;
+ }
+ return kid_128_to_r(to, gid.type, &gid.uuid, id);
+}
+EXPORT_SYMBOL(from_kgid_r);
+
+/**
+ * from_kuid_r_munged - map a kuid into a *_r system call uid, never failing
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @id: the uid, overflowuid if @uid has no mapping in @to
+ */
+void from_kuid_r_munged(struct user_namespace *to, kuid_t uid,
+ struct __kernel_uid_r *id)
+{
+ if (!from_kuid_r(to, uid, id)) {
+ id->type = UID_R_LEGACY;
+ id->legacy = overflowuid;
+ }
+}
+EXPORT_SYMBOL(from_kuid_r_munged);
+
+/**
+ * from_kgid_r_munged - map a kgid into a *_r system call gid, never failing
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @id: the gid, overflowgid if @gid has no mapping in @to
+ */
+void from_kgid_r_munged(struct user_namespace *to, kgid_t gid,
+ struct __kernel_uid_r *id)
+{
+ if (!from_kgid_r(to, gid, id)) {
+ id->type = UID_R_LEGACY;
+ id->legacy = overflowgid;
+ }
+}
+EXPORT_SYMBOL(from_kgid_r_munged);
+
+/*
+ * "No id" from userspace: no pointer, UID_R_NONE, or the classic -1. The
+ * set*id_r() and chown_r() calls take it as "leave unchanged".
+ */
+static bool kid_r_is_none(const struct __kernel_uid_r *id)
+{
+ return id->type == UID_R_NONE ||
+ (id->type == UID_R_LEGACY && id->legacy == (u32)-1);
+}
+
+/**
+ * get_kuid_r - read a uid of a *_r system call from userspace
+ * @from: user namespace the uid is in
+ * @uid: the uid, or NULL
+ * @kuid: the kuid, INVALID_UID for "no id" (NULL, UID_R_NONE or -1)
+ *
+ * Return: 0, -EFAULT, or -EINVAL if the uid is malformed or has no mapping
+ * in @from.
+ */
+int get_kuid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *uid, kuid_t *kuid)
+{
+ struct __kernel_uid_r id;
+
+ *kuid = INVALID_UID;
+ if (!uid)
+ return 0;
+ if (copy_from_user(&id, uid, sizeof(id)))
+ return -EFAULT;
+ if (kid_r_is_none(&id))
+ return 0;
+ *kuid = make_kuid_r(from, &id);
+ return uid_valid(*kuid) ? 0 : -EINVAL;
+}
+EXPORT_SYMBOL(get_kuid_r);
+
+/**
+ * get_kgid_r - read a gid of a *_r system call from userspace
+ * @from: user namespace the gid is in
+ * @gid: the gid, or NULL
+ * @kgid: the kgid, INVALID_GID for "no id" (NULL, UID_R_NONE or -1)
+ *
+ * Return: 0, -EFAULT, or -EINVAL if the gid is malformed or has no mapping
+ * in @from.
+ */
+int get_kgid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *gid, kgid_t *kgid)
+{
+ struct __kernel_uid_r id;
+
+ *kgid = INVALID_GID;
+ if (!gid)
+ return 0;
+ if (copy_from_user(&id, gid, sizeof(id)))
+ return -EFAULT;
+ if (kid_r_is_none(&id))
+ return 0;
+ *kgid = make_kgid_r(from, &id);
+ return gid_valid(*kgid) ? 0 : -EINVAL;
+}
+EXPORT_SYMBOL(get_kgid_r);
+
+/**
+ * put_kuid_r - write a kuid to userspace as a *_r system call uid
+ * @to: user namespace the uid is wanted in
+ * @kuid: the kuid
+ * @uid: where to put it, overflowuid if @kuid has no mapping in @to
+ *
+ * Return: 0 or -EFAULT.
+ */
+int put_kuid_r(struct user_namespace *to, kuid_t kuid,
+ struct __kernel_uid_r __user *uid)
+{
+ struct __kernel_uid_r id;
+
+ from_kuid_r_munged(to, kuid, &id);
+ return copy_to_user(uid, &id, sizeof(id)) ? -EFAULT : 0;
+}
+EXPORT_SYMBOL(put_kuid_r);
+
+/**
+ * put_kgid_r - write a kgid to userspace as a *_r system call gid
+ * @to: user namespace the gid is wanted in
+ * @kgid: the kgid
+ * @gid: where to put it, overflowgid if @kgid has no mapping in @to
+ *
+ * Return: 0 or -EFAULT.
+ */
+int put_kgid_r(struct user_namespace *to, kgid_t kgid,
+ struct __kernel_uid_r __user *gid)
+{
+ struct __kernel_uid_r id;
+
+ from_kgid_r_munged(to, kgid, &id);
+ return copy_to_user(gid, &id, sizeof(id)) ? -EFAULT : 0;
+}
+EXPORT_SYMBOL(put_kgid_r);
+
+static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
+{
+ switch (type) {
+ case KID_LEGACY:
+ snprintf(buf, KID_STR_LEN, "%u", legacy);
+ break;
+ case KID_UUID:
+ snprintf(buf, KID_STR_LEN, "%pUb", b);
+ break;
+ default:
+ snprintf(buf, KID_STR_LEN, "{%pUl}", b);
+ break;
+ }
+ return buf;
+}
+
+/**
+ * kuid_str - text form of a kuid, for logs and procfs
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding @uid in the initial user namespace: a decimal
+ * number, a UUID, or a GUID in braces.
+ */
+char *kuid_str(kuid_t uid, char buf[KID_STR_LEN])
+{
+ return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf);
+}
+EXPORT_SYMBOL(kuid_str);
+
+/**
+ * kgid_str - text form of a kgid, for logs and procfs
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding @gid in the initial user namespace: a decimal
+ * number, a UUID, or a GUID in braces.
+ */
+char *kgid_str(kgid_t gid, char buf[KID_STR_LEN])
+{
+ return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf);
+}
+EXPORT_SYMBOL(kgid_str);
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 11/41] fscrypt: show the whole uid in the description of user key claims
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
` (9 preceding siblings ...)
2026-10-18 18:01 ` [RFC PATCH 10/41] uidgid: convert between kernel ids and the ids of the *_r system calls Jan Koester
@ 2026-10-18 18:01 ` Jan Koester
10 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-18 18:01 UTC (permalink / raw)
To: Christian Brauner, Alexander Viro, Serge E. Hallyn
Cc: Jan Kara, Theodore Y. Ts'o, Paul Moore, Arnd Bergmann,
linux-fsdevel, linux-api, linux-security-module, linux-arch,
linux-ext4, linux-nfs, linux-cifs, netdev, audit, keyrings,
linux-kernel
The ".fscrypt" keys that account a master key to a user are described
as "<identifier>.uid.<uid>". For a UUID or GUID uid that printed -1;
use kuid_str() so /proc/keys shows who the claim belongs to. Lookups
compare the kuid itself and are not affected.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Jan Koester <jan.koester@gmx.net>
---
fs/crypto/keyring.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index 76e28d1e00..080166267f 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -178,7 +178,7 @@ static struct key_type key_type_fscrypt_user = {
};
#define FSCRYPT_MK_USER_DESCRIPTION_SIZE \
- (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + CONST_STRLEN(".uid.") + 10 + 1)
+ (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + CONST_STRLEN(".uid.") + KID_STR_LEN)
/* Create ->s_master_keys if needed. Synchronized by fscrypt_add_key_mutex. */
static int allocate_filesystem_keyring(struct super_block *sb)
@@ -338,13 +338,14 @@ static int add_master_key_user(struct fscrypt_master_key *mk)
{
kuid_t uid = current_fsuid();
char description[FSCRYPT_MK_USER_DESCRIPTION_SIZE];
+ char uidbuf[KID_STR_LEN];
struct key *quota_key;
struct fscrypt_master_key_user *mk_user;
int err;
- snprintf(description, sizeof(description), "%*phN.uid.%u",
+ snprintf(description, sizeof(description), "%*phN.uid.%s",
FSCRYPT_KEY_IDENTIFIER_SIZE, mk->mk_spec.u.identifier,
- __kuid_val(uid));
+ kuid_str(uid, uidbuf));
quota_key = key_alloc(&key_type_fscrypt_user, description, uid,
current_gid(), current_cred(),
KEY_POS_SEARCH | KEY_USR_VIEW, 0, NULL);
--
2.56.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID
2026-10-18 18:01 ` [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID Jan Koester
@ 2026-10-18 19:47 ` Andy Lutomirski
2026-10-18 21:02 ` Eric Biggers
1 sibling, 0 replies; 15+ messages in thread
From: Andy Lutomirski @ 2026-10-18 19:47 UTC (permalink / raw)
To: Jan Koester
Cc: Christian Brauner, Alexander Viro, Serge E. Hallyn, Jan Kara,
Theodore Y. Ts'o, Paul Moore, Arnd Bergmann, linux-fsdevel,
linux-api, linux-security-module, linux-arch, linux-ext4,
linux-nfs, linux-cifs, netdev, audit, keyrings, linux-kernel
On Sun, Oct 18, 2026 at 11:10 AM Jan Koester <jan.koester@gmx.net> wrote:
>
> Make kuid_t and kgid_t (and vfsuid_t and vfsgid_t, which share their
> layout) tagged unions: a classic 32 bit id, an RFC 9562 UUID, or a
> Microsoft GUID. KID_LEGACY is 0, so zeroed memory still holds the root
> ids, and KUIDT_INIT()/KGIDT_INIT() keep building classic ids, so the
> bulk of the kernel needs no change. The member is renamed from val to
> legacy so the compiler finds code that peeks at it directly.
>
> Semantics of the new ids:
>
> - They are not in any uid_map or gid_map: they exist in the initial
> user namespace only. kuid_has_mapping() is false for them in every
> other namespace, so no capability in a child namespace gives
> privilege over them, and from_kuid() and friends give -1 (the
> overflow id when munged), exactly as for any other id without a
> mapping. Anything that can only store or report a 32 bit id thereby
> already treats them safely.
Why?
>
> - Idmapped mounts map classic ids; UUIDs and GUIDs pass through them
> unchanged, as long as the filesystem itself is in the initial user
> namespace.
Okay, so they pass through but don't work. Sounds unfortunate.
> +/* Like kuid_t and kgid_t: a classic id, a UUID or a GUID */
> typedef struct {
> - uid_t val;
> + u32 type;
> + union {
> + uid_t legacy;
Linux is Linux. small numerical uids are not "legacy".
> + uuid_t uuid;
> + guid_t guid;
I'm at a complete loss. Is guid is only unique on Earth but uuid
unique in the entire universe? Even Microsoft admits that GUID and
UUID are two names for the same thing.
I realize that Microsoft colossally screwed up years ago with their
little endian uuid idea, and I sincerely hope that this is not some
endian thing.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID
2026-10-18 18:01 ` [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID Jan Koester
2026-10-18 19:47 ` Andy Lutomirski
@ 2026-10-18 21:02 ` Eric Biggers
2026-10-19 2:10 ` Jan Koester
1 sibling, 1 reply; 15+ messages in thread
From: Eric Biggers @ 2026-10-18 21:02 UTC (permalink / raw)
To: Jan Koester
Cc: Christian Brauner, Alexander Viro, Serge E. Hallyn, Jan Kara,
Theodore Y. Ts'o, Paul Moore, Arnd Bergmann, linux-fsdevel,
linux-api, linux-security-module, linux-arch, linux-ext4,
linux-nfs, linux-cifs, netdev, audit, keyrings, linux-kernel
On Sun, Oct 18, 2026 at 08:01:30PM +0200, Jan Koester wrote:
> Make kuid_t and kgid_t (and vfsuid_t and vfsgid_t, which share their
> layout) tagged unions: a classic 32 bit id, an RFC 9562 UUID, or a
> Microsoft GUID. KID_LEGACY is 0, so zeroed memory still holds the root
> ids, and KUIDT_INIT()/KGIDT_INIT() keep building classic ids, so the
> bulk of the kernel needs no change. The member is renamed from val to
> legacy so the compiler finds code that peeks at it directly.
Most Linux systems aren't going to want this bloat to the core uid and
gid types. So if this is supported at all (unclear it's worth it...),
it will need to be controlled by a default-off kconfig option.
Side note: the subject line says the series contains 41 patches, but it
seems only 11 were actually sent.
- Eric
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID
2026-10-18 21:02 ` Eric Biggers
@ 2026-10-19 2:10 ` Jan Koester
0 siblings, 0 replies; 15+ messages in thread
From: Jan Koester @ 2026-10-19 2:10 UTC (permalink / raw)
To: Eric Biggers
Cc: Christian Brauner, Alexander Viro, Serge E. Hallyn, Jan Kara,
Theodore Y. Ts'o, Paul Moore, Arnd Bergmann, linux-fsdevel,
linux-api, linux-security-module, linux-arch, linux-ext4,
linux-nfs, linux-cifs, netdev, audit, keyrings, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 978 bytes --]
Hi,
here are all patches. I have problems to 42 mails with my provder sryy for that.
Am 18.10.26 um 23:02 schrieb Eric Biggers:
> On Sun, Oct 18, 2026 at 08:01:30PM +0200, Jan Koester wrote:
>> Make kuid_t and kgid_t (and vfsuid_t and vfsgid_t, which share their
>> layout) tagged unions: a classic 32 bit id, an RFC 9562 UUID, or a
>> Microsoft GUID. KID_LEGACY is 0, so zeroed memory still holds the root
>> ids, and KUIDT_INIT()/KGIDT_INIT() keep building classic ids, so the
>> bulk of the kernel needs no change. The member is renamed from val to
>> legacy so the compiler finds code that peeks at it directly.
>
> Most Linux systems aren't going to want this bloat to the core uid and
> gid types. So if this is supported at all (unclear it's worth it...),
> it will need to be controlled by a default-off kconfig option.
>
> Side note: the subject line says the series contains 41 patches, but it
> seems only 11 were actually sent.
>
> - Eric
[-- Attachment #2: uid_r-0001-uuid-move-uuid_t-and-guid_t-into-linux-uuid_types.h.patch --]
[-- Type: text/x-patch, Size: 1961 bytes --]
From 2c211c3fa29b75f9a75915a291c2eb4aef0977ed Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 04:46:02 +0200
Subject: [PATCH 01/43] uuid: move uuid_t and guid_t into <linux/uuid_types.h>
<linux/uuid.h> pulls in <linux/string.h> for its helpers, which makes it
unusable from headers low in the include chain. The next patches embed
UUIDs and GUIDs in kuid_t and kgid_t, whose <linux/uidgid_types.h> is
included by <linux/sched.h>.
Move the two types and UUID_SIZE into a header of their own that only
needs <linux/types.h>. <linux/uuid.h> includes it, so no user changes.
No functional change.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/uuid.h | 11 +----------
include/linux/uuid_types.h | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+), 10 deletions(-)
create mode 100644 include/linux/uuid_types.h
diff --git a/include/linux/uuid.h b/include/linux/uuid.h
index 43d4a79b2..cbdaf0ca4 100644
--- a/include/linux/uuid.h
+++ b/include/linux/uuid.h
@@ -9,16 +9,7 @@
#define _LINUX_UUID_H_
#include <linux/string.h>
-
-#define UUID_SIZE 16
-
-typedef struct {
- __u8 b[UUID_SIZE];
-} guid_t;
-
-typedef struct {
- __u8 b[UUID_SIZE];
-} uuid_t;
+#include <linux/uuid_types.h>
#define GUID_INIT(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \
((guid_t) \
diff --git a/include/linux/uuid_types.h b/include/linux/uuid_types.h
new file mode 100644
index 000000000..1e45b6957
--- /dev/null
+++ b/include/linux/uuid_types.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UUID/GUID types, without the helpers of <linux/uuid.h>, for headers
+ * low in the include chain (such as <linux/uidgid_types.h>)
+ */
+#ifndef _LINUX_UUID_TYPES_H_
+#define _LINUX_UUID_TYPES_H_
+
+#include <linux/types.h>
+
+#define UUID_SIZE 16
+
+typedef struct {
+ __u8 b[UUID_SIZE];
+} guid_t;
+
+typedef struct {
+ __u8 b[UUID_SIZE];
+} uuid_t;
+
+#endif /* _LINUX_UUID_TYPES_H_ */
[-- Attachment #3: uid_r-0002-uapi-add-linux-uidgid_r.h-for-128-bit-capable-user-a.patch --]
[-- Type: text/x-patch, Size: 2820 bytes --]
From 2de9c6c6992fc63fcdbcb695264b58dbc8f5abf7 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 04:53:23 +0200
Subject: [PATCH 02/43] uapi: add <linux/uidgid_r.h> for 128 bit capable user
and group ids
User and group ids are 32 bit numbers. Identities that come from
elsewhere -- UUIDs handed out by a directory service, or the GUIDs of a
Windows domain -- have to be squeezed into that range through mapping
tables kept in userspace, and the kernel never sees who a process really
is.
Add a tagged id for a new family of system calls (named after the
classic ones with an _r suffix): a classic 32 bit id, an RFC 9562
version 4 UUID, or a Microsoft GUID in the byte order Windows stores it
in. Type 0 is "no id", so a zero-initialized id is never root, and lets
the set*id_r() calls express "unchanged" without a magic number.
The struct has no 64 bit members and the same layout on all ABIs, so
none of the new calls needs a compat entry point.
Assisted-by: Claude:claude-opus-5-5
---
include/uapi/linux/uidgid_r.h | 45 +++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 include/uapi/linux/uidgid_r.h
diff --git a/include/uapi/linux/uidgid_r.h b/include/uapi/linux/uidgid_r.h
new file mode 100644
index 000000000..c3b1cb2ae
--- /dev/null
+++ b/include/uapi/linux/uidgid_r.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * User and group ids for the *_r system calls: a classic 32 bit id, a
+ * UUID or a Microsoft GUID.
+ *
+ * The classic system calls (getuid(), chown(), ...) keep their 32 bit
+ * ids; for an id that has no 32 bit form they see the overflow id
+ * (/proc/sys/kernel/overflowuid and overflowgid), like the 16 bit system
+ * calls do for ids above 65535.
+ */
+#ifndef _UAPI_LINUX_UIDGID_R_H
+#define _UAPI_LINUX_UIDGID_R_H
+
+#include <linux/types.h>
+
+/*
+ * Kinds of id (__kernel_uid_r_t.type). An all-zero id is UID_R_NONE, so a
+ * zero-initialized id never stands for root.
+ */
+#define UID_R_NONE 0 /* no id: "unchanged" or "not set" */
+#define UID_R_LEGACY 1 /* classic 32 bit id in .legacy */
+#define UID_R_UUID 2 /* RFC 9562 UUID, version 4, in .uuid */
+#define UID_R_GUID 3 /* Microsoft GUID in .guid */
+
+#define UID_R_SIZE 16 /* bytes of .uuid and .guid */
+
+/*
+ * .uuid holds the UUID in network byte order, as RFC 9562 lays it out.
+ * .guid holds the GUID as Windows stores it: Data1, Data2 and Data3 little
+ * endian, Data4 as is.
+ *
+ * Bytes past the member .type selects are reserved and must be zero.
+ */
+typedef struct __kernel_uid_r {
+ __u32 type;
+ union {
+ __u32 legacy;
+ __u8 uuid[UID_R_SIZE];
+ __u8 guid[UID_R_SIZE];
+ };
+} __kernel_uid_r_t;
+
+typedef __kernel_uid_r_t __kernel_gid_r_t;
+
+#endif /* _UAPI_LINUX_UIDGID_R_H */
[-- Attachment #4: uid_r-0003-uidgid-add-uid_is_legacy-and-gid_is_legacy.patch --]
[-- Type: text/x-patch, Size: 1305 bytes --]
From 08e37622aa591e4ed71916080efdcfa8fbbaf29c Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:09 +0200
Subject: [PATCH 03/43] uidgid: add uid_is_legacy() and gid_is_legacy()
User and group ids are about to become able to hold a UUID or a GUID
besides a classic 32 bit id. Add the predicates code that can only keep
a classic id (in 32 bits of a buffer, for instance) tests with, so it
can be converted before the types change. For now every id is a classic
one.
No functional change.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/uidgid.h | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index 2dc767e08..182c68245 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -22,6 +22,20 @@ struct uid_gid_map;
#define KUIDT_INIT(value) (kuid_t){ value }
#define KGIDT_INIT(value) (kgid_t){ value }
+/*
+ * Whether an id is a classic 32 bit id. All of them are for now; code that
+ * needs one (to store it in 32 bits, for example) checks with these.
+ */
+static inline bool uid_is_legacy(kuid_t uid)
+{
+ return true;
+}
+
+static inline bool gid_is_legacy(kgid_t gid)
+{
+ return true;
+}
+
#ifdef CONFIG_MULTIUSER
static inline uid_t __kuid_val(kuid_t uid)
{
[-- Attachment #5: uid_r-0004-net-sock-read-sk_uid-through-a-seqcount.patch --]
[-- Type: text/x-patch, Size: 5237 bytes --]
From ef1608126b20e64560dda494e242a59e01f27671 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:09 +0200
Subject: [PATCH 04/43] net: sock: read sk_uid through a seqcount
sk_uid is read without a lock, with READ_ONCE(), by the routing lookups
and the reuseport checks, while sockfs_setattr() may change it. That
only works for as long as a kuid_t is a single word, which it won't be
once ids can be UUIDs and GUIDs.
Add sk_uid_seq next to it: writers bump it around the store and hold
sk_callback_lock (sock_graft(), sockfs_setattr()) or own the socket
alone (sock_init_data_uid()); sk_uid() retries until it sees a whole
id. sk_clone() reads the parent's id the same way rather than relying
on the copy made by sock_copy().
Assisted-by: Claude:claude-opus-5-5
---
include/net/sock.h | 28 +++++++++++++++++++++++++---
net/core/sock.c | 6 +++++-
net/socket.c | 5 +++--
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/include/net/sock.h b/include/net/sock.h
index 60ea55dc1..0986d9466 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -294,6 +294,8 @@ struct sk_filter;
* @sk_ack_backlog: current listen backlog
* @sk_max_ack_backlog: listen backlog set in listen()
* @sk_uid: user id of owner
+ * @sk_uid_seq: makes lockless reads of @sk_uid see a whole id; writers
+ * hold @sk_callback_lock
* @sk_ino: inode number (zero if orphaned)
* @sk_prefer_busy_poll: prefer busypolling over softirq processing
* @sk_busy_poll_budget: napi processing budget when busypolling
@@ -509,6 +511,7 @@ struct sock {
u32 sk_priority;
u32 sk_mark;
kuid_t sk_uid;
+ seqcount_t sk_uid_seq;
u16 sk_protocol;
u16 sk_type;
struct dst_entry __rcu *sk_dst_cache;
@@ -2109,11 +2112,23 @@ static inline int sk_rx_queue_get(const struct sock *sk)
return -1;
}
+/*
+ * sk_uid is too big for WRITE_ONCE(): writers hold sk_callback_lock (or own
+ * the socket alone, before it is published) and bump sk_uid_seq around the
+ * store, so sk_uid() can read it without a lock.
+ */
+static inline void __sk_set_uid(struct sock *sk, kuid_t uid)
+{
+ write_seqcount_begin(&sk->sk_uid_seq);
+ sk->sk_uid = uid;
+ write_seqcount_end(&sk->sk_uid_seq);
+}
+
static inline void sk_set_socket(struct sock *sk, struct socket *sock)
{
WRITE_ONCE(sk->sk_socket, sock);
if (sock) {
- WRITE_ONCE(sk->sk_uid, SOCK_INODE(sock)->i_uid);
+ __sk_set_uid(sk, SOCK_INODE(sock)->i_uid);
WRITE_ONCE(sk->sk_ino, SOCK_INODE(sock)->i_ino);
} else {
/* Note: sk_uid is unchanged. */
@@ -2161,8 +2176,15 @@ static inline u64 sock_i_ino(const struct sock *sk)
static inline kuid_t sk_uid(const struct sock *sk)
{
- /* Paired with WRITE_ONCE() in sockfs_setattr() */
- return READ_ONCE(sk->sk_uid);
+ unsigned int seq;
+ kuid_t uid;
+
+ /* Paired with __sk_set_uid() */
+ do {
+ seq = read_seqcount_begin(&sk->sk_uid_seq);
+ uid = sk->sk_uid;
+ } while (read_seqcount_retry(&sk->sk_uid_seq, seq));
+ return uid;
}
static inline kuid_t sock_net_uid(const struct net *net, const struct sock *sk)
diff --git a/net/core/sock.c b/net/core/sock.c
index e4503ea6f..e814828e4 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -2449,6 +2449,7 @@ static void sk_init_common(struct sock *sk)
skb_queue_head_init(&sk->sk_error_queue);
rwlock_init(&sk->sk_callback_lock);
+ seqcount_init(&sk->sk_uid_seq);
lockdep_set_class_and_name(&sk->sk_receive_queue.lock,
af_rlock_keys + sk->sk_family,
af_family_rlock_key_strings[sk->sk_family]);
@@ -2526,6 +2527,8 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
atomic_set(&newsk->sk_omem_alloc, 0);
sk_init_common(newsk);
+ /* sock_copy() may have caught a half written sk_uid */
+ newsk->sk_uid = sk_uid(sk);
newsk->sk_dst_cache = NULL;
newsk->sk_dst_pending_confirm = 0;
@@ -3770,7 +3773,7 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
} else {
RCU_INIT_POINTER(sk->sk_wq, NULL);
}
- sk->sk_uid = uid;
+ __sk_set_uid(sk, uid);
sk->sk_state_change = sock_def_wakeup;
sk->sk_data_ready = sock_def_readable;
@@ -4611,6 +4614,7 @@ static int __init sock_struct_check(void)
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_priority);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_mark);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_uid);
+ CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_uid_seq);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_protocol);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_dst_cache);
CACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_route_caps);
diff --git a/net/socket.c b/net/socket.c
index 63c69a0fa..abfa71e03 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -654,8 +654,9 @@ static int sockfs_setattr(struct mnt_idmap *idmap,
struct socket *sock = SOCKET_I(d_inode(dentry));
if (sock->sk) {
- /* Paired with READ_ONCE() in sk_uid() */
- WRITE_ONCE(sock->sk->sk_uid, iattr->ia_uid);
+ write_lock_bh(&sock->sk->sk_callback_lock);
+ __sk_set_uid(sock->sk, iattr->ia_uid);
+ write_unlock_bh(&sock->sk->sk_callback_lock);
} else {
err = -ENOENT;
}
[-- Attachment #6: uid_r-0005-af_unix-keep-sender-ids-in-skb-cb-only-when-they-are.patch --]
[-- Type: text/x-patch, Size: 4508 bytes --]
From bc9995e784487b1a01a632fa05cc913c44bea0b3 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:10 +0200
Subject: [PATCH 05/43] af_unix: keep sender ids in skb->cb only when they are
classic ids
unix_skb_parms has to fit into the 48 bytes of skb->cb. With kuid_t and
kgid_t growing to hold UUIDs and GUIDs the two of them alone would take
40 bytes.
Store classic ids as 32 bit values as before, and only for a UUID or
GUID allocate a small struct unix_skb_ids for both, freed together with
the pid reference in unix_destruct_scm(). AF_UNIX skbs carrying a pid
reference are never cloned, so the pointer has a single owner.
Senders with classic ids see no change and no allocation.
Assisted-by: Claude:claude-opus-5-5
---
net/unix/af_unix.c | 50 +++++++++++++++++++++++++++++++++++++++-------
net/unix/af_unix.h | 11 ++++++++--
2 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index c7a03b011..e7af9f870 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1962,11 +1962,46 @@ static void unix_peek_fds(struct scm_cookie *scm, struct sk_buff *skb)
unix_peek_fpl(scm->fp);
}
+/*
+ * The sender's ids, in the skb itself for legacy ids; UUIDs and GUIDs don't
+ * fit in skb->cb and come from an allocation, freed with the pid reference
+ * in unix_destruct_scm().
+ */
+static int unix_skb_set_ids(struct sk_buff *skb, kuid_t uid, kgid_t gid)
+{
+ struct unix_skb_ids *ids = NULL;
+
+ if (!uid_is_legacy(uid) || !gid_is_legacy(gid)) {
+ ids = kmalloc_obj(*ids, GFP_KERNEL_ACCOUNT);
+ if (!ids)
+ return -ENOMEM;
+ ids->uid = uid;
+ ids->gid = gid;
+ }
+ kfree(UNIXCB(skb).ids);
+ UNIXCB(skb).ids = ids;
+ UNIXCB(skb).uid = __kuid_val(uid);
+ UNIXCB(skb).gid = __kgid_val(gid);
+ return 0;
+}
+
+static kuid_t unix_skb_uid(const struct sk_buff *skb)
+{
+ return UNIXCB(skb).ids ? UNIXCB(skb).ids->uid : KUIDT_INIT(UNIXCB(skb).uid);
+}
+
+static kgid_t unix_skb_gid(const struct sk_buff *skb)
+{
+ return UNIXCB(skb).ids ? UNIXCB(skb).ids->gid : KGIDT_INIT(UNIXCB(skb).gid);
+}
+
static void unix_destruct_scm(struct sk_buff *skb)
{
struct scm_cookie scm = {};
swap(scm.pid, UNIXCB(skb).pid);
+ kfree(UNIXCB(skb).ids);
+ UNIXCB(skb).ids = NULL;
if (UNIXCB(skb).fp)
unix_detach_fds(&scm, skb);
@@ -1985,20 +2020,21 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
int err = 0;
UNIXCB(skb).pid = get_pid(scm->pid);
- UNIXCB(skb).uid = scm->creds.uid;
- UNIXCB(skb).gid = scm->creds.gid;
UNIXCB(skb).fp = NULL;
+ skb->destructor = unix_wfree;
+ err = unix_skb_set_ids(skb, scm->creds.uid, scm->creds.gid);
+ if (err)
+ return err;
unix_get_secdata(scm, skb);
if (scm->fp && send_fds)
err = unix_attach_fds(scm, skb);
- skb->destructor = unix_wfree;
return err;
}
static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)
{
- scm_set_cred(scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid);
+ scm_set_cred(scm, UNIXCB(skb).pid, unix_skb_uid(skb), unix_skb_gid(skb));
unix_set_secdata(scm, skb);
}
@@ -2032,7 +2068,7 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
return err;
UNIXCB(skb).pid = get_pid(pid);
- current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid);
+ return unix_skb_set_ids(skb, current_uid(), current_gid());
}
return 0;
@@ -2042,8 +2078,8 @@ static bool unix_skb_scm_eq(struct sk_buff *skb,
struct scm_cookie *scm)
{
return UNIXCB(skb).pid == scm->pid &&
- uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
- gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
+ uid_eq(unix_skb_uid(skb), scm->creds.uid) &&
+ gid_eq(unix_skb_gid(skb), scm->creds.gid) &&
unix_secdata_eq(scm, skb);
}
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef..e861a430c 100644
--- a/net/unix/af_unix.h
+++ b/net/unix/af_unix.h
@@ -10,10 +10,17 @@
struct sock *unix_peer_get(struct sock *sk);
-struct unix_skb_parms {
- struct pid *pid; /* skb credentials */
+/* The sender's ids, when either is a UUID or GUID */
+struct unix_skb_ids {
kuid_t uid;
kgid_t gid;
+};
+
+struct unix_skb_parms {
+ struct pid *pid; /* skb credentials */
+ struct unix_skb_ids *ids; /* uid and gid, or NULL */
+ uid_t uid; /* for ids NULL: legacy */
+ gid_t gid; /* uid and gid */
struct scm_fp_list *fp; /* Passed files */
#ifdef CONFIG_SECURITY_NETWORK
u32 secid; /* Security ID */
[-- Attachment #7: uid_r-0006-netlink-keep-classic-sender-ids-in-skb-cb.patch --]
[-- Type: text/x-patch, Size: 4249 bytes --]
From 569df5c5b28e22abba24652840bcfb06460e86d4 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:10 +0200
Subject: [PATCH 06/43] netlink: keep classic sender ids in skb->cb
netlink_skb_parms has to fit into skb->cb, and netlink skbs are cloned
together with it for broadcasts, so there is neither room for kuid_t
and kgid_t holding UUIDs nor a single owner for an allocation.
Store the sender ids as classic kernel ids: a UUID or GUID becomes -1,
which reaches the receiver of SCM_CREDENTIALS as the overflow id. The
kernel's own permission checks on netlink messages use the socket's
file credentials, not these.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/netlink.h | 2 +-
include/net/scm.h | 28 ++++++++++++++++++++++++++++
lib/kobject_uevent.c | 8 ++++----
net/netlink/af_netlink.c | 4 ++--
4 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 882e9c1b6..999c7a352 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -23,7 +23,7 @@ enum netlink_skb_flags {
};
struct netlink_skb_parms {
- struct scm_creds creds; /* Skb credentials */
+ struct scm_creds_classic creds; /* Skb credentials */
__u32 portid;
__u32 dst_group;
__u32 flags;
diff --git a/include/net/scm.h b/include/net/scm.h
index c52519669..e8a32f2d5 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -23,6 +23,34 @@ struct scm_creds {
kgid_t gid;
};
+/*
+ * Sender credentials for skb->cb of skbs that get cloned along with it
+ * (netlink, bluetooth): no room for kuid_t and kgid_t, and no single owner
+ * for an allocation. The ids are classic kernel ids; a UUID or GUID becomes
+ * -1, which the receiver of SCM_CREDENTIALS sees as the overflow id.
+ */
+struct scm_creds_classic {
+ u32 pid;
+ uid_t uid;
+ gid_t gid;
+};
+
+static inline void scm_creds_to_classic(struct scm_creds_classic *classic,
+ const struct scm_creds *creds)
+{
+ classic->pid = creds->pid;
+ classic->uid = __kuid_val(creds->uid);
+ classic->gid = __kgid_val(creds->gid);
+}
+
+static inline void scm_creds_from_classic(struct scm_creds *creds,
+ const struct scm_creds_classic *classic)
+{
+ creds->pid = classic->pid;
+ creds->uid = KUIDT_INIT(classic->uid);
+ creds->gid = KGIDT_INIT(classic->gid);
+}
+
#ifdef CONFIG_UNIX
struct unix_edge;
#endif
diff --git a/lib/kobject_uevent.c b/lib/kobject_uevent.c
index ddbc4d748..8b6304488 100644
--- a/lib/kobject_uevent.c
+++ b/lib/kobject_uevent.c
@@ -297,8 +297,8 @@ static struct sk_buff *alloc_uevent_skb(struct kobj_uevent_env *env,
skb_put_data(skb, env->buf, env->buflen);
parms = &NETLINK_CB(skb);
- parms->creds.uid = GLOBAL_ROOT_UID;
- parms->creds.gid = GLOBAL_ROOT_GID;
+ parms->creds.uid = __kuid_val(GLOBAL_ROOT_UID);
+ parms->creds.gid = __kgid_val(GLOBAL_ROOT_GID);
parms->dst_group = 1;
parms->portid = 0;
@@ -362,12 +362,12 @@ static int uevent_net_broadcast_tagged(struct sock *usk,
/* fix uid */
root_uid = make_kuid(owning_user_ns, 0);
if (uid_valid(root_uid))
- parms->creds.uid = root_uid;
+ parms->creds.uid = __kuid_val(root_uid);
/* fix gid */
root_gid = make_kgid(owning_user_ns, 0);
if (gid_valid(root_gid))
- parms->creds.gid = root_gid;
+ parms->creds.gid = __kgid_val(root_gid);
}
ret = netlink_broadcast(usk, skb, 0, 1, GFP_KERNEL);
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
index 7288de715..2cc51f5cd 100644
--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -1897,7 +1897,7 @@ static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
NETLINK_CB(skb).portid = nlk->portid;
NETLINK_CB(skb).dst_group = dst_group;
- NETLINK_CB(skb).creds = scm.creds;
+ scm_creds_to_classic(&NETLINK_CB(skb).creds, &scm.creds);
NETLINK_CB(skb).flags = netlink_skb_flags;
err = -EFAULT;
@@ -1990,7 +1990,7 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
netlink_cmsg_listen_all_nsid(sk, msg, skb);
memset(&scm, 0, sizeof(scm));
- scm.creds = *NETLINK_CREDS(skb);
+ scm_creds_from_classic(&scm.creds, NETLINK_CREDS(skb));
if (flags & MSG_TRUNC)
copied = data_skb->len;
[-- Attachment #8: uid_r-0007-Bluetooth-keep-classic-sender-ids-in-skb-cb.patch --]
[-- Type: text/x-patch, Size: 2145 bytes --]
From d7971b6cc6b094a0d9e6c844b153e45d447b9a49 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:25:48 +0200
Subject: [PATCH 07/43] Bluetooth: keep classic sender ids in skb->cb
struct bt_skb_cb holds the sender's struct scm_creds for HCI sockets,
which no longer fits into skb->cb now that kuid_t and kgid_t can hold
UUIDs and GUIDs. Like netlink, keep them as struct scm_creds_classic:
classic ids as before, the overflow id for UUID and GUID senders.
Assisted-by: Claude:claude-opus-5-5
---
include/net/bluetooth/bluetooth.h | 2 +-
net/bluetooth/hci_sock.c | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index b624da502..2c8fe50f4 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -497,7 +497,7 @@ struct bt_skb_cb {
struct l2cap_ctrl l2cap;
struct hci_ctrl hci;
struct mgmt_ctrl mgmt;
- struct scm_creds creds;
+ struct scm_creds_classic creds;
};
};
#define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb))
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 81068b585..9f422ae5f 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -276,7 +276,7 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb)
static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)
{
- struct scm_creds *creds;
+ struct scm_creds_classic *creds;
if (!sk || WARN_ON(!skb))
return;
@@ -300,8 +300,8 @@ static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)
creds->pid = pid_vnr(sk->sk_peer_pid);
if (sk->sk_peer_cred) {
- creds->uid = sk->sk_peer_cred->uid;
- creds->gid = sk->sk_peer_cred->gid;
+ creds->uid = __kuid_val(sk->sk_peer_cred->uid);
+ creds->gid = __kgid_val(sk->sk_peer_cred->gid);
}
}
@@ -1615,7 +1615,7 @@ static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg,
}
memset(&scm, 0, sizeof(scm));
- scm.creds = bt_cb(skb)->creds;
+ scm_creds_from_classic(&scm.creds, &bt_cb(skb)->creds);
skb_free_datagram(sk, skb);
[-- Attachment #9: uid_r-0008-smb-client-use-__kuid_val-for-the-unix-SIDs-of-the-c.patch --]
[-- Type: text/x-patch, Size: 2502 bytes --]
From 225a9221002f52d05cecffd96c09bb83fb4ab23f Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:25:48 +0200
Subject: [PATCH 08/43] smb: client: use __kuid_val() for the unix SIDs of the
caller
The S-1-5-88-1-<uid> and S-1-5-88-2-<gid> SIDs sent with a create
request take the raw kernel ids from kuid_t.val, which is gone now that
the ids can be UUIDs and GUIDs. Use __kuid_val()/__kgid_val(), the same
value for classic ids and -1 for the others, which have no unix SID.
Assisted-by: Claude:claude-opus-5-5
---
fs/smb/client/cifsacl.c | 2 +-
fs/smb/client/smb2pdu.c | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index c5e47a835..78ed25b1f 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -1086,7 +1086,7 @@ unsigned int setup_special_user_owner_ACE(struct smb_ace *pntace)
pntace->sid.sub_auth[0] = sid_unix_NFS_users.sub_auth[0];
pntace->sid.sub_auth[1] = sid_unix_NFS_users.sub_auth[1];
- pntace->sid.sub_auth[2] = cpu_to_le32(current_fsgid().val);
+ pntace->sid.sub_auth[2] = cpu_to_le32(__kgid_val(current_fsgid()));
/* size = 1 + 1 + 2 + 4 + 1 + 1 + 6 + (psid->num_subauth*4) */
pntace->size = cpu_to_le16(ace_size);
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index cfe3c4b4a..941ca8a3d 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2731,7 +2731,7 @@ static void setup_owner_group_sids(char *buf)
sids->owner.Authority[5] = 5;
sids->owner.SubAuthorities[0] = cpu_to_le32(88);
sids->owner.SubAuthorities[1] = cpu_to_le32(1);
- sids->owner.SubAuthorities[2] = cpu_to_le32(current_fsuid().val);
+ sids->owner.SubAuthorities[2] = cpu_to_le32(__kuid_val(current_fsuid()));
/* Populate the group ownership fields S-1-5-88-2 */
sids->group.Revision = 1;
@@ -2739,9 +2739,10 @@ static void setup_owner_group_sids(char *buf)
sids->group.Authority[5] = 5;
sids->group.SubAuthorities[0] = cpu_to_le32(88);
sids->group.SubAuthorities[1] = cpu_to_le32(2);
- sids->group.SubAuthorities[2] = cpu_to_le32(current_fsgid().val);
+ sids->group.SubAuthorities[2] = cpu_to_le32(__kgid_val(current_fsgid()));
- cifs_dbg(FYI, "owner S-1-5-88-1-%d, group S-1-5-88-2-%d\n", current_fsuid().val, current_fsgid().val);
+ cifs_dbg(FYI, "owner S-1-5-88-1-%d, group S-1-5-88-2-%d\n",
+ __kuid_val(current_fsuid()), __kgid_val(current_fsgid()));
}
/* See MS-SMB2 2.2.13.2.2 and MS-DTYP 2.4.6 */
[-- Attachment #10: uid_r-0009-uidgid-let-kuid_t-and-kgid_t-hold-a-UUID-or-a-GUID.patch --]
[-- Type: text/x-patch, Size: 21242 bytes --]
From 093cde89699173d36105300d26d92a2f4bf5475a Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:33 +0200
Subject: [PATCH 09/43] uidgid: let kuid_t and kgid_t hold a UUID or a GUID
Make kuid_t and kgid_t (and vfsuid_t and vfsgid_t, which share their
layout) tagged unions: a classic 32 bit id, an RFC 9562 UUID, or a
Microsoft GUID. KID_LEGACY is 0, so zeroed memory still holds the root
ids, and KUIDT_INIT()/KGIDT_INIT() keep building classic ids, so the
bulk of the kernel needs no change. The member is renamed from val to
legacy so the compiler finds code that peeks at it directly.
Semantics of the new ids:
- They are not in any uid_map or gid_map: they exist in the initial
user namespace only. kuid_has_mapping() is false for them in every
other namespace, so no capability in a child namespace gives
privilege over them, and from_kuid() and friends give -1 (the
overflow id when munged), exactly as for any other id without a
mapping. Anything that can only store or report a 32 bit id thereby
already treats them safely.
- Idmapped mounts map classic ids; UUIDs and GUIDs pass through them
unchanged, as long as the filesystem itself is in the initial user
namespace.
- Ids are ordered by kind first and value second, so sorted group
lists, bsearch() and range checks between classic ids keep working:
a UUID never falls into a range of classic ids.
- __kuid_val() and __kgid_val() stay the classic value of a classic id
and are -1 for the others; hash tables keyed by ids (user_struct,
ucounts) use the new kuid_hash() instead, and the IMA policy
comparators the new ordering.
The structs grow from 4 to 20 bytes: struct cred by 128 bytes, struct
inode by 32 bytes.
Assisted-by: Claude:claude-opus-5-5
---
fs/mnt_idmapping.c | 14 ++++
include/linux/mnt_idmapping.h | 74 ++++++++++++++-----
include/linux/uidgid.h | 109 ++++++++++++++++++++++------
include/linux/uidgid_types.h | 32 +++++++-
kernel/groups.c | 15 ++--
kernel/ucount.c | 2 +-
kernel/user.c | 2 +-
kernel/user_namespace.c | 10 ++-
mm/oom_kill.c | 2 +-
security/integrity/ima/ima_policy.c | 8 +-
10 files changed, 209 insertions(+), 59 deletions(-)
diff --git a/fs/mnt_idmapping.c b/fs/mnt_idmapping.c
index cb61fbdb5..c36e74a37 100644
--- a/fs/mnt_idmapping.c
+++ b/fs/mnt_idmapping.c
@@ -87,6 +87,10 @@ vfsuid_t make_vfsuid(struct mnt_idmap *idmap,
return VFSUIDT_INIT(kuid);
if (idmap == &invalid_mnt_idmap)
return INVALID_VFSUID;
+ /* idmappings map classic ids only; UUIDs and GUIDs pass unchanged */
+ if (!uid_is_legacy(kuid))
+ return kuid_has_mapping(fs_userns, kuid) ?
+ VFSUIDT_INIT(kuid) : INVALID_VFSUID;
if (initial_idmapping(fs_userns))
uid = __kuid_val(kuid);
else
@@ -126,6 +130,10 @@ vfsgid_t make_vfsgid(struct mnt_idmap *idmap,
return VFSGIDT_INIT(kgid);
if (idmap == &invalid_mnt_idmap)
return INVALID_VFSGID;
+ /* idmappings map classic ids only; UUIDs and GUIDs pass unchanged */
+ if (!gid_is_legacy(kgid))
+ return kgid_has_mapping(fs_userns, kgid) ?
+ VFSGIDT_INIT(kgid) : INVALID_VFSGID;
if (initial_idmapping(fs_userns))
gid = __kgid_val(kgid);
else
@@ -156,6 +164,9 @@ kuid_t from_vfsuid(struct mnt_idmap *idmap,
return AS_KUIDT(vfsuid);
if (idmap == &invalid_mnt_idmap)
return INVALID_UID;
+ if (!vfsuid_is_legacy(vfsuid))
+ return kuid_has_mapping(fs_userns, AS_KUIDT(vfsuid)) ?
+ AS_KUIDT(vfsuid) : INVALID_UID;
uid = map_id_up(&idmap->uid_map, __vfsuid_val(vfsuid));
if (uid == (uid_t)-1)
return INVALID_UID;
@@ -185,6 +196,9 @@ kgid_t from_vfsgid(struct mnt_idmap *idmap,
return AS_KGIDT(vfsgid);
if (idmap == &invalid_mnt_idmap)
return INVALID_GID;
+ if (!vfsgid_is_legacy(vfsgid))
+ return kgid_has_mapping(fs_userns, AS_KGIDT(vfsgid)) ?
+ AS_KGIDT(vfsgid) : INVALID_GID;
gid = map_id_up(&idmap->gid_map, __vfsgid_val(vfsgid));
if (gid == (gid_t)-1)
return INVALID_GID;
diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h
index e71a6070a..40f0f994a 100644
--- a/include/linux/mnt_idmapping.h
+++ b/include/linux/mnt_idmapping.h
@@ -12,33 +12,55 @@ extern struct mnt_idmap nop_mnt_idmap;
extern struct mnt_idmap invalid_mnt_idmap;
extern struct user_namespace init_user_ns;
+/* Like kuid_t and kgid_t: a classic id, a UUID or a GUID */
typedef struct {
- uid_t val;
+ u32 type;
+ union {
+ uid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} vfsuid_t;
typedef struct {
- gid_t val;
+ u32 type;
+ union {
+ gid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} vfsgid_t;
static_assert(sizeof(vfsuid_t) == sizeof(kuid_t));
static_assert(sizeof(vfsgid_t) == sizeof(kgid_t));
-static_assert(offsetof(vfsuid_t, val) == offsetof(kuid_t, val));
-static_assert(offsetof(vfsgid_t, val) == offsetof(kgid_t, val));
+static_assert(offsetof(vfsuid_t, uuid) == offsetof(kuid_t, uuid));
+static_assert(offsetof(vfsgid_t, uuid) == offsetof(kgid_t, uuid));
static inline bool is_valid_mnt_idmap(const struct mnt_idmap *idmap)
{
return idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap;
}
+static inline bool vfsuid_is_legacy(vfsuid_t uid)
+{
+ return uid.type == KID_LEGACY;
+}
+
+static inline bool vfsgid_is_legacy(vfsgid_t gid)
+{
+ return gid.type == KID_LEGACY;
+}
+
+/* The classic value of a legacy id, -1 for a UUID or GUID */
#ifdef CONFIG_MULTIUSER
static inline uid_t __vfsuid_val(vfsuid_t uid)
{
- return uid.val;
+ return vfsuid_is_legacy(uid) ? uid.legacy : (uid_t)-1;
}
static inline gid_t __vfsgid_val(vfsgid_t gid)
{
- return gid.val;
+ return vfsgid_is_legacy(gid) ? gid.legacy : (gid_t)-1;
}
#else
static inline uid_t __vfsuid_val(vfsuid_t uid)
@@ -54,22 +76,34 @@ static inline gid_t __vfsgid_val(vfsgid_t gid)
static inline bool vfsuid_valid(vfsuid_t uid)
{
- return __vfsuid_val(uid) != (uid_t)-1;
+ return !vfsuid_is_legacy(uid) || __vfsuid_val(uid) != (uid_t)-1;
}
static inline bool vfsgid_valid(vfsgid_t gid)
{
- return __vfsgid_val(gid) != (gid_t)-1;
+ return !vfsgid_is_legacy(gid) || __vfsgid_val(gid) != (gid_t)-1;
+}
+
+static inline int vfsuid_cmp(vfsuid_t left, vfsuid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
+}
+
+static inline int vfsgid_cmp(vfsgid_t left, vfsgid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
}
static inline bool vfsuid_eq(vfsuid_t left, vfsuid_t right)
{
- return vfsuid_valid(left) && __vfsuid_val(left) == __vfsuid_val(right);
+ return vfsuid_valid(left) && vfsuid_cmp(left, right) == 0;
}
static inline bool vfsgid_eq(vfsgid_t left, vfsgid_t right)
{
- return vfsgid_valid(left) && __vfsgid_val(left) == __vfsgid_val(right);
+ return vfsgid_valid(left) && vfsgid_cmp(left, right) == 0;
}
/**
@@ -84,7 +118,9 @@ static inline bool vfsgid_eq(vfsgid_t left, vfsgid_t right)
*/
static inline bool vfsuid_eq_kuid(vfsuid_t vfsuid, kuid_t kuid)
{
- return vfsuid_valid(vfsuid) && __vfsuid_val(vfsuid) == __kuid_val(kuid);
+ return vfsuid_valid(vfsuid) &&
+ __kid_cmp(vfsuid.type, vfsuid.legacy, vfsuid.uuid.b,
+ kuid.type, kuid.legacy, kuid.uuid.b) == 0;
}
/**
@@ -99,15 +135,17 @@ static inline bool vfsuid_eq_kuid(vfsuid_t vfsuid, kuid_t kuid)
*/
static inline bool vfsgid_eq_kgid(vfsgid_t vfsgid, kgid_t kgid)
{
- return vfsgid_valid(vfsgid) && __vfsgid_val(vfsgid) == __kgid_val(kgid);
+ return vfsgid_valid(vfsgid) &&
+ __kid_cmp(vfsgid.type, vfsgid.legacy, vfsgid.uuid.b,
+ kgid.type, kgid.legacy, kgid.uuid.b) == 0;
}
/*
* vfs{g,u}ids are created from k{g,u}ids.
* We don't allow them to be created from regular {u,g}id.
*/
-#define VFSUIDT_INIT(val) (vfsuid_t){ __kuid_val(val) }
-#define VFSGIDT_INIT(val) (vfsgid_t){ __kgid_val(val) }
+#define VFSUIDT_INIT(val) (vfsuid_t){ .type = (val).type, .uuid = (val).uuid }
+#define VFSGIDT_INIT(val) (vfsgid_t){ .type = (val).type, .uuid = (val).uuid }
#define INVALID_VFSUID VFSUIDT_INIT(INVALID_UID)
#define INVALID_VFSGID VFSGIDT_INIT(INVALID_GID)
@@ -116,8 +154,8 @@ static inline bool vfsgid_eq_kgid(vfsgid_t vfsgid, kgid_t kgid)
* Allow a vfs{g,u}id to be used as a k{g,u}id where we want to compare
* whether the mapped value is identical to value of a k{g,u}id.
*/
-#define AS_KUIDT(val) (kuid_t){ __vfsuid_val(val) }
-#define AS_KGIDT(val) (kgid_t){ __vfsgid_val(val) }
+#define AS_KUIDT(val) (kuid_t){ .type = (val).type, .uuid = (val).uuid }
+#define AS_KGIDT(val) (kgid_t){ .type = (val).type, .uuid = (val).uuid }
int vfsgid_in_group_p(vfsgid_t vfsgid);
@@ -158,7 +196,7 @@ static inline bool vfsuid_has_fsmapping(struct mnt_idmap *idmap,
static inline bool vfsuid_has_mapping(struct user_namespace *userns,
vfsuid_t vfsuid)
{
- return from_kuid(userns, AS_KUIDT(vfsuid)) != (uid_t)-1;
+ return kuid_has_mapping(userns, AS_KUIDT(vfsuid));
}
/**
@@ -196,7 +234,7 @@ static inline bool vfsgid_has_fsmapping(struct mnt_idmap *idmap,
static inline bool vfsgid_has_mapping(struct user_namespace *userns,
vfsgid_t vfsgid)
{
- return from_kgid(userns, AS_KGIDT(vfsgid)) != (gid_t)-1;
+ return kgid_has_mapping(userns, AS_KGIDT(vfsgid));
}
/**
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index 182c68245..e1adb50d5 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -19,32 +19,37 @@ struct user_namespace;
extern struct user_namespace init_user_ns;
struct uid_gid_map;
-#define KUIDT_INIT(value) (kuid_t){ value }
-#define KGIDT_INIT(value) (kgid_t){ value }
+#define KUIDT_INIT(value) (kuid_t){ .type = KID_LEGACY, .legacy = (value) }
+#define KGIDT_INIT(value) (kgid_t){ .type = KID_LEGACY, .legacy = (value) }
+
+#define KUIDT_INIT_UUID(value) (kuid_t){ .type = KID_UUID, .uuid = (value) }
+#define KGIDT_INIT_UUID(value) (kgid_t){ .type = KID_UUID, .uuid = (value) }
+#define KUIDT_INIT_GUID(value) (kuid_t){ .type = KID_GUID, .guid = (value) }
+#define KGIDT_INIT_GUID(value) (kgid_t){ .type = KID_GUID, .guid = (value) }
-/*
- * Whether an id is a classic 32 bit id. All of them are for now; code that
- * needs one (to store it in 32 bits, for example) checks with these.
- */
static inline bool uid_is_legacy(kuid_t uid)
{
- return true;
+ return uid.type == KID_LEGACY;
}
static inline bool gid_is_legacy(kgid_t gid)
{
- return true;
+ return gid.type == KID_LEGACY;
}
+/*
+ * __kuid_val() and __kgid_val() are the classic value of a legacy id;
+ * a UUID or GUID has none and gives -1, which is never a valid id.
+ */
#ifdef CONFIG_MULTIUSER
static inline uid_t __kuid_val(kuid_t uid)
{
- return uid.val;
+ return uid_is_legacy(uid) ? uid.legacy : (uid_t)-1;
}
static inline gid_t __kgid_val(kgid_t gid)
{
- return gid.val;
+ return gid_is_legacy(gid) ? gid.legacy : (gid_t)-1;
}
#else
static inline uid_t __kuid_val(kuid_t uid)
@@ -64,64 +69,115 @@ static inline gid_t __kgid_val(kgid_t gid)
#define INVALID_UID KUIDT_INIT(-1)
#define INVALID_GID KGIDT_INIT(-1)
+/*
+ * Ids are ordered by kind first (legacy ids before UUIDs before GUIDs),
+ * then by value: numerically for legacy ids, bytewise for the others.
+ * That keeps sorted id arrays and range checks between legacy ids
+ * working: a UUID is never inside a range of legacy ids.
+ */
+static inline int __kid_cmp(u32 ltype, u32 llegacy, const u8 *lb,
+ u32 rtype, u32 rlegacy, const u8 *rb)
+{
+ if (!IS_ENABLED(CONFIG_MULTIUSER))
+ return 0;
+ if (ltype != rtype)
+ return ltype < rtype ? -1 : 1;
+ if (ltype == KID_LEGACY)
+ return llegacy == rlegacy ? 0 : llegacy < rlegacy ? -1 : 1;
+ return __builtin_memcmp(lb, rb, UUID_SIZE);
+}
+
+static inline int uid_cmp(kuid_t left, kuid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
+}
+
+static inline int gid_cmp(kgid_t left, kgid_t right)
+{
+ return __kid_cmp(left.type, left.legacy, left.uuid.b,
+ right.type, right.legacy, right.uuid.b);
+}
+
static inline bool uid_eq(kuid_t left, kuid_t right)
{
- return __kuid_val(left) == __kuid_val(right);
+ return uid_cmp(left, right) == 0;
}
static inline bool gid_eq(kgid_t left, kgid_t right)
{
- return __kgid_val(left) == __kgid_val(right);
+ return gid_cmp(left, right) == 0;
}
static inline bool uid_gt(kuid_t left, kuid_t right)
{
- return __kuid_val(left) > __kuid_val(right);
+ return uid_cmp(left, right) > 0;
}
static inline bool gid_gt(kgid_t left, kgid_t right)
{
- return __kgid_val(left) > __kgid_val(right);
+ return gid_cmp(left, right) > 0;
}
static inline bool uid_gte(kuid_t left, kuid_t right)
{
- return __kuid_val(left) >= __kuid_val(right);
+ return uid_cmp(left, right) >= 0;
}
static inline bool gid_gte(kgid_t left, kgid_t right)
{
- return __kgid_val(left) >= __kgid_val(right);
+ return gid_cmp(left, right) >= 0;
}
static inline bool uid_lt(kuid_t left, kuid_t right)
{
- return __kuid_val(left) < __kuid_val(right);
+ return uid_cmp(left, right) < 0;
}
static inline bool gid_lt(kgid_t left, kgid_t right)
{
- return __kgid_val(left) < __kgid_val(right);
+ return gid_cmp(left, right) < 0;
}
static inline bool uid_lte(kuid_t left, kuid_t right)
{
- return __kuid_val(left) <= __kuid_val(right);
+ return uid_cmp(left, right) <= 0;
}
static inline bool gid_lte(kgid_t left, kgid_t right)
{
- return __kgid_val(left) <= __kgid_val(right);
+ return gid_cmp(left, right) <= 0;
}
static inline bool uid_valid(kuid_t uid)
{
- return __kuid_val(uid) != (uid_t) -1;
+ return !uid_is_legacy(uid) || __kuid_val(uid) != (uid_t) -1;
}
static inline bool gid_valid(kgid_t gid)
{
- return __kgid_val(gid) != (gid_t) -1;
+ return !gid_is_legacy(gid) || __kgid_val(gid) != (gid_t) -1;
+}
+
+/* A hash of an id, for hash tables keyed by kuid_t or kgid_t */
+static inline u32 __kid_hash(u32 type, u32 legacy, const u8 *b)
+{
+ u32 w[4];
+
+ if (type == KID_LEGACY)
+ return legacy;
+ __builtin_memcpy(w, b, sizeof(w));
+ return w[0] ^ w[1] ^ w[2] ^ w[3] ^ type;
+}
+
+static inline u32 kuid_hash(kuid_t uid)
+{
+ return __kid_hash(uid.type, __kuid_val(uid), uid.uuid.b);
+}
+
+static inline u32 kgid_hash(kgid_t gid)
+{
+ return __kid_hash(gid.type, __kgid_val(gid), gid.uuid.b);
}
#ifdef CONFIG_USER_NS
@@ -134,13 +190,22 @@ extern gid_t from_kgid(struct user_namespace *to, kgid_t gid);
extern uid_t from_kuid_munged(struct user_namespace *to, kuid_t uid);
extern gid_t from_kgid_munged(struct user_namespace *to, kgid_t gid);
+/*
+ * UUIDs and GUIDs aren't in any namespace's uid_map or gid_map: they
+ * exist in the initial user namespace only, and nobody has privilege
+ * over them through a capability in another one.
+ */
static inline bool kuid_has_mapping(struct user_namespace *ns, kuid_t uid)
{
+ if (!uid_is_legacy(uid))
+ return ns == &init_user_ns;
return from_kuid(ns, uid) != (uid_t) -1;
}
static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid)
{
+ if (!gid_is_legacy(gid))
+ return ns == &init_user_ns;
return from_kgid(ns, gid) != (gid_t) -1;
}
diff --git a/include/linux/uidgid_types.h b/include/linux/uidgid_types.h
index b35ac4955..0f595d075 100644
--- a/include/linux/uidgid_types.h
+++ b/include/linux/uidgid_types.h
@@ -3,13 +3,41 @@
#define _LINUX_UIDGID_TYPES_H
#include <linux/types.h>
+#include <linux/uuid_types.h>
+/*
+ * Kinds of kuid_t and kgid_t. KID_LEGACY is 0, so zeroed memory still
+ * holds GLOBAL_ROOT_UID and GLOBAL_ROOT_GID as it did when the ids were
+ * plain numbers. Userspace has its own numbering (UID_R_* in
+ * <uapi/linux/uidgid_r.h>), in which zero is "no id".
+ */
+enum kid_type {
+ KID_LEGACY = 0, /* classic 32 bit id in .legacy */
+ KID_UUID, /* RFC 9562 UUID in .uuid */
+ KID_GUID, /* Microsoft GUID in .guid */
+};
+
+/*
+ * Only the member .type selects is meaningful; the bytes of .uuid past
+ * .legacy are undefined for a KID_LEGACY id. Copy the whole struct and
+ * use the helpers of <linux/uidgid.h> to look at it.
+ */
typedef struct {
- uid_t val;
+ u32 type;
+ union {
+ uid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} kuid_t;
typedef struct {
- gid_t val;
+ u32 type;
+ union {
+ gid_t legacy;
+ uuid_t uuid;
+ guid_t guid;
+ };
} kgid_t;
#endif /* _LINUX_UIDGID_TYPES_H */
diff --git a/kernel/groups.c b/kernel/groups.c
index b5e3be6a6..3aa0651d7 100644
--- a/kernel/groups.c
+++ b/kernel/groups.c
@@ -73,18 +73,15 @@ static int groups_from_user(struct group_info *group_info,
return 0;
}
-static int gid_cmp(const void *_a, const void *_b)
+static int groups_cmp(const void *_a, const void *_b)
{
- kgid_t a = *(kgid_t *)_a;
- kgid_t b = *(kgid_t *)_b;
-
- return gid_gt(a, b) - gid_lt(a, b);
+ return gid_cmp(*(kgid_t *)_a, *(kgid_t *)_b);
}
void groups_sort(struct group_info *group_info)
{
sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid),
- gid_cmp, NULL);
+ groups_cmp, NULL);
}
EXPORT_SYMBOL(groups_sort);
@@ -100,9 +97,11 @@ int groups_search(const struct group_info *group_info, kgid_t grp)
right = group_info->ngroups;
while (left < right) {
unsigned int mid = (left+right)/2;
- if (gid_gt(grp, group_info->gid[mid]))
+ int cmp = gid_cmp(grp, group_info->gid[mid]);
+
+ if (cmp > 0)
left = mid + 1;
- else if (gid_lt(grp, group_info->gid[mid]))
+ else if (cmp < 0)
right = mid;
else
return 1;
diff --git a/kernel/ucount.c b/kernel/ucount.c
index d6dc3e859..3cf03ad7c 100644
--- a/kernel/ucount.c
+++ b/kernel/ucount.c
@@ -22,7 +22,7 @@ static struct hlist_nulls_head ucounts_hashtable[UCOUNTS_HASHTABLE_ENTRIES] = {
static DEFINE_SPINLOCK(ucounts_lock);
#define ucounts_hashfn(ns, uid) \
- hash_long((unsigned long)__kuid_val(uid) + (unsigned long)(ns), \
+ hash_long((unsigned long)kuid_hash(uid) + (unsigned long)(ns), \
UCOUNTS_HASHTABLE_BITS)
#define ucounts_hashentry(ns, uid) \
(ucounts_hashtable + ucounts_hashfn(ns, uid))
diff --git a/kernel/user.c b/kernel/user.c
index 7aef4e679..11a226bf8 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -88,7 +88,7 @@ EXPORT_SYMBOL_GPL(init_user_ns);
#define UIDHASH_SZ (1 << UIDHASH_BITS)
#define UIDHASH_MASK (UIDHASH_SZ - 1)
#define __uidhashfn(uid) (((uid >> UIDHASH_BITS) + uid) & UIDHASH_MASK)
-#define uidhashentry(uid) (uidhash_table + __uidhashfn((__kuid_val(uid))))
+#define uidhashentry(uid) (uidhash_table + __uidhashfn(kuid_hash(uid)))
static struct kmem_cache *uid_cachep;
static struct hlist_head uidhash_table[UIDHASH_SZ];
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 0bed462e9..88fc8ba58 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -436,10 +436,13 @@ EXPORT_SYMBOL(make_kuid);
*
* There is always a mapping into the initial user_namespace.
*
- * If @kuid has no mapping in @targ (uid_t)-1 is returned.
+ * If @kuid has no mapping in @targ (uid_t)-1 is returned. A UUID or
+ * GUID has no classic uid in any user namespace.
*/
uid_t from_kuid(struct user_namespace *targ, kuid_t kuid)
{
+ if (!uid_is_legacy(kuid))
+ return (uid_t) -1;
/* Map the uid from a global kernel uid */
return map_id_up(&targ->uid_map, __kuid_val(kuid));
}
@@ -504,10 +507,13 @@ EXPORT_SYMBOL(make_kgid);
*
* There is always a mapping into the initial user_namespace.
*
- * If @kgid has no mapping in @targ (gid_t)-1 is returned.
+ * If @kgid has no mapping in @targ (gid_t)-1 is returned. A UUID or
+ * GUID has no classic gid in any user namespace.
*/
gid_t from_kgid(struct user_namespace *targ, kgid_t kgid)
{
+ if (!gid_is_legacy(kgid))
+ return (gid_t) -1;
/* Map the gid from a global kernel gid */
return map_id_up(&targ->gid_map, __kgid_val(kgid));
}
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index 5f372f6e2..2ce307293 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -771,7 +771,7 @@ static void mark_oom_victim(struct task_struct *tsk)
thaw_process(tsk);
atomic_inc(&oom_victims);
cred = get_task_cred(tsk);
- trace_mark_victim(tsk, cred->uid.val);
+ trace_mark_victim(tsk, __kuid_val(cred->uid));
put_cred(cred);
}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index b1c010e8e..3ea17c6c0 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -79,22 +79,22 @@ struct ima_rule_opt_list {
*/
static inline bool vfsuid_gt_kuid(vfsuid_t vfsuid, kuid_t kuid)
{
- return __vfsuid_val(vfsuid) > __kuid_val(kuid);
+ return vfsuid_cmp(vfsuid, VFSUIDT_INIT(kuid)) > 0;
}
static inline bool vfsgid_gt_kgid(vfsgid_t vfsgid, kgid_t kgid)
{
- return __vfsgid_val(vfsgid) > __kgid_val(kgid);
+ return vfsgid_cmp(vfsgid, VFSGIDT_INIT(kgid)) > 0;
}
static inline bool vfsuid_lt_kuid(vfsuid_t vfsuid, kuid_t kuid)
{
- return __vfsuid_val(vfsuid) < __kuid_val(kuid);
+ return vfsuid_cmp(vfsuid, VFSUIDT_INIT(kuid)) < 0;
}
static inline bool vfsgid_lt_kgid(vfsgid_t vfsgid, kgid_t kgid)
{
- return __vfsgid_val(vfsgid) < __kgid_val(kgid);
+ return vfsgid_cmp(vfsgid, VFSGIDT_INIT(kgid)) < 0;
}
struct ima_rule_entry {
[-- Attachment #11: uid_r-0010-uidgid-convert-between-kernel-ids-and-the-ids-of-the.patch --]
[-- Type: text/x-patch, Size: 12658 bytes --]
From 071a6e5d021ccb4f8480761e148b06ce58114758 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:33 +0200
Subject: [PATCH 10/43] uidgid: convert between kernel ids and the ids of the
*_r system calls
Add the conversions between kuid_t/kgid_t and __kernel_uid_r_t the new
system calls need: make_kuid_r(), from_kuid_r() and the never failing
from_kuid_r_munged() (and the gid variants), and get_kuid_r() and
put_kuid_r() to copy them from and to userspace.
get_kuid_r() returns INVALID_UID for "no id" -- a NULL pointer,
UID_R_NONE, or a classic -1 -- which the set*id_r() and chown_r() calls
take as "unchanged", like the classic calls take -1. It rejects
malformed ids: unknown types, nonzero reserved bytes, UUIDs other than
RFC 9562 version 4, the nil GUID, and UUIDs or GUIDs given from outside
the initial user namespace.
Also add kuid_str() and kgid_str(), the text form of an id for logs and
procfs: a classic id in decimal, a UUID as usual, a GUID in braces.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/uidgid.h | 35 +++++
kernel/Makefile | 1 +
kernel/uidgid_r.c | 323 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 359 insertions(+)
create mode 100644 kernel/uidgid_r.c
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index e1adb50d5..ac7786f76 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -180,6 +180,17 @@ static inline u32 kgid_hash(kgid_t gid)
return __kid_hash(gid.type, __kgid_val(gid), gid.uuid.b);
}
+/*
+ * The text form of an id, for logs and procfs: a legacy id as a decimal
+ * number, a UUID as "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" and a GUID in
+ * braces, "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}". KID_STR_LEN includes
+ * the terminating NUL.
+ */
+#define KID_STR_LEN 39
+
+char *kuid_str(kuid_t uid, char buf[KID_STR_LEN]);
+char *kgid_str(kgid_t gid, char buf[KID_STR_LEN]);
+
#ifdef CONFIG_USER_NS
extern kuid_t make_kuid(struct user_namespace *from, uid_t uid);
@@ -277,4 +288,28 @@ static inline u32 map_id_up(struct uid_gid_map *map, u32 id)
}
#endif /* CONFIG_USER_NS */
+/*
+ * Conversion between kuid_t/kgid_t and the ids of the *_r system calls
+ * (<uapi/linux/uidgid_r.h>), in kernel/uidgid_r.c.
+ */
+struct __kernel_uid_r;
+
+kuid_t make_kuid_r(struct user_namespace *from, const struct __kernel_uid_r *id);
+kgid_t make_kgid_r(struct user_namespace *from, const struct __kernel_uid_r *id);
+bool from_kuid_r(struct user_namespace *to, kuid_t uid, struct __kernel_uid_r *id);
+bool from_kgid_r(struct user_namespace *to, kgid_t gid, struct __kernel_uid_r *id);
+void from_kuid_r_munged(struct user_namespace *to, kuid_t uid,
+ struct __kernel_uid_r *id);
+void from_kgid_r_munged(struct user_namespace *to, kgid_t gid,
+ struct __kernel_uid_r *id);
+
+int get_kuid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *uid, kuid_t *kuid);
+int get_kgid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *gid, kgid_t *kgid);
+int put_kuid_r(struct user_namespace *to, kuid_t kuid,
+ struct __kernel_uid_r __user *uid);
+int put_kgid_r(struct user_namespace *to, kgid_t kgid,
+ struct __kernel_uid_r __user *gid);
+
#endif /* _LINUX_UIDGID_H */
diff --git a/kernel/Makefile b/kernel/Makefile
index 1e1a31673..813ddbf11 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_BACKTRACE_SELF_TEST) += backtracetest.o
obj-$(CONFIG_COMPAT) += compat.o
obj-$(CONFIG_CGROUPS) += cgroup/
obj-$(CONFIG_UTS_NS) += utsname.o
+obj-y += uidgid_r.o
obj-$(CONFIG_USER_NS) += user_namespace.o
obj-$(CONFIG_PID_NS) += pid_namespace.o
obj-$(CONFIG_IKCONFIG) += configs.o
diff --git a/kernel/uidgid_r.c b/kernel/uidgid_r.c
new file mode 100644
index 000000000..d71aecddb
--- /dev/null
+++ b/kernel/uidgid_r.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Conversion between kuid_t/kgid_t and the ids of the *_r system calls
+ * (<uapi/linux/uidgid_r.h>).
+ */
+
+#include <linux/export.h>
+#include <linux/highuid.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+#include <linux/uidgid_r.h>
+#include <linux/user_namespace.h>
+
+/*
+ * Whether @id is well formed: a known type, reserved bytes zero, a UUID of
+ * version 4 with the RFC 9562 variant, a GUID other than the nil GUID.
+ */
+static bool kid_r_valid(const struct __kernel_uid_r *id)
+{
+ switch (id->type) {
+ case UID_R_LEGACY:
+ return !memchr_inv(id->uuid + sizeof(id->legacy), 0,
+ UID_R_SIZE - sizeof(id->legacy));
+ case UID_R_UUID:
+ return (id->uuid[6] >> 4) == 4 && (id->uuid[8] & 0xc0) == 0x80;
+ case UID_R_GUID:
+ return memchr_inv(id->guid, 0, UID_R_SIZE);
+ }
+ return false;
+}
+
+/*
+ * The kind and 128 bit value of @id for a kuid_t or kgid_t; false if it is
+ * no UUID or GUID or has no mapping in @ns.
+ */
+static bool kid_r_to_128(struct user_namespace *ns,
+ const struct __kernel_uid_r *id, u32 *type, uuid_t *b)
+{
+ if (!kid_r_valid(id) || ns != &init_user_ns)
+ return false;
+ if (id->type == UID_R_UUID)
+ *type = KID_UUID;
+ else if (id->type == UID_R_GUID)
+ *type = KID_GUID;
+ else
+ return false;
+ memcpy(b->b, id->uuid, UID_R_SIZE);
+ return true;
+}
+
+static bool kid_128_to_r(struct user_namespace *ns, u32 type, const uuid_t *b,
+ struct __kernel_uid_r *id)
+{
+ if (ns != &init_user_ns)
+ return false;
+ id->type = type == KID_UUID ? UID_R_UUID : UID_R_GUID;
+ memcpy(id->uuid, b->b, UID_R_SIZE);
+ return true;
+}
+
+/**
+ * make_kuid_r - map a *_r system call uid into a kuid
+ * @from: user namespace the uid is in
+ * @id: the uid
+ *
+ * Return: the kuid, INVALID_UID if @id is malformed, UID_R_NONE or has no
+ * mapping in @from.
+ */
+kuid_t make_kuid_r(struct user_namespace *from, const struct __kernel_uid_r *id)
+{
+ kuid_t uid = INVALID_UID;
+
+ if (id->type == UID_R_LEGACY && kid_r_valid(id))
+ return make_kuid(from, id->legacy);
+ if (!kid_r_to_128(from, id, &uid.type, &uid.uuid))
+ return INVALID_UID;
+ return uid;
+}
+EXPORT_SYMBOL(make_kuid_r);
+
+/**
+ * make_kgid_r - map a *_r system call gid into a kgid
+ * @from: user namespace the gid is in
+ * @id: the gid
+ *
+ * Return: the kgid, INVALID_GID if @id is malformed, UID_R_NONE or has no
+ * mapping in @from.
+ */
+kgid_t make_kgid_r(struct user_namespace *from, const struct __kernel_uid_r *id)
+{
+ kgid_t gid = INVALID_GID;
+
+ if (id->type == UID_R_LEGACY && kid_r_valid(id))
+ return make_kgid(from, id->legacy);
+ if (!kid_r_to_128(from, id, &gid.type, &gid.uuid))
+ return INVALID_GID;
+ return gid;
+}
+EXPORT_SYMBOL(make_kgid_r);
+
+/**
+ * from_kuid_r - map a kuid into a *_r system call uid
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @id: the uid, all zero (UID_R_NONE) if there is no mapping
+ *
+ * Return: whether @uid has a mapping in @to.
+ */
+bool from_kuid_r(struct user_namespace *to, kuid_t uid, struct __kernel_uid_r *id)
+{
+ memset(id, 0, sizeof(*id));
+ if (uid_is_legacy(uid)) {
+ uid_t v = from_kuid(to, uid);
+
+ if (v == (uid_t)-1)
+ return false;
+ id->type = UID_R_LEGACY;
+ id->legacy = v;
+ return true;
+ }
+ return kid_128_to_r(to, uid.type, &uid.uuid, id);
+}
+EXPORT_SYMBOL(from_kuid_r);
+
+/**
+ * from_kgid_r - map a kgid into a *_r system call gid
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @id: the gid, all zero (UID_R_NONE) if there is no mapping
+ *
+ * Return: whether @gid has a mapping in @to.
+ */
+bool from_kgid_r(struct user_namespace *to, kgid_t gid, struct __kernel_uid_r *id)
+{
+ memset(id, 0, sizeof(*id));
+ if (gid_is_legacy(gid)) {
+ gid_t v = from_kgid(to, gid);
+
+ if (v == (gid_t)-1)
+ return false;
+ id->type = UID_R_LEGACY;
+ id->legacy = v;
+ return true;
+ }
+ return kid_128_to_r(to, gid.type, &gid.uuid, id);
+}
+EXPORT_SYMBOL(from_kgid_r);
+
+/**
+ * from_kuid_r_munged - map a kuid into a *_r system call uid, never failing
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @id: the uid, overflowuid if @uid has no mapping in @to
+ */
+void from_kuid_r_munged(struct user_namespace *to, kuid_t uid,
+ struct __kernel_uid_r *id)
+{
+ if (!from_kuid_r(to, uid, id)) {
+ id->type = UID_R_LEGACY;
+ id->legacy = overflowuid;
+ }
+}
+EXPORT_SYMBOL(from_kuid_r_munged);
+
+/**
+ * from_kgid_r_munged - map a kgid into a *_r system call gid, never failing
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @id: the gid, overflowgid if @gid has no mapping in @to
+ */
+void from_kgid_r_munged(struct user_namespace *to, kgid_t gid,
+ struct __kernel_uid_r *id)
+{
+ if (!from_kgid_r(to, gid, id)) {
+ id->type = UID_R_LEGACY;
+ id->legacy = overflowgid;
+ }
+}
+EXPORT_SYMBOL(from_kgid_r_munged);
+
+/*
+ * "No id" from userspace: no pointer, UID_R_NONE, or the classic -1. The
+ * set*id_r() and chown_r() calls take it as "leave unchanged".
+ */
+static bool kid_r_is_none(const struct __kernel_uid_r *id)
+{
+ return id->type == UID_R_NONE ||
+ (id->type == UID_R_LEGACY && id->legacy == (u32)-1);
+}
+
+/**
+ * get_kuid_r - read a uid of a *_r system call from userspace
+ * @from: user namespace the uid is in
+ * @uid: the uid, or NULL
+ * @kuid: the kuid, INVALID_UID for "no id" (NULL, UID_R_NONE or -1)
+ *
+ * Return: 0, -EFAULT, or -EINVAL if the uid is malformed or has no mapping
+ * in @from.
+ */
+int get_kuid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *uid, kuid_t *kuid)
+{
+ struct __kernel_uid_r id;
+
+ *kuid = INVALID_UID;
+ if (!uid)
+ return 0;
+ if (copy_from_user(&id, uid, sizeof(id)))
+ return -EFAULT;
+ if (kid_r_is_none(&id))
+ return 0;
+ *kuid = make_kuid_r(from, &id);
+ return uid_valid(*kuid) ? 0 : -EINVAL;
+}
+EXPORT_SYMBOL(get_kuid_r);
+
+/**
+ * get_kgid_r - read a gid of a *_r system call from userspace
+ * @from: user namespace the gid is in
+ * @gid: the gid, or NULL
+ * @kgid: the kgid, INVALID_GID for "no id" (NULL, UID_R_NONE or -1)
+ *
+ * Return: 0, -EFAULT, or -EINVAL if the gid is malformed or has no mapping
+ * in @from.
+ */
+int get_kgid_r(struct user_namespace *from,
+ const struct __kernel_uid_r __user *gid, kgid_t *kgid)
+{
+ struct __kernel_uid_r id;
+
+ *kgid = INVALID_GID;
+ if (!gid)
+ return 0;
+ if (copy_from_user(&id, gid, sizeof(id)))
+ return -EFAULT;
+ if (kid_r_is_none(&id))
+ return 0;
+ *kgid = make_kgid_r(from, &id);
+ return gid_valid(*kgid) ? 0 : -EINVAL;
+}
+EXPORT_SYMBOL(get_kgid_r);
+
+/**
+ * put_kuid_r - write a kuid to userspace as a *_r system call uid
+ * @to: user namespace the uid is wanted in
+ * @kuid: the kuid
+ * @uid: where to put it, overflowuid if @kuid has no mapping in @to
+ *
+ * Return: 0 or -EFAULT.
+ */
+int put_kuid_r(struct user_namespace *to, kuid_t kuid,
+ struct __kernel_uid_r __user *uid)
+{
+ struct __kernel_uid_r id;
+
+ from_kuid_r_munged(to, kuid, &id);
+ return copy_to_user(uid, &id, sizeof(id)) ? -EFAULT : 0;
+}
+EXPORT_SYMBOL(put_kuid_r);
+
+/**
+ * put_kgid_r - write a kgid to userspace as a *_r system call gid
+ * @to: user namespace the gid is wanted in
+ * @kgid: the kgid
+ * @gid: where to put it, overflowgid if @kgid has no mapping in @to
+ *
+ * Return: 0 or -EFAULT.
+ */
+int put_kgid_r(struct user_namespace *to, kgid_t kgid,
+ struct __kernel_uid_r __user *gid)
+{
+ struct __kernel_uid_r id;
+
+ from_kgid_r_munged(to, kgid, &id);
+ return copy_to_user(gid, &id, sizeof(id)) ? -EFAULT : 0;
+}
+EXPORT_SYMBOL(put_kgid_r);
+
+static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
+{
+ switch (type) {
+ case KID_LEGACY:
+ snprintf(buf, KID_STR_LEN, "%u", legacy);
+ break;
+ case KID_UUID:
+ snprintf(buf, KID_STR_LEN, "%pUb", b);
+ break;
+ default:
+ snprintf(buf, KID_STR_LEN, "{%pUl}", b);
+ break;
+ }
+ return buf;
+}
+
+/**
+ * kuid_str - text form of a kuid, for logs and procfs
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding @uid in the initial user namespace: a decimal
+ * number, a UUID, or a GUID in braces.
+ */
+char *kuid_str(kuid_t uid, char buf[KID_STR_LEN])
+{
+ return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf);
+}
+EXPORT_SYMBOL(kuid_str);
+
+/**
+ * kgid_str - text form of a kgid, for logs and procfs
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding @gid in the initial user namespace: a decimal
+ * number, a UUID, or a GUID in braces.
+ */
+char *kgid_str(kgid_t gid, char buf[KID_STR_LEN])
+{
+ return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf);
+}
+EXPORT_SYMBOL(kgid_str);
[-- Attachment #12: uid_r-0011-fscrypt-show-the-whole-uid-in-the-description-of-use.patch --]
[-- Type: text/x-patch, Size: 1841 bytes --]
From ff405749f415e682fa1ce99c7dcba8795fe35997 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:20:33 +0200
Subject: [PATCH 11/43] fscrypt: show the whole uid in the description of user
key claims
The ".fscrypt" keys that account a master key to a user are described
as "<identifier>.uid.<uid>". For a UUID or GUID uid that printed -1;
use kuid_str() so /proc/keys shows who the claim belongs to. Lookups
compare the kuid itself and are not affected.
Assisted-by: Claude:claude-opus-5-5
---
fs/crypto/keyring.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index 38b73e703..1b9e80223 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -178,7 +178,7 @@ static struct key_type key_type_fscrypt_user = {
};
#define FSCRYPT_MK_USER_DESCRIPTION_SIZE \
- (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + CONST_STRLEN(".uid.") + 10 + 1)
+ (2 * FSCRYPT_KEY_IDENTIFIER_SIZE + CONST_STRLEN(".uid.") + KID_STR_LEN)
/* Create ->s_master_keys if needed. Synchronized by fscrypt_add_key_mutex. */
static int allocate_filesystem_keyring(struct super_block *sb)
@@ -338,13 +338,14 @@ static int add_master_key_user(struct fscrypt_master_key *mk)
{
kuid_t uid = current_fsuid();
char description[FSCRYPT_MK_USER_DESCRIPTION_SIZE];
+ char uidbuf[KID_STR_LEN];
struct key *quota_key;
struct fscrypt_master_key_user *mk_user;
int err;
- snprintf(description, sizeof(description), "%*phN.uid.%u",
+ snprintf(description, sizeof(description), "%*phN.uid.%s",
FSCRYPT_KEY_IDENTIFIER_SIZE, mk->mk_spec.u.identifier,
- __kuid_val(uid));
+ kuid_str(uid, uidbuf));
quota_key = key_alloc(&key_type_fscrypt_user, description, uid,
current_gid(), current_cred(),
KEY_POS_SEARCH | KEY_USR_VIEW, 0, NULL);
[-- Attachment #13: uid_r-0012-keys-name-per-user-keyrings-of-UUID-and-GUID-users-b.patch --]
[-- Type: text/x-patch, Size: 12396 bytes --]
From f6833729017d969eeb706c8d025ceecfcbd109e2 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:01:52 +0200
Subject: [PATCH 12/43] keys: name per-user keyrings of UUID and GUID users by
the whole uid
The user keyring, the user session keyring and the persistent keyring
are found by name: "_uid.<uid>", "_uid_ses.<uid>" and
"_persistent.<uid>", the uid in the caller's user namespace. A UUID or
GUID uid has none and comes out as -1, so all such users would find
and share the same keyrings -- each other's keys.
Name their keyrings after the text form of the uid (kuid_str()), which
is unique; classic uids keep their names. Hand request-key the text
form as well instead of -1.
Also add KEYCTL_CHOWN_R, KEYCTL_CHOWN with __kernel_uid_r_t ids, so keys
can be given to UUID and GUID users and groups.
Assisted-by: Claude:claude-opus-5-5
---
include/uapi/linux/keyctl.h | 1 +
security/keys/compat.c | 3 ++
security/keys/internal.h | 3 ++
security/keys/keyctl.c | 61 ++++++++++++++++++++++++++----------
security/keys/persistent.c | 11 +++++--
security/keys/process_keys.c | 36 +++++++++++++++------
security/keys/request_key.c | 12 +++++--
7 files changed, 97 insertions(+), 30 deletions(-)
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index 4c8884eea..73ee0cb7b 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -70,6 +70,7 @@
#define KEYCTL_MOVE 30 /* Move keys between keyrings */
#define KEYCTL_CAPABILITIES 31 /* Find capabilities of keyrings subsystem */
#define KEYCTL_WATCH_KEY 32 /* Watch a key or ring of keys for changes */
+#define KEYCTL_CHOWN_R 33 /* set ownership of a key, __kernel_uid_r_t ids */
/* keyctl structures */
struct keyctl_dh_params {
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 1545efdca..c7acf7db5 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -52,6 +52,9 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_CHOWN:
return keyctl_chown_key(arg2, arg3, arg4);
+ case KEYCTL_CHOWN_R:
+ return keyctl_chown_key_r(arg2, compat_ptr(arg3), compat_ptr(arg4));
+
case KEYCTL_SETPERM:
return keyctl_setperm_key(arg2, arg3);
diff --git a/security/keys/internal.h b/security/keys/internal.h
index b7b622bc3..63874fa53 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -249,6 +249,9 @@ extern long keyctl_keyring_search(key_serial_t, const char __user *,
const char __user *, key_serial_t);
extern long keyctl_read_key(key_serial_t, char __user *, size_t);
extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
+extern long keyctl_chown_key_r(key_serial_t id,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group);
extern long keyctl_setperm_key(key_serial_t, key_perm_t);
extern long keyctl_instantiate_key(key_serial_t, const void __user *,
size_t, key_serial_t);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index d14ace88e..984c9c54a 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -946,26 +946,17 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen)
*
* If successful, 0 will be returned.
*/
-long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
+static long keyctl_chown_kid(key_serial_t id, kuid_t uid, kgid_t gid)
{
struct key_user *newowner, *zapowner = NULL;
struct key *key;
key_ref_t key_ref;
long ret;
- kuid_t uid;
- kgid_t gid;
unsigned long flags;
- uid = make_kuid(current_user_ns(), user);
- gid = make_kgid(current_user_ns(), group);
- ret = -EINVAL;
- if ((user != (uid_t) -1) && !uid_valid(uid))
- goto error;
- if ((group != (gid_t) -1) && !gid_valid(gid))
- goto error;
-
+ /* invalid ids leave the owner or group unchanged */
ret = 0;
- if (user == (uid_t) -1 && group == (gid_t) -1)
+ if (!uid_valid(uid) && !gid_valid(gid))
goto error;
key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL,
@@ -985,12 +976,12 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
bool is_privileged_op = false;
/* only the sysadmin can chown a key to some other UID */
- if (user != (uid_t) -1 && !uid_eq(key->uid, uid))
+ if (uid_valid(uid) && !uid_eq(key->uid, uid))
is_privileged_op = true;
/* only the sysadmin can set the key's GID to a group other
* than one of those that the current process subscribes to */
- if (group != (gid_t) -1 && !gid_eq(gid, key->gid) && !in_group_p(gid))
+ if (gid_valid(gid) && !gid_eq(gid, key->gid) && !in_group_p(gid))
is_privileged_op = true;
if (is_privileged_op && !capable(CAP_SYS_ADMIN))
@@ -998,7 +989,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
}
/* change the UID */
- if (user != (uid_t) -1 && !uid_eq(uid, key->uid)) {
+ if (uid_valid(uid) && !uid_eq(uid, key->uid)) {
ret = -ENOMEM;
newowner = key_user_lookup(uid);
if (!newowner)
@@ -1042,7 +1033,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
}
/* change the GID */
- if (group != (gid_t) -1)
+ if (gid_valid(gid))
key->gid = gid;
notify_key(key, NOTIFY_KEY_SETATTR, 0);
@@ -1063,6 +1054,39 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
goto error_put;
}
+long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
+{
+ kuid_t uid = make_kuid(current_user_ns(), user);
+ kgid_t gid = make_kgid(current_user_ns(), group);
+
+ if ((user != (uid_t) -1) && !uid_valid(uid))
+ return -EINVAL;
+ if ((group != (gid_t) -1) && !gid_valid(gid))
+ return -EINVAL;
+ return keyctl_chown_kid(id, uid, gid);
+}
+
+/*
+ * KEYCTL_CHOWN with __kernel_uid_r_t ids, which can be UUIDs and GUIDs. A
+ * NULL pointer, UID_R_NONE or a classic -1 leave the owner or group
+ * unchanged.
+ */
+long keyctl_chown_key_r(key_serial_t id,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group)
+{
+ kuid_t uid;
+ kgid_t gid;
+ long ret;
+
+ ret = get_kuid_r(current_user_ns(), user, &uid);
+ if (!ret)
+ ret = get_kgid_r(current_user_ns(), group, &gid);
+ if (ret)
+ return ret;
+ return keyctl_chown_kid(id, uid, gid);
+}
+
/*
* Change the permission mask on a key.
*
@@ -1934,6 +1958,11 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(uid_t) arg3,
(gid_t) arg4);
+ case KEYCTL_CHOWN_R:
+ return keyctl_chown_key_r((key_serial_t) arg2,
+ (const struct __kernel_uid_r __user *) arg3,
+ (const struct __kernel_uid_r __user *) arg4);
+
case KEYCTL_SETPERM:
return keyctl_setperm_key((key_serial_t) arg2,
(key_perm_t) arg3);
diff --git a/security/keys/persistent.c b/security/keys/persistent.c
index bdab99539..2c4b32cd2 100644
--- a/security/keys/persistent.c
+++ b/security/keys/persistent.c
@@ -83,7 +83,8 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
struct keyring_index_key index_key;
struct key *persistent;
key_ref_t reg_ref, persistent_ref;
- char buf[32];
+ char buf[sizeof("_persistent.") + KID_STR_LEN];
+ char idbuf[KID_STR_LEN];
long ret;
bool created = false;
@@ -91,7 +92,13 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
memset(&index_key, 0, sizeof(index_key));
index_key.type = &key_type_keyring;
index_key.description = buf;
- index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
+ /* a UUID or GUID has no uid in @ns: name it by its text form */
+ if (uid_is_legacy(uid))
+ index_key.desc_len = sprintf(buf, "_persistent.%u",
+ from_kuid(ns, uid));
+ else
+ index_key.desc_len = sprintf(buf, "_persistent.%s",
+ kuid_str(uid, idbuf));
key_set_index_key(&index_key);
if (ns->persistent_keyring_register) {
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index a63c46bb2..6f275899f 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -67,6 +67,25 @@ static struct key *get_user_register(struct user_namespace *user_ns)
return reg_keyring;
}
+/*
+ * The name of a user's keyring or user session keyring: @prefix and the uid
+ * in @user_ns, or for a UUID or GUID uid its text form. Those have no uid
+ * in any namespace, and as -1 all of them would share the keyrings.
+ */
+#define USER_KEYRING_NAME_SIZE (sizeof("_uid_ses.") + KID_STR_LEN)
+
+static int user_keyring_name(char *buf, const char *prefix,
+ struct user_namespace *user_ns, kuid_t kuid)
+{
+ char idbuf[KID_STR_LEN];
+
+ if (!uid_is_legacy(kuid))
+ return snprintf(buf, USER_KEYRING_NAME_SIZE, "%s%s", prefix,
+ kuid_str(kuid, idbuf));
+ return snprintf(buf, USER_KEYRING_NAME_SIZE, "%s%u", prefix,
+ from_kuid(user_ns, kuid));
+}
+
/*
* Look up the user and user session keyrings for the current process's UID,
* creating them if they don't exist.
@@ -79,13 +98,12 @@ int look_up_user_keyrings(struct key **_user_keyring,
struct key *reg_keyring, *uid_keyring, *session_keyring;
key_perm_t user_keyring_perm;
key_ref_t uid_keyring_r, session_keyring_r;
- uid_t uid = from_kuid(user_ns, cred->user->uid);
- char buf[20];
+ char buf[USER_KEYRING_NAME_SIZE];
int ret;
user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
- kenter("%u", uid);
+ kenter("%u", from_kuid(user_ns, cred->user->uid));
reg_keyring = get_user_register(user_ns);
if (IS_ERR(reg_keyring))
@@ -98,7 +116,7 @@ int look_up_user_keyrings(struct key **_user_keyring,
* already as it may have been pinned by a session, but the user_struct
* pointing to it may have been destroyed by setuid.
*/
- snprintf(buf, sizeof(buf), "_uid.%u", uid);
+ user_keyring_name(buf, "_uid.", user_ns, cred->user->uid);
uid_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
&key_type_keyring, buf, false);
kdebug("_uid %p", uid_keyring_r);
@@ -120,7 +138,7 @@ int look_up_user_keyrings(struct key **_user_keyring,
}
/* Get a default session keyring (which might also exist already) */
- snprintf(buf, sizeof(buf), "_uid_ses.%u", uid);
+ user_keyring_name(buf, "_uid_ses.", user_ns, cred->user->uid);
session_keyring_r = keyring_search(make_key_ref(reg_keyring, true),
&key_type_keyring, buf, false);
kdebug("_uid_ses %p", session_keyring_r);
@@ -186,7 +204,7 @@ struct key *get_user_session_keyring_rcu(const struct cred *cred)
{
struct key *reg_keyring = READ_ONCE(cred->user_ns->user_keyring_register);
key_ref_t session_keyring_r;
- char buf[20];
+ char buf[USER_KEYRING_NAME_SIZE];
struct keyring_search_context ctx = {
.index_key.type = &key_type_keyring,
@@ -201,9 +219,9 @@ struct key *get_user_session_keyring_rcu(const struct cred *cred)
if (!reg_keyring)
return NULL;
- ctx.index_key.desc_len = snprintf(buf, sizeof(buf), "_uid_ses.%u",
- from_kuid(cred->user_ns,
- cred->user->uid));
+ ctx.index_key.desc_len = user_keyring_name(buf, "_uid_ses.",
+ cred->user_ns,
+ cred->user->uid);
session_keyring_r = keyring_search_rcu(make_key_ref(reg_keyring, true),
&ctx);
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index fa2bb9f2f..48067bb73 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -122,7 +122,7 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
const struct cred *cred = current_cred();
key_serial_t prkey, sskey;
struct key *key = rka->target_key, *keyring, *session, *user_session;
- char *argv[9], *envp[3], uid_str[12], gid_str[12];
+ char *argv[9], *envp[3], uid_str[KID_STR_LEN], gid_str[KID_STR_LEN];
char key_str[12], keyring_str[3][12];
char desc[20];
int ret, i;
@@ -152,8 +152,14 @@ static int call_sbin_request_key(struct key *authkey, void *aux)
goto error_link;
/* record the UID and GID */
- sprintf(uid_str, "%d", from_kuid(&init_user_ns, cred->fsuid));
- sprintf(gid_str, "%d", from_kgid(&init_user_ns, cred->fsgid));
+ if (uid_is_legacy(cred->fsuid))
+ sprintf(uid_str, "%d", from_kuid(&init_user_ns, cred->fsuid));
+ else
+ kuid_str(cred->fsuid, uid_str);
+ if (gid_is_legacy(cred->fsgid))
+ sprintf(gid_str, "%d", from_kgid(&init_user_ns, cred->fsgid));
+ else
+ kgid_str(cred->fsgid, gid_str);
/* we say which key is under construction */
sprintf(key_str, "%d", key->serial);
[-- Attachment #14: uid_r-0013-ublk-refuse-unprivileged-devices-to-UUID-and-GUID-us.patch --]
[-- Type: text/x-patch, Size: 2569 bytes --]
From 245f472248d9b190f4f071cf273b3b99513f0188 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:44:28 +0200
Subject: [PATCH 13/43] ublk: refuse unprivileged devices to UUID and GUID
users
The owner of an unprivileged ublk device is recorded in 32 bit fields of
struct ublksrv_ctrl_dev_info, and only the owner may open the disk. A
UUID or GUID user comes out as -1 there, like every other one, so they
could open each other's devices.
Fail the creation of unprivileged devices by such users with
-EOVERFLOW, and never let such a user pass the owner check.
Assisted-by: Claude:claude-opus-5-5
---
drivers/block/ublk_drv.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index d879e8492..6db109433 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -1313,7 +1313,12 @@ static void ublk_free_disk(struct gendisk *disk)
ublk_put_device(ub);
}
-static void ublk_store_owner_uid_gid(unsigned int *owner_uid,
+/*
+ * The owner of an unprivileged device is kept in 32 bits: -EOVERFLOW for
+ * a UUID or GUID user or group, which all would come out as -1 and so
+ * pass for each other.
+ */
+static int ublk_store_owner_uid_gid(unsigned int *owner_uid,
unsigned int *owner_gid)
{
kuid_t uid;
@@ -1323,6 +1328,7 @@ static void ublk_store_owner_uid_gid(unsigned int *owner_uid,
*owner_uid = from_kuid(&init_user_ns, uid);
*owner_gid = from_kgid(&init_user_ns, gid);
+ return uid_is_legacy(uid) && gid_is_legacy(gid) ? 0 : -EOVERFLOW;
}
static int ublk_open(struct gendisk *disk, blk_mode_t mode)
@@ -1344,9 +1350,8 @@ static int ublk_open(struct gendisk *disk, blk_mode_t mode)
if (ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV) {
unsigned int curr_uid, curr_gid;
- ublk_store_owner_uid_gid(&curr_uid, &curr_gid);
-
- if (curr_uid != ub->dev_info.owner_uid || curr_gid !=
+ if (ublk_store_owner_uid_gid(&curr_uid, &curr_gid) ||
+ curr_uid != ub->dev_info.owner_uid || curr_gid !=
ub->dev_info.owner_gid)
return -EPERM;
}
@@ -4740,7 +4745,9 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header)
return -EINVAL;
/* the created device is always owned by current user */
- ublk_store_owner_uid_gid(&info.owner_uid, &info.owner_gid);
+ if (ublk_store_owner_uid_gid(&info.owner_uid, &info.owner_gid) &&
+ (info.flags & UBLK_F_UNPRIVILEGED_DEV))
+ return -EOVERFLOW;
if (header->dev_id != info.dev_id) {
pr_warn("%s: dev id not match %u %u\n",
[-- Attachment #15: uid_r-0014-nfsd-don-t-hand-out-uid-0-for-read-layouts-of-UUID-o.patch --]
[-- Type: text/x-patch, Size: 1269 bytes --]
From 4bed0c3c6d7f1a88aa2245e56540bff41546879e Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:44:28 +0200
Subject: [PATCH 14/43] nfsd: don't hand out uid 0 for read layouts of
UUID-owned files
Read-only flexfile layout segments use the owner's uid plus one, so the
client gets no write access on the data server. For a UUID or GUID
owner from_kuid() is -1 and the "plus one" makes it 0, root. Use the
overflow uid for them instead.
Assisted-by: Claude:claude-opus-5-5
---
fs/nfsd/flexfilelayout.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/nfsd/flexfilelayout.c b/fs/nfsd/flexfilelayout.c
index 6d531285a..f459d2c1c 100644
--- a/fs/nfsd/flexfilelayout.c
+++ b/fs/nfsd/flexfilelayout.c
@@ -51,7 +51,11 @@ nfsd4_ff_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode,
/* Do not allow a IOMODE_READ segment to have write pemissions */
if (seg->iomode == IOMODE_READ) {
- u = from_kuid(&init_user_ns, inode->i_uid) + 1;
+ /* any uid but the owner's; a UUID or GUID has no "+ 1" */
+ if (uid_is_legacy(inode->i_uid))
+ u = from_kuid(&init_user_ns, inode->i_uid) + 1;
+ else
+ u = overflowuid;
fl->uid = make_kuid(&init_user_ns, u);
} else
fl->uid = inode->i_uid;
[-- Attachment #16: uid_r-0015-SUNRPC-name-UUID-and-GUID-users-to-gssd-as-uid_r.patch --]
[-- Type: text/x-patch, Size: 3724 bytes --]
From 4f4ba81d63301a105956deac69ebb4f20915fe9c Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:44:28 +0200
Subject: [PATCH 15/43] SUNRPC: name UUID and GUID users to gssd as uid_r=
gssd learns the user of an upcall as a 32 bit uid. UUID and GUID users
would all reach it as the overflow uid and share its credentials.
In the text (v1) upcall, name such a user as uid_r= with the text form
of the id instead of uid=. gssd versions that don't know uid_r= find no
uid= and refuse the upcall, so nobody ends up with the overflow uid's
credentials; where gssd's user namespace can't see the user, the upcall
names no user at all and fails the same way.
A downcall for such a user starts with uid -1, which is never valid,
followed by the user as __kernel_uid_r_t; downcalls for classic uids are
unchanged.
The binary (v0) upcall has room for a 32 bit uid only: fail it with
-EACCES for UUID and GUID users.
Assisted-by: Claude:claude-opus-5-5
---
net/sunrpc/auth_gss/auth_gss.c | 44 +++++++++++++++++++++++++++++++---
1 file changed, 41 insertions(+), 3 deletions(-)
diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c
index 8ddc65e89..5e9e2cf4e 100644
--- a/net/sunrpc/auth_gss/auth_gss.c
+++ b/net/sunrpc/auth_gss/auth_gss.c
@@ -18,6 +18,7 @@
#include <linux/sched.h>
#include <linux/pagemap.h>
#include <linux/sunrpc/clnt.h>
+#include <linux/uidgid_r.h>
#include <linux/sunrpc/auth.h>
#include <linux/sunrpc/auth_gss.h>
#include <linux/sunrpc/gss_krb5.h>
@@ -438,10 +439,23 @@ static int gss_encode_v1_msg(struct gss_upcall_msg *gss_msg,
struct gss_api_mech *mech = gss_msg->auth->mech;
char *p = gss_msg->databuf;
size_t buflen = sizeof(gss_msg->databuf);
+ char idbuf[KID_STR_LEN];
int len;
- len = scnprintf(p, buflen, "mech=%s uid=%d", mech->gm_name,
- from_kuid_munged(userns, gss_msg->uid));
+ /*
+ * A UUID or GUID user is uid_r= in its text form instead of uid=,
+ * which gssd versions that don't know it reject rather than taking
+ * an overflow uid. Where gssd can't see the user at all, there is
+ * neither, and the upcall fails.
+ */
+ if (uid_is_legacy(gss_msg->uid))
+ len = scnprintf(p, buflen, "mech=%s uid=%d", mech->gm_name,
+ from_kuid_munged(userns, gss_msg->uid));
+ else if (kuid_has_mapping(userns, gss_msg->uid))
+ len = scnprintf(p, buflen, "mech=%s uid_r=%s", mech->gm_name,
+ kuid_str(gss_msg->uid, idbuf));
+ else
+ len = scnprintf(p, buflen, "mech=%s", mech->gm_name);
buflen -= len;
p += len;
gss_msg->msg.len = len;
@@ -536,6 +550,15 @@ gss_alloc_msg(struct gss_auth *gss_auth,
err = vers;
if (err < 0)
goto err_free_msg;
+ /*
+ * The binary v0 upcall is a 32 bit uid: UUID and GUID users would
+ * all be the overflow uid there and share its credentials.
+ */
+ if (vers == 0 && !uid_is_legacy(uid)) {
+ put_pipe_version(gss_auth->net);
+ err = -EACCES;
+ goto err_free_msg;
+ }
gss_msg->pipe = gss_auth->gss_pipe[vers]->pipe;
INIT_LIST_HEAD(&gss_msg->list);
rpc_init_wait_queue(&gss_msg->rpc_waitqueue, "RPCSEC_GSS upcall waitq");
@@ -752,7 +775,22 @@ gss_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
goto err;
}
- uid = make_kuid(current_user_ns(), id);
+ /*
+ * A uid of -1, never valid, is followed by a __kernel_uid_r_t: the
+ * reply to a uid_r= upcall of a UUID or GUID user.
+ */
+ if (id == (uid_t)-1) {
+ struct __kernel_uid_r id_r;
+
+ p = simple_get_bytes(p, end, &id_r, sizeof(id_r));
+ if (IS_ERR(p)) {
+ err = PTR_ERR(p);
+ goto err;
+ }
+ uid = make_kuid_r(current_user_ns(), &id_r);
+ } else {
+ uid = make_kuid(current_user_ns(), id);
+ }
if (!uid_valid(uid)) {
err = -EINVAL;
goto err;
[-- Attachment #17: uid_r-0016-ecryptfs-hash-daemons-by-kuid_hash.patch --]
[-- Type: text/x-patch, Size: 1100 bytes --]
From 4753274b6b481c959cdfc8c1c1ed87926f3e918f Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:44:28 +0200
Subject: [PATCH 16/43] ecryptfs: hash daemons by kuid_hash()
The daemon hash uses from_kuid(&init_user_ns, euid), which puts all UUID
and GUID users into one bucket. Lookups compare the kuid, so this only
costs time; use kuid_hash().
Assisted-by: Claude:claude-opus-5-5
---
fs/ecryptfs/messaging.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/ecryptfs/messaging.c b/fs/ecryptfs/messaging.c
index 73c7b8215..4e2524f78 100644
--- a/fs/ecryptfs/messaging.c
+++ b/fs/ecryptfs/messaging.c
@@ -21,7 +21,7 @@ static struct hlist_head *ecryptfs_daemon_hash;
DEFINE_MUTEX(ecryptfs_daemon_hash_mux);
static int ecryptfs_hash_bits;
#define ecryptfs_current_euid_hash(uid) \
- hash_long((unsigned long)from_kuid(&init_user_ns, current_euid()), ecryptfs_hash_bits)
+ hash_long((unsigned long)kuid_hash(current_euid()), ecryptfs_hash_bits)
static u32 ecryptfs_msg_counter;
static struct ecryptfs_msg_ctx *ecryptfs_msg_ctx_arr;
[-- Attachment #18: uid_r-0017-quota-don-t-account-UUID-and-GUID-owners.patch --]
[-- Type: text/x-patch, Size: 2130 bytes --]
From 3ffa1a2f4681e8c60b29af384f1b6b8933862ca0 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:01:52 +0200
Subject: [PATCH 17/43] quota: don't account UUID and GUID owners
Quota files and the quotactl interface use 32 bit ids. dqget() would
hash and store a UUID or GUID owner as -1, so all of them would share
one dquot, written out as id 4294967295.
Treat such ids like quota being off for them: dqget() returns -ESRCH,
which its callers already handle by not charging the inode. An inode
given to such an owner stays charged to the previous one.
Assisted-by: Claude:claude-opus-5-5
---
fs/quota/dquot.c | 3 +++
include/linux/quota.h | 18 ++++++++++++++++++
2 files changed, 21 insertions(+)
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 9850de395..363335461 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -954,6 +954,9 @@ struct dquot *dqget(struct super_block *sb, struct kqid qid)
if (!qid_has_mapping(sb->s_user_ns, qid))
return ERR_PTR(-EINVAL);
+ /* Quota files hold 32 bit ids: like quota off for UUIDs and GUIDs */
+ if (!qid_is_legacy(qid))
+ return ERR_PTR(-ESRCH);
if (!sb_has_quota_active(sb, qid.type))
return ERR_PTR(-ESRCH);
diff --git a/include/linux/quota.h b/include/linux/quota.h
index 89a0d83dd..00685209f 100644
--- a/include/linux/quota.h
+++ b/include/linux/quota.h
@@ -80,6 +80,24 @@ extern qid_t from_kqid(struct user_namespace *to, struct kqid qid);
extern qid_t from_kqid_munged(struct user_namespace *to, struct kqid qid);
extern bool qid_valid(struct kqid qid);
+/**
+ * qid_is_legacy - Report if a kqid fits the 32 bit ids of quota files
+ * @qid: The kernel internal quota identifier to test.
+ *
+ * UUID and GUID owners don't, and are not subject to quotas.
+ */
+static inline bool qid_is_legacy(struct kqid qid)
+{
+ switch (qid.type) {
+ case USRQUOTA:
+ return uid_is_legacy(qid.uid);
+ case GRPQUOTA:
+ return gid_is_legacy(qid.gid);
+ default:
+ return true;
+ }
+}
+
/**
* make_kqid - Map a user-namespace, type, qid tuple into a kqid.
* @from: User namespace that the qid is in
[-- Attachment #19: uid_r-0018-fs-keep-UUID-and-GUID-owners-off-filesystems-that-ca.patch --]
[-- Type: text/x-patch, Size: 4344 bytes --]
From 11b9761ef9b0089c4f6244221b91ea63786fd22d Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:46:04 +0200
Subject: [PATCH 18/43] fs: keep UUID and GUID owners off filesystems that
can't store them
Filesystems write inode owners with i_uid_read()/i_gid_read(), which
turn a UUID or GUID into -1. Add SB_I_UID_R for filesystems that can
store such owners, and refuse them elsewhere with -EOVERFLOW, the error
already used for ids without a mapping in the filesystem's namespace:
- notify_change() refuses ATTR_UID/ATTR_GID with such an id,
- fsuidgid_has_mapping() is false for a caller whose fsuid or fsgid is
one, so may_create() and friends refuse to create inodes owned by it.
No filesystem sets the flag yet.
Assisted-by: Claude:claude-opus-5-5
---
fs/attr.c | 7 +++++++
include/linux/fs.h | 29 +++++++++++++++++++++++++----
include/linux/fs/super_types.h | 1 +
3 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/fs/attr.c b/fs/attr.c
index 71888ac90..09d51b1a3 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -526,6 +526,13 @@ int notify_change(struct mnt_idmap *idmap, struct dentry *dentry,
!vfsgid_has_fsmapping(idmap, inode->i_sb->s_user_ns,
attr->ia_vfsgid))
return -EOVERFLOW;
+ /* and that the filesystem can store a UUID or GUID owner */
+ if (ia_valid & ATTR_UID &&
+ !sb_can_own_kuid(inode->i_sb, vfsuid_into_kuid(attr->ia_vfsuid)))
+ return -EOVERFLOW;
+ if (ia_valid & ATTR_GID &&
+ !sb_can_own_kgid(inode->i_sb, vfsgid_into_kgid(attr->ia_vfsgid)))
+ return -EOVERFLOW;
/* Don't allow modifications of files with invalid uids or
* gids unless those uids & gids are being made valid.
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 09de7bf6f..3778f48dd 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1563,14 +1563,35 @@ static inline void inode_fsgid_set(struct inode *inode,
inode->i_gid = mapped_fsgid(idmap, i_user_ns(inode));
}
+/**
+ * sb_can_own_kuid() - check whether a filesystem can store an owner
+ * @sb: the superblock
+ * @kuid: the owner
+ *
+ * Classic ids fit any filesystem; UUIDs and GUIDs only those that set
+ * SB_I_UID_R. Elsewhere i_uid_read() would turn them into -1.
+ *
+ * Return: true if @sb can store @kuid.
+ */
+static inline bool sb_can_own_kuid(const struct super_block *sb, kuid_t kuid)
+{
+ return uid_is_legacy(kuid) || (sb->s_iflags & SB_I_UID_R);
+}
+
+static inline bool sb_can_own_kgid(const struct super_block *sb, kgid_t kgid)
+{
+ return gid_is_legacy(kgid) || (sb->s_iflags & SB_I_UID_R);
+}
+
/**
* fsuidgid_has_mapping() - check whether caller's fsuid/fsgid is mapped
* @sb: the superblock we want a mapping in
* @idmap: idmap of the relevant mount
*
* Check whether the caller's fsuid and fsgid have a valid mapping in the
- * s_user_ns of the superblock @sb. If the caller is on an idmapped mount map
- * the caller's fsuid and fsgid according to the @idmap first.
+ * s_user_ns of the superblock @sb, and whether @sb can store them as owner
+ * of a new inode. If the caller is on an idmapped mount map the caller's
+ * fsuid and fsgid according to the @idmap first.
*
* Return: true if fsuid and fsgid is mapped, false if not.
*/
@@ -1582,10 +1603,10 @@ static inline bool fsuidgid_has_mapping(struct super_block *sb,
kgid_t kgid;
kuid = mapped_fsuid(idmap, fs_userns);
- if (!uid_valid(kuid))
+ if (!uid_valid(kuid) || !sb_can_own_kuid(sb, kuid))
return false;
kgid = mapped_fsgid(idmap, fs_userns);
- if (!gid_valid(kgid))
+ if (!gid_valid(kgid) || !sb_can_own_kgid(sb, kgid))
return false;
return kuid_has_mapping(fs_userns, kuid) &&
kgid_has_mapping(fs_userns, kgid);
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index 68747182a..8ba93c4b8 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -348,5 +348,6 @@ struct super_block {
#define SB_I_NOIDMAP 0x00002000 /* No idmapped mounts on this superblock */
#define SB_I_ALLOW_HSM 0x00004000 /* Allow HSM events on this superblock */
#define SB_I_NO_DATA_INTEGRITY 0x00008000 /* fs cannot guarantee data persistence on sync */
+#define SB_I_UID_R 0x00010000 /* fs stores UUID and GUID owners, not just classic ids */
#endif /* _LINUX_FS_SUPER_TYPES_H */
[-- Attachment #20: uid_r-0019-cred-add-the-_r-variants-of-the-get-set-id-system-ca.patch --]
[-- Type: text/x-patch, Size: 24812 bytes --]
From cf69bedf0e5b28065152ee8d73226ccf731e829e Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:46:04 +0200
Subject: [PATCH 19/43] cred: add the *_r variants of the get/set*id() system
calls
Add getuid_r(), geteuid_r(), getgid_r(), getegid_r(), setuid_r(),
setgid_r(), setreuid_r(), setregid_r(), setresuid_r(), getresuid_r(),
setresgid_r(), getresgid_r(), setfsuid_r() and setfsgid_r(). They work
like the classic calls but take and return pointers to
__kernel_uid_r_t, so a process can be a UUID or GUID user and group.
The classic calls see such ids as the overflow id, like the 16 bit
calls see ids above 65535.
To share the logic, the classic implementations are split into a part
converting the arguments and a do_*() part working on kuid_t/kgid_t,
in which an invalid id means "unchanged" -- what -1 converts to. A
NULL pointer, UID_R_NONE or a classic -1 mean "unchanged" in the new
calls as well.
UUIDs and GUIDs have no mapping outside the initial user namespace, so
only CAP_SETUID/CAP_SETGID there can switch to one.
setfsuid_r() and setfsgid_r() differ from the classic calls in one
respect: they return -EPERM when the change is not allowed instead of
failing silently, and hand the old id back through a second argument.
The system call numbers are assigned in a later patch.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/syscalls.h | 27 +++
kernel/sys.c | 472 +++++++++++++++++++++++++++++----------
kernel/sys_ni.c | 10 +
3 files changed, 385 insertions(+), 124 deletions(-)
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 874d9067a..903cc6fa0 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -17,6 +17,7 @@ struct iocb;
struct io_event;
struct iovec;
struct __kernel_old_itimerval;
+struct __kernel_uid_r;
struct kexec_segment;
struct linux_dirent;
struct linux_dirent64;
@@ -966,6 +967,32 @@ asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len,
int flags, uint32_t sig);
asmlinkage long sys_rseq_slice_yield(void);
+asmlinkage long sys_getuid_r(struct __kernel_uid_r __user *uid);
+asmlinkage long sys_geteuid_r(struct __kernel_uid_r __user *uid);
+asmlinkage long sys_getgid_r(struct __kernel_uid_r __user *gid);
+asmlinkage long sys_getegid_r(struct __kernel_uid_r __user *gid);
+asmlinkage long sys_setuid_r(const struct __kernel_uid_r __user *uid);
+asmlinkage long sys_setgid_r(const struct __kernel_uid_r __user *gid);
+asmlinkage long sys_setreuid_r(const struct __kernel_uid_r __user *ruid,
+ const struct __kernel_uid_r __user *euid);
+asmlinkage long sys_setregid_r(const struct __kernel_uid_r __user *rgid,
+ const struct __kernel_uid_r __user *egid);
+asmlinkage long sys_setresuid_r(const struct __kernel_uid_r __user *ruid,
+ const struct __kernel_uid_r __user *euid,
+ const struct __kernel_uid_r __user *suid);
+asmlinkage long sys_getresuid_r(struct __kernel_uid_r __user *ruid,
+ struct __kernel_uid_r __user *euid,
+ struct __kernel_uid_r __user *suid);
+asmlinkage long sys_setresgid_r(const struct __kernel_uid_r __user *rgid,
+ const struct __kernel_uid_r __user *egid,
+ const struct __kernel_uid_r __user *sgid);
+asmlinkage long sys_getresgid_r(struct __kernel_uid_r __user *rgid,
+ struct __kernel_uid_r __user *egid,
+ struct __kernel_uid_r __user *sgid);
+asmlinkage long sys_setfsuid_r(const struct __kernel_uid_r __user *uid,
+ struct __kernel_uid_r __user *old_uid);
+asmlinkage long sys_setfsgid_r(const struct __kernel_uid_r __user *gid,
+ struct __kernel_uid_r __user *old_gid);
asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
asmlinkage long sys_open_tree_attr(int dfd, const char __user *path,
unsigned flags,
diff --git a/kernel/sys.c b/kernel/sys.c
index 35b538ba8..fad557323 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -410,21 +410,12 @@ SYSCALL_DEFINE2(getpriority, int, which, int, who)
* operations (as far as semantic preservation is concerned).
*/
#ifdef CONFIG_MULTIUSER
-long __sys_setregid(gid_t rgid, gid_t egid)
+/* Invalid ids (INVALID_GID) stand for "unchanged" */
+static long do_setregid(kgid_t krgid, kgid_t kegid)
{
- struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
- kgid_t krgid, kegid;
-
- krgid = make_kgid(ns, rgid);
- kegid = make_kgid(ns, egid);
-
- if ((rgid != (gid_t) -1) && !gid_valid(krgid))
- return -EINVAL;
- if ((egid != (gid_t) -1) && !gid_valid(kegid))
- return -EINVAL;
new = prepare_creds();
if (!new)
@@ -432,7 +423,7 @@ long __sys_setregid(gid_t rgid, gid_t egid)
old = current_cred();
retval = -EPERM;
- if (rgid != (gid_t) -1) {
+ if (gid_valid(krgid)) {
if (gid_eq(old->gid, krgid) ||
gid_eq(old->egid, krgid) ||
ns_capable_setid(old->user_ns, CAP_SETGID))
@@ -440,7 +431,7 @@ long __sys_setregid(gid_t rgid, gid_t egid)
else
goto error;
}
- if (egid != (gid_t) -1) {
+ if (gid_valid(kegid)) {
if (gid_eq(old->gid, kegid) ||
gid_eq(old->egid, kegid) ||
gid_eq(old->sgid, kegid) ||
@@ -450,8 +441,8 @@ long __sys_setregid(gid_t rgid, gid_t egid)
goto error;
}
- if (rgid != (gid_t) -1 ||
- (egid != (gid_t) -1 && !gid_eq(kegid, old->gid)))
+ if (gid_valid(krgid) ||
+ (gid_valid(kegid) && !gid_eq(kegid, old->gid)))
new->sgid = new->egid;
new->fsgid = new->egid;
@@ -466,6 +457,22 @@ long __sys_setregid(gid_t rgid, gid_t egid)
return retval;
}
+long __sys_setregid(gid_t rgid, gid_t egid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kgid_t krgid, kegid;
+
+ krgid = make_kgid(ns, rgid);
+ kegid = make_kgid(ns, egid);
+
+ if ((rgid != (gid_t) -1) && !gid_valid(krgid))
+ return -EINVAL;
+ if ((egid != (gid_t) -1) && !gid_valid(kegid))
+ return -EINVAL;
+
+ return do_setregid(krgid, kegid);
+}
+
SYSCALL_DEFINE2(setregid, gid_t, rgid, gid_t, egid)
{
return __sys_setregid(rgid, egid);
@@ -476,15 +483,12 @@ SYSCALL_DEFINE2(setregid, gid_t, rgid, gid_t, egid)
*
* SMP: Same implicit races as above.
*/
-long __sys_setgid(gid_t gid)
+static long do_setgid(kgid_t kgid)
{
- struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
- kgid_t kgid;
- kgid = make_kgid(ns, gid);
if (!gid_valid(kgid))
return -EINVAL;
@@ -512,6 +516,11 @@ long __sys_setgid(gid_t gid)
return retval;
}
+long __sys_setgid(gid_t gid)
+{
+ return do_setgid(make_kgid(current_user_ns(), gid));
+}
+
SYSCALL_DEFINE1(setgid, gid_t, gid)
{
return __sys_setgid(gid);
@@ -567,21 +576,12 @@ static void flag_nproc_exceeded(struct cred *new)
* 100% compatible with BSD. A program which uses just setuid() will be
* 100% compatible with POSIX with saved IDs.
*/
-long __sys_setreuid(uid_t ruid, uid_t euid)
+/* Invalid ids (INVALID_UID) stand for "unchanged" */
+static long do_setreuid(kuid_t kruid, kuid_t keuid)
{
- struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
- kuid_t kruid, keuid;
-
- kruid = make_kuid(ns, ruid);
- keuid = make_kuid(ns, euid);
-
- if ((ruid != (uid_t) -1) && !uid_valid(kruid))
- return -EINVAL;
- if ((euid != (uid_t) -1) && !uid_valid(keuid))
- return -EINVAL;
new = prepare_creds();
if (!new)
@@ -589,7 +589,7 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
old = current_cred();
retval = -EPERM;
- if (ruid != (uid_t) -1) {
+ if (uid_valid(kruid)) {
new->uid = kruid;
if (!uid_eq(old->uid, kruid) &&
!uid_eq(old->euid, kruid) &&
@@ -597,7 +597,7 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
goto error;
}
- if (euid != (uid_t) -1) {
+ if (uid_valid(keuid)) {
new->euid = keuid;
if (!uid_eq(old->uid, keuid) &&
!uid_eq(old->euid, keuid) &&
@@ -611,8 +611,8 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
if (retval < 0)
goto error;
}
- if (ruid != (uid_t) -1 ||
- (euid != (uid_t) -1 && !uid_eq(keuid, old->uid)))
+ if (uid_valid(kruid) ||
+ (uid_valid(keuid) && !uid_eq(keuid, old->uid)))
new->suid = new->euid;
new->fsuid = new->euid;
@@ -632,6 +632,22 @@ long __sys_setreuid(uid_t ruid, uid_t euid)
return retval;
}
+long __sys_setreuid(uid_t ruid, uid_t euid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kuid_t kruid, keuid;
+
+ kruid = make_kuid(ns, ruid);
+ keuid = make_kuid(ns, euid);
+
+ if ((ruid != (uid_t) -1) && !uid_valid(kruid))
+ return -EINVAL;
+ if ((euid != (uid_t) -1) && !uid_valid(keuid))
+ return -EINVAL;
+
+ return do_setreuid(kruid, keuid);
+}
+
SYSCALL_DEFINE2(setreuid, uid_t, ruid, uid_t, euid)
{
return __sys_setreuid(ruid, euid);
@@ -648,15 +664,12 @@ SYSCALL_DEFINE2(setreuid, uid_t, ruid, uid_t, euid)
* will allow a root program to temporarily drop privileges and be able to
* regain them by swapping the real and effective uid.
*/
-long __sys_setuid(uid_t uid)
+static long do_setuid(kuid_t kuid)
{
- struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
- kuid_t kuid;
- kuid = make_kuid(ns, uid);
if (!uid_valid(kuid))
return -EINVAL;
@@ -695,6 +708,11 @@ long __sys_setuid(uid_t uid)
return retval;
}
+long __sys_setuid(uid_t uid)
+{
+ return do_setuid(make_kuid(current_user_ns(), uid));
+}
+
SYSCALL_DEFINE1(setuid, uid_t, uid)
{
return __sys_setuid(uid);
@@ -705,42 +723,28 @@ SYSCALL_DEFINE1(setuid, uid_t, uid)
* This function implements a generic ability to update ruid, euid,
* and suid. This allows you to implement the 4.4 compatible seteuid().
*/
-long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
+/* Invalid ids (INVALID_UID) stand for "unchanged" */
+static long do_setresuid(kuid_t kruid, kuid_t keuid, kuid_t ksuid)
{
- struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
- kuid_t kruid, keuid, ksuid;
bool ruid_new, euid_new, suid_new;
- kruid = make_kuid(ns, ruid);
- keuid = make_kuid(ns, euid);
- ksuid = make_kuid(ns, suid);
-
- if ((ruid != (uid_t) -1) && !uid_valid(kruid))
- return -EINVAL;
-
- if ((euid != (uid_t) -1) && !uid_valid(keuid))
- return -EINVAL;
-
- if ((suid != (uid_t) -1) && !uid_valid(ksuid))
- return -EINVAL;
-
old = current_cred();
/* check for no-op */
- if ((ruid == (uid_t) -1 || uid_eq(kruid, old->uid)) &&
- (euid == (uid_t) -1 || (uid_eq(keuid, old->euid) &&
- uid_eq(keuid, old->fsuid))) &&
- (suid == (uid_t) -1 || uid_eq(ksuid, old->suid)))
+ if ((!uid_valid(kruid) || uid_eq(kruid, old->uid)) &&
+ (!uid_valid(keuid) || (uid_eq(keuid, old->euid) &&
+ uid_eq(keuid, old->fsuid))) &&
+ (!uid_valid(ksuid) || uid_eq(ksuid, old->suid)))
return 0;
- ruid_new = ruid != (uid_t) -1 && !uid_eq(kruid, old->uid) &&
+ ruid_new = uid_valid(kruid) && !uid_eq(kruid, old->uid) &&
!uid_eq(kruid, old->euid) && !uid_eq(kruid, old->suid);
- euid_new = euid != (uid_t) -1 && !uid_eq(keuid, old->uid) &&
+ euid_new = uid_valid(keuid) && !uid_eq(keuid, old->uid) &&
!uid_eq(keuid, old->euid) && !uid_eq(keuid, old->suid);
- suid_new = suid != (uid_t) -1 && !uid_eq(ksuid, old->uid) &&
+ suid_new = uid_valid(ksuid) && !uid_eq(ksuid, old->uid) &&
!uid_eq(ksuid, old->euid) && !uid_eq(ksuid, old->suid);
if ((ruid_new || euid_new || suid_new) &&
!ns_capable_setid(old->user_ns, CAP_SETUID))
@@ -750,7 +754,7 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
if (!new)
return -ENOMEM;
- if (ruid != (uid_t) -1) {
+ if (uid_valid(kruid)) {
new->uid = kruid;
if (!uid_eq(kruid, old->uid)) {
retval = set_user(new);
@@ -758,9 +762,9 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
goto error;
}
}
- if (euid != (uid_t) -1)
+ if (uid_valid(keuid))
new->euid = keuid;
- if (suid != (uid_t) -1)
+ if (uid_valid(ksuid))
new->suid = ksuid;
new->fsuid = new->euid;
@@ -780,6 +784,27 @@ long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
return retval;
}
+long __sys_setresuid(uid_t ruid, uid_t euid, uid_t suid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kuid_t kruid, keuid, ksuid;
+
+ kruid = make_kuid(ns, ruid);
+ keuid = make_kuid(ns, euid);
+ ksuid = make_kuid(ns, suid);
+
+ if ((ruid != (uid_t) -1) && !uid_valid(kruid))
+ return -EINVAL;
+
+ if ((euid != (uid_t) -1) && !uid_valid(keuid))
+ return -EINVAL;
+
+ if ((suid != (uid_t) -1) && !uid_valid(ksuid))
+ return -EINVAL;
+
+ return do_setresuid(kruid, keuid, ksuid);
+}
+
SYSCALL_DEFINE3(setresuid, uid_t, ruid, uid_t, euid, uid_t, suid)
{
return __sys_setresuid(ruid, euid, suid);
@@ -807,40 +832,28 @@ SYSCALL_DEFINE3(getresuid, uid_t __user *, ruidp, uid_t __user *, euidp, uid_t _
/*
* Same as above, but for rgid, egid, sgid.
*/
-long __sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
+/* Invalid ids (INVALID_GID) stand for "unchanged" */
+static long do_setresgid(kgid_t krgid, kgid_t kegid, kgid_t ksgid)
{
- struct user_namespace *ns = current_user_ns();
const struct cred *old;
struct cred *new;
int retval;
- kgid_t krgid, kegid, ksgid;
bool rgid_new, egid_new, sgid_new;
- krgid = make_kgid(ns, rgid);
- kegid = make_kgid(ns, egid);
- ksgid = make_kgid(ns, sgid);
-
- if ((rgid != (gid_t) -1) && !gid_valid(krgid))
- return -EINVAL;
- if ((egid != (gid_t) -1) && !gid_valid(kegid))
- return -EINVAL;
- if ((sgid != (gid_t) -1) && !gid_valid(ksgid))
- return -EINVAL;
-
old = current_cred();
/* check for no-op */
- if ((rgid == (gid_t) -1 || gid_eq(krgid, old->gid)) &&
- (egid == (gid_t) -1 || (gid_eq(kegid, old->egid) &&
- gid_eq(kegid, old->fsgid))) &&
- (sgid == (gid_t) -1 || gid_eq(ksgid, old->sgid)))
+ if ((!gid_valid(krgid) || gid_eq(krgid, old->gid)) &&
+ (!gid_valid(kegid) || (gid_eq(kegid, old->egid) &&
+ gid_eq(kegid, old->fsgid))) &&
+ (!gid_valid(ksgid) || gid_eq(ksgid, old->sgid)))
return 0;
- rgid_new = rgid != (gid_t) -1 && !gid_eq(krgid, old->gid) &&
+ rgid_new = gid_valid(krgid) && !gid_eq(krgid, old->gid) &&
!gid_eq(krgid, old->egid) && !gid_eq(krgid, old->sgid);
- egid_new = egid != (gid_t) -1 && !gid_eq(kegid, old->gid) &&
+ egid_new = gid_valid(kegid) && !gid_eq(kegid, old->gid) &&
!gid_eq(kegid, old->egid) && !gid_eq(kegid, old->sgid);
- sgid_new = sgid != (gid_t) -1 && !gid_eq(ksgid, old->gid) &&
+ sgid_new = gid_valid(ksgid) && !gid_eq(ksgid, old->gid) &&
!gid_eq(ksgid, old->egid) && !gid_eq(ksgid, old->sgid);
if ((rgid_new || egid_new || sgid_new) &&
!ns_capable_setid(old->user_ns, CAP_SETGID))
@@ -850,11 +863,11 @@ long __sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
if (!new)
return -ENOMEM;
- if (rgid != (gid_t) -1)
+ if (gid_valid(krgid))
new->gid = krgid;
- if (egid != (gid_t) -1)
+ if (gid_valid(kegid))
new->egid = kegid;
- if (sgid != (gid_t) -1)
+ if (gid_valid(ksgid))
new->sgid = ksgid;
new->fsgid = new->egid;
@@ -869,6 +882,25 @@ long __sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
return retval;
}
+long __sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kgid_t krgid, kegid, ksgid;
+
+ krgid = make_kgid(ns, rgid);
+ kegid = make_kgid(ns, egid);
+ ksgid = make_kgid(ns, sgid);
+
+ if ((rgid != (gid_t) -1) && !gid_valid(krgid))
+ return -EINVAL;
+ if ((egid != (gid_t) -1) && !gid_valid(kegid))
+ return -EINVAL;
+ if ((sgid != (gid_t) -1) && !gid_valid(ksgid))
+ return -EINVAL;
+
+ return do_setresgid(krgid, kegid, ksgid);
+}
+
SYSCALL_DEFINE3(setresgid, gid_t, rgid, gid_t, egid, gid_t, sgid)
{
return __sys_setresgid(rgid, egid, sgid);
@@ -901,40 +933,44 @@ SYSCALL_DEFINE3(getresgid, gid_t __user *, rgidp, gid_t __user *, egidp, gid_t _
* whatever uid it wants to). It normally shadows "euid", except when
* explicitly set by setfsuid() or for access..
*/
-long __sys_setfsuid(uid_t uid)
+/*
+ * Sets the fsuid to @kuid, unless it is invalid; 0 or -EPERM. The fsuid
+ * before the call goes to @old.
+ */
+static long do_setfsuid(kuid_t kuid, kuid_t *old_fsuid)
{
const struct cred *old;
struct cred *new;
- uid_t old_fsuid;
- kuid_t kuid;
old = current_cred();
- old_fsuid = from_kuid_munged(old->user_ns, old->fsuid);
+ *old_fsuid = old->fsuid;
- kuid = make_kuid(old->user_ns, uid);
- if (!uid_valid(kuid))
- return old_fsuid;
+ if (!uid_valid(kuid) || uid_eq(kuid, old->fsuid))
+ return 0;
new = prepare_creds();
if (!new)
- return old_fsuid;
+ return -ENOMEM;
if (uid_eq(kuid, old->uid) || uid_eq(kuid, old->euid) ||
- uid_eq(kuid, old->suid) || uid_eq(kuid, old->fsuid) ||
+ uid_eq(kuid, old->suid) ||
ns_capable_setid(old->user_ns, CAP_SETUID)) {
- if (!uid_eq(kuid, old->fsuid)) {
- new->fsuid = kuid;
- if (security_task_fix_setuid(new, old, LSM_SETID_FS) == 0)
- goto change_okay;
- }
+ new->fsuid = kuid;
+ if (security_task_fix_setuid(new, old, LSM_SETID_FS) == 0)
+ return commit_creds(new);
}
abort_creds(new);
- return old_fsuid;
+ return -EPERM;
+}
+
+long __sys_setfsuid(uid_t uid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kuid_t old_fsuid;
-change_okay:
- commit_creds(new);
- return old_fsuid;
+ do_setfsuid(make_kuid(ns, uid), &old_fsuid);
+ return from_kuid_munged(ns, old_fsuid);
}
SYSCALL_DEFINE1(setfsuid, uid_t, uid)
@@ -945,46 +981,214 @@ SYSCALL_DEFINE1(setfsuid, uid_t, uid)
/*
* Samma på svenska..
*/
-long __sys_setfsgid(gid_t gid)
+/*
+ * Sets the fsgid to @kgid, unless it is invalid; 0 or -EPERM. The fsgid
+ * before the call goes to @old.
+ */
+static long do_setfsgid(kgid_t kgid, kgid_t *old_fsgid)
{
const struct cred *old;
struct cred *new;
- gid_t old_fsgid;
- kgid_t kgid;
old = current_cred();
- old_fsgid = from_kgid_munged(old->user_ns, old->fsgid);
+ *old_fsgid = old->fsgid;
- kgid = make_kgid(old->user_ns, gid);
- if (!gid_valid(kgid))
- return old_fsgid;
+ if (!gid_valid(kgid) || gid_eq(kgid, old->fsgid))
+ return 0;
new = prepare_creds();
if (!new)
- return old_fsgid;
+ return -ENOMEM;
if (gid_eq(kgid, old->gid) || gid_eq(kgid, old->egid) ||
- gid_eq(kgid, old->sgid) || gid_eq(kgid, old->fsgid) ||
+ gid_eq(kgid, old->sgid) ||
ns_capable_setid(old->user_ns, CAP_SETGID)) {
- if (!gid_eq(kgid, old->fsgid)) {
- new->fsgid = kgid;
- if (security_task_fix_setgid(new,old,LSM_SETID_FS) == 0)
- goto change_okay;
- }
+ new->fsgid = kgid;
+ if (security_task_fix_setgid(new, old, LSM_SETID_FS) == 0)
+ return commit_creds(new);
}
abort_creds(new);
- return old_fsgid;
+ return -EPERM;
+}
+
+long __sys_setfsgid(gid_t gid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kgid_t old_fsgid;
-change_okay:
- commit_creds(new);
- return old_fsgid;
+ do_setfsgid(make_kgid(ns, gid), &old_fsgid);
+ return from_kgid_munged(ns, old_fsgid);
}
SYSCALL_DEFINE1(setfsgid, gid_t, gid)
{
return __sys_setfsgid(gid);
}
+
+/*
+ * The *_r variants take and return __kernel_uid_r_t, which can also be a
+ * UUID or a GUID. A NULL pointer, UID_R_NONE or a classic -1 leave an id
+ * unchanged, like -1 does for the classic calls.
+ */
+SYSCALL_DEFINE1(setuid_r, const struct __kernel_uid_r __user *, uid)
+{
+ kuid_t kuid;
+ int err;
+
+ err = get_kuid_r(current_user_ns(), uid, &kuid);
+ if (err)
+ return err;
+ return do_setuid(kuid);
+}
+
+SYSCALL_DEFINE1(setgid_r, const struct __kernel_uid_r __user *, gid)
+{
+ kgid_t kgid;
+ int err;
+
+ err = get_kgid_r(current_user_ns(), gid, &kgid);
+ if (err)
+ return err;
+ return do_setgid(kgid);
+}
+
+SYSCALL_DEFINE2(setreuid_r, const struct __kernel_uid_r __user *, ruid,
+ const struct __kernel_uid_r __user *, euid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kuid_t kruid, keuid;
+ int err;
+
+ err = get_kuid_r(ns, ruid, &kruid);
+ if (!err)
+ err = get_kuid_r(ns, euid, &keuid);
+ if (err)
+ return err;
+ return do_setreuid(kruid, keuid);
+}
+
+SYSCALL_DEFINE2(setregid_r, const struct __kernel_uid_r __user *, rgid,
+ const struct __kernel_uid_r __user *, egid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kgid_t krgid, kegid;
+ int err;
+
+ err = get_kgid_r(ns, rgid, &krgid);
+ if (!err)
+ err = get_kgid_r(ns, egid, &kegid);
+ if (err)
+ return err;
+ return do_setregid(krgid, kegid);
+}
+
+SYSCALL_DEFINE3(setresuid_r, const struct __kernel_uid_r __user *, ruid,
+ const struct __kernel_uid_r __user *, euid,
+ const struct __kernel_uid_r __user *, suid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kuid_t kruid, keuid, ksuid;
+ int err;
+
+ err = get_kuid_r(ns, ruid, &kruid);
+ if (!err)
+ err = get_kuid_r(ns, euid, &keuid);
+ if (!err)
+ err = get_kuid_r(ns, suid, &ksuid);
+ if (err)
+ return err;
+ return do_setresuid(kruid, keuid, ksuid);
+}
+
+SYSCALL_DEFINE3(setresgid_r, const struct __kernel_uid_r __user *, rgid,
+ const struct __kernel_uid_r __user *, egid,
+ const struct __kernel_uid_r __user *, sgid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kgid_t krgid, kegid, ksgid;
+ int err;
+
+ err = get_kgid_r(ns, rgid, &krgid);
+ if (!err)
+ err = get_kgid_r(ns, egid, &kegid);
+ if (!err)
+ err = get_kgid_r(ns, sgid, &ksgid);
+ if (err)
+ return err;
+ return do_setresgid(krgid, kegid, ksgid);
+}
+
+/* NULL pointers are skipped */
+SYSCALL_DEFINE3(getresuid_r, struct __kernel_uid_r __user *, ruid,
+ struct __kernel_uid_r __user *, euid,
+ struct __kernel_uid_r __user *, suid)
+{
+ const struct cred *cred = current_cred();
+ int err = 0;
+
+ if (ruid)
+ err = put_kuid_r(cred->user_ns, cred->uid, ruid);
+ if (!err && euid)
+ err = put_kuid_r(cred->user_ns, cred->euid, euid);
+ if (!err && suid)
+ err = put_kuid_r(cred->user_ns, cred->suid, suid);
+ return err;
+}
+
+SYSCALL_DEFINE3(getresgid_r, struct __kernel_uid_r __user *, rgid,
+ struct __kernel_uid_r __user *, egid,
+ struct __kernel_uid_r __user *, sgid)
+{
+ const struct cred *cred = current_cred();
+ int err = 0;
+
+ if (rgid)
+ err = put_kgid_r(cred->user_ns, cred->gid, rgid);
+ if (!err && egid)
+ err = put_kgid_r(cred->user_ns, cred->egid, egid);
+ if (!err && sgid)
+ err = put_kgid_r(cred->user_ns, cred->sgid, sgid);
+ return err;
+}
+
+/*
+ * Unlike setfsuid(), which returns the old fsuid whatever happens,
+ * setfsuid_r() reports failure: 0, or -EPERM if the caller may not use
+ * @uid. The fsuid before the call goes to @old_uid unless it is NULL; a
+ * NULL @uid only queries it.
+ */
+SYSCALL_DEFINE2(setfsuid_r, const struct __kernel_uid_r __user *, uid,
+ struct __kernel_uid_r __user *, old_uid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kuid_t kuid, old_fsuid;
+ int err;
+
+ err = get_kuid_r(ns, uid, &kuid);
+ if (err)
+ return err;
+ err = do_setfsuid(kuid, &old_fsuid);
+ if (!err && old_uid)
+ err = put_kuid_r(ns, old_fsuid, old_uid);
+ return err;
+}
+
+SYSCALL_DEFINE2(setfsgid_r, const struct __kernel_uid_r __user *, gid,
+ struct __kernel_uid_r __user *, old_gid)
+{
+ struct user_namespace *ns = current_user_ns();
+ kgid_t kgid, old_fsgid;
+ int err;
+
+ err = get_kgid_r(ns, gid, &kgid);
+ if (err)
+ return err;
+ err = do_setfsgid(kgid, &old_fsgid);
+ if (!err && old_gid)
+ err = put_kgid_r(ns, old_fsgid, old_gid);
+ return err;
+}
#endif /* CONFIG_MULTIUSER */
/**
@@ -1048,6 +1252,26 @@ SYSCALL_DEFINE0(getegid)
return from_kgid_munged(current_user_ns(), current_egid());
}
+SYSCALL_DEFINE1(getuid_r, struct __kernel_uid_r __user *, uid)
+{
+ return put_kuid_r(current_user_ns(), current_uid(), uid);
+}
+
+SYSCALL_DEFINE1(geteuid_r, struct __kernel_uid_r __user *, uid)
+{
+ return put_kuid_r(current_user_ns(), current_euid(), uid);
+}
+
+SYSCALL_DEFINE1(getgid_r, struct __kernel_uid_r __user *, gid)
+{
+ return put_kgid_r(current_user_ns(), current_gid(), gid);
+}
+
+SYSCALL_DEFINE1(getegid_r, struct __kernel_uid_r __user *, gid)
+{
+ return put_kgid_r(current_user_ns(), current_egid(), gid);
+}
+
static void do_sys_times(struct tms *tms)
{
u64 tgutime, tgstime, cutime, cstime;
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index add3032da..090996af1 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -103,6 +103,16 @@ COND_SYSCALL(setfsuid);
COND_SYSCALL(setfsgid);
COND_SYSCALL(setgroups);
COND_SYSCALL(getgroups);
+COND_SYSCALL(setregid_r);
+COND_SYSCALL(setgid_r);
+COND_SYSCALL(setreuid_r);
+COND_SYSCALL(setuid_r);
+COND_SYSCALL(setresuid_r);
+COND_SYSCALL(getresuid_r);
+COND_SYSCALL(setresgid_r);
+COND_SYSCALL(getresgid_r);
+COND_SYSCALL(setfsuid_r);
+COND_SYSCALL(setfsgid_r);
COND_SYSCALL(mq_open);
COND_SYSCALL_COMPAT(mq_open);
COND_SYSCALL(mq_unlink);
[-- Attachment #21: uid_r-0020-groups-add-getgroups_r-and-setgroups_r.patch --]
[-- Type: text/x-patch, Size: 4982 bytes --]
From c65fc7ba019e7c8e55cb4f3cc5f8f30a3e3ba6d5 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:46:05 +0200
Subject: [PATCH 20/43] groups: add getgroups_r() and setgroups_r()
Supplementary groups as arrays of __kernel_gid_r_t, so they can include
UUID and GUID groups. Unlike the set*id_r() calls, "no id" is not
accepted in the list. getgroups() reports such groups as the overflow
gid.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/syscalls.h | 4 ++
kernel/groups.c | 79 +++++++++++++++++++++++++++++++++++++++-
kernel/sys_ni.c | 2 +
3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 903cc6fa0..8e7b4e927 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -993,6 +993,10 @@ asmlinkage long sys_setfsuid_r(const struct __kernel_uid_r __user *uid,
struct __kernel_uid_r __user *old_uid);
asmlinkage long sys_setfsgid_r(const struct __kernel_uid_r __user *gid,
struct __kernel_uid_r __user *old_gid);
+asmlinkage long sys_getgroups_r(int gidsetsize,
+ struct __kernel_uid_r __user *grouplist);
+asmlinkage long sys_setgroups_r(int gidsetsize,
+ const struct __kernel_uid_r __user *grouplist);
asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
asmlinkage long sys_open_tree_attr(int dfd, const char __user *path,
unsigned flags,
diff --git a/kernel/groups.c b/kernel/groups.c
index 3aa0651d7..8eb8260fc 100644
--- a/kernel/groups.c
+++ b/kernel/groups.c
@@ -8,6 +8,7 @@
#include <linux/security.h>
#include <linux/sort.h>
#include <linux/syscalls.h>
+#include <linux/uidgid_r.h>
#include <linux/user_namespace.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
@@ -194,7 +195,9 @@ bool may_setgroups(void)
* without another task interfering.
*/
-SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
+static long do_setgroups(int gidsetsize, const void __user *grouplist,
+ int (*from_user)(struct group_info *,
+ const void __user *))
{
struct group_info *group_info;
int retval;
@@ -207,7 +210,7 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
group_info = groups_alloc(gidsetsize);
if (!group_info)
return -ENOMEM;
- retval = groups_from_user(group_info, grouplist);
+ retval = from_user(group_info, grouplist);
if (retval) {
put_group_info(group_info);
return retval;
@@ -220,6 +223,78 @@ SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
return retval;
}
+static int groups_from_user_classic(struct group_info *group_info,
+ const void __user *grouplist)
+{
+ return groups_from_user(group_info, (gid_t __user *)grouplist);
+}
+
+SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
+{
+ return do_setgroups(gidsetsize, grouplist, groups_from_user_classic);
+}
+
+/* group_info to a user-space array of __kernel_gid_r_t */
+static int groups_to_user_r(struct __kernel_uid_r __user *grouplist,
+ const struct group_info *group_info)
+{
+ struct user_namespace *user_ns = current_user_ns();
+ unsigned int i;
+
+ for (i = 0; i < group_info->ngroups; i++) {
+ if (put_kgid_r(user_ns, group_info->gid[i], grouplist + i))
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/* group_info from a user-space array of __kernel_gid_r_t; no "no id" here */
+static int groups_from_user_r(struct group_info *group_info,
+ const void __user *grouplist)
+{
+ const struct __kernel_uid_r __user *list = grouplist;
+ struct user_namespace *user_ns = current_user_ns();
+ struct __kernel_uid_r id;
+ unsigned int i;
+
+ for (i = 0; i < group_info->ngroups; i++) {
+ kgid_t kgid;
+
+ if (copy_from_user(&id, list + i, sizeof(id)))
+ return -EFAULT;
+ kgid = make_kgid_r(user_ns, &id);
+ if (!gid_valid(kgid))
+ return -EINVAL;
+ group_info->gid[i] = kgid;
+ }
+ return 0;
+}
+
+SYSCALL_DEFINE2(getgroups_r, int, gidsetsize,
+ struct __kernel_uid_r __user *, grouplist)
+{
+ const struct cred *cred = current_cred();
+ int i;
+
+ if (gidsetsize < 0)
+ return -EINVAL;
+
+ i = cred->group_info->ngroups;
+ if (gidsetsize) {
+ if (i > gidsetsize)
+ return -EINVAL;
+ if (groups_to_user_r(grouplist, cred->group_info))
+ return -EFAULT;
+ }
+ return i;
+}
+
+SYSCALL_DEFINE2(setgroups_r, int, gidsetsize,
+ const struct __kernel_uid_r __user *, grouplist)
+{
+ return do_setgroups(gidsetsize, grouplist, groups_from_user_r);
+}
+
/*
* Check whether we're fsgid/egid or in the supplemental group..
*/
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 090996af1..555c94d58 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -113,6 +113,8 @@ COND_SYSCALL(setresgid_r);
COND_SYSCALL(getresgid_r);
COND_SYSCALL(setfsuid_r);
COND_SYSCALL(setfsgid_r);
+COND_SYSCALL(setgroups_r);
+COND_SYSCALL(getgroups_r);
COND_SYSCALL(mq_open);
COND_SYSCALL_COMPAT(mq_open);
COND_SYSCALL(mq_unlink);
[-- Attachment #22: uid_r-0021-fs-add-chown_r-lchown_r-fchown_r-and-fchownat_r.patch --]
[-- Type: text/x-patch, Size: 8283 bytes --]
From 44dd43a5e185f3e17a1a41b91783a80bfafd7c2f Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:46:05 +0200
Subject: [PATCH 21/43] fs: add chown_r(), lchown_r(), fchown_r() and
fchownat_r()
The chown family with __kernel_uid_r_t owner and group, so files can be
given UUID and GUID owners on filesystems that support them
(SB_I_UID_R); others refuse with -EOVERFLOW. NULL pointers, UID_R_NONE
and the classic -1 leave the owner or group unchanged.
The classic calls and the new ones share the lookup and setattr code,
which now works on a struct chown_ids. The classic calls still report a
malformed id only once the file is found, as before.
Assisted-by: Claude:claude-opus-5-5
---
fs/open.c | 144 +++++++++++++++++++++++++++++++++++----
include/linux/syscalls.h | 13 ++++
2 files changed, 145 insertions(+), 12 deletions(-)
diff --git a/fs/open.c b/fs/open.c
index 408925d7b..6c9ed2408 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -736,7 +736,46 @@ static inline bool setattr_vfsgid(struct iattr *attr, kgid_t kgid)
return true;
}
-int chown_common(const struct path *path, uid_t user, gid_t group)
+/*
+ * The new owner and group of a chown. Invalid ids stand for "unchanged";
+ * @err is an error of their conversion, reported once the path is found,
+ * which keeps the classic calls' order of errors.
+ */
+struct chown_ids {
+ kuid_t uid;
+ kgid_t gid;
+ int err;
+};
+
+static struct chown_ids chown_ids_classic(uid_t user, gid_t group)
+{
+ struct user_namespace *ns = current_user_ns();
+ struct chown_ids ids = {
+ .uid = make_kuid(ns, user),
+ .gid = make_kgid(ns, group),
+ };
+
+ if ((user != (uid_t)-1 && !uid_valid(ids.uid)) ||
+ (group != (gid_t)-1 && !gid_valid(ids.gid)))
+ ids.err = -EINVAL;
+ return ids;
+}
+
+static int chown_ids_r(const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group,
+ struct chown_ids *ids)
+{
+ struct user_namespace *ns = current_user_ns();
+ int err;
+
+ ids->err = 0;
+ err = get_kuid_r(ns, user, &ids->uid);
+ if (!err)
+ err = get_kgid_r(ns, group, &ids->gid);
+ return err;
+}
+
+static int chown_path(const struct path *path, const struct chown_ids *ids)
{
struct mnt_idmap *idmap;
struct user_namespace *fs_userns;
@@ -744,11 +783,9 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
struct delegated_inode delegated_inode = { };
int error;
struct iattr newattrs;
- kuid_t uid;
- kgid_t gid;
- uid = make_kuid(current_user_ns(), user);
- gid = make_kgid(current_user_ns(), group);
+ if (ids->err)
+ return ids->err;
idmap = mnt_idmap(path->mnt);
fs_userns = i_user_ns(inode);
@@ -757,9 +794,9 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
newattrs.ia_vfsuid = INVALID_VFSUID;
newattrs.ia_vfsgid = INVALID_VFSGID;
newattrs.ia_valid = ATTR_CTIME;
- if ((user != (uid_t)-1) && !setattr_vfsuid(&newattrs, uid))
+ if (uid_valid(ids->uid) && !setattr_vfsuid(&newattrs, ids->uid))
return -EINVAL;
- if ((group != (gid_t)-1) && !setattr_vfsgid(&newattrs, gid))
+ if (gid_valid(ids->gid) && !setattr_vfsgid(&newattrs, ids->gid))
return -EINVAL;
error = inode_lock_killable(inode);
if (error)
@@ -784,8 +821,15 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
return error;
}
-int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
- int flag)
+int chown_common(const struct path *path, uid_t user, gid_t group)
+{
+ struct chown_ids ids = chown_ids_classic(user, group);
+
+ return chown_path(path, &ids);
+}
+
+static int fchownat_ids(int dfd, const char __user *filename,
+ const struct chown_ids *ids, int flag)
{
struct path path;
int error;
@@ -801,7 +845,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
if (!error) {
error = mnt_want_write(path.mnt);
if (!error) {
- error = chown_common(&path, user, group);
+ error = chown_path(&path, ids);
mnt_drop_write(path.mnt);
}
path_put(&path);
@@ -813,6 +857,14 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
return error;
}
+int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
+ int flag)
+{
+ struct chown_ids ids = chown_ids_classic(user, group);
+
+ return fchownat_ids(dfd, filename, &ids, flag);
+}
+
SYSCALL_DEFINE5(fchownat, int, dfd, const char __user *, filename, uid_t, user,
gid_t, group, int, flag)
{
@@ -830,7 +882,7 @@ SYSCALL_DEFINE3(lchown, const char __user *, filename, uid_t, user, gid_t, group
AT_SYMLINK_NOFOLLOW);
}
-int vfs_fchown(struct file *file, uid_t user, gid_t group)
+static int fchown_ids(struct file *file, const struct chown_ids *ids)
{
int error;
@@ -838,11 +890,18 @@ int vfs_fchown(struct file *file, uid_t user, gid_t group)
if (error)
return error;
audit_file(file);
- error = chown_common(&file->f_path, user, group);
+ error = chown_path(&file->f_path, ids);
mnt_drop_write_file(file);
return error;
}
+int vfs_fchown(struct file *file, uid_t user, gid_t group)
+{
+ struct chown_ids ids = chown_ids_classic(user, group);
+
+ return fchown_ids(file, &ids);
+}
+
int ksys_fchown(unsigned int fd, uid_t user, gid_t group)
{
CLASS(fd, f)(fd);
@@ -858,6 +917,67 @@ SYSCALL_DEFINE3(fchown, unsigned int, fd, uid_t, user, gid_t, group)
return ksys_fchown(fd, user, group);
}
+/*
+ * The *_r variants take __kernel_uid_r_t ids, which can also be UUIDs or
+ * GUIDs. A NULL pointer, UID_R_NONE or a classic -1 leave the owner or
+ * group unchanged.
+ */
+SYSCALL_DEFINE5(fchownat_r, int, dfd, const char __user *, filename,
+ const struct __kernel_uid_r __user *, user,
+ const struct __kernel_uid_r __user *, group, int, flag)
+{
+ struct chown_ids ids;
+ int error;
+
+ error = chown_ids_r(user, group, &ids);
+ if (error)
+ return error;
+ return fchownat_ids(dfd, filename, &ids, flag);
+}
+
+SYSCALL_DEFINE3(chown_r, const char __user *, filename,
+ const struct __kernel_uid_r __user *, user,
+ const struct __kernel_uid_r __user *, group)
+{
+ struct chown_ids ids;
+ int error;
+
+ error = chown_ids_r(user, group, &ids);
+ if (error)
+ return error;
+ return fchownat_ids(AT_FDCWD, filename, &ids, 0);
+}
+
+SYSCALL_DEFINE3(lchown_r, const char __user *, filename,
+ const struct __kernel_uid_r __user *, user,
+ const struct __kernel_uid_r __user *, group)
+{
+ struct chown_ids ids;
+ int error;
+
+ error = chown_ids_r(user, group, &ids);
+ if (error)
+ return error;
+ return fchownat_ids(AT_FDCWD, filename, &ids, AT_SYMLINK_NOFOLLOW);
+}
+
+SYSCALL_DEFINE3(fchown_r, unsigned int, fd,
+ const struct __kernel_uid_r __user *, user,
+ const struct __kernel_uid_r __user *, group)
+{
+ struct chown_ids ids;
+ int error;
+
+ error = chown_ids_r(user, group, &ids);
+ if (error)
+ return error;
+
+ CLASS(fd, f)(fd);
+ if (fd_empty(f))
+ return -EBADF;
+ return fchown_ids(fd_file(f), &ids);
+}
+
static inline int file_get_write_access(struct file *f)
{
int error;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 8e7b4e927..38d9cfa05 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -997,6 +997,19 @@ asmlinkage long sys_getgroups_r(int gidsetsize,
struct __kernel_uid_r __user *grouplist);
asmlinkage long sys_setgroups_r(int gidsetsize,
const struct __kernel_uid_r __user *grouplist);
+asmlinkage long sys_chown_r(const char __user *filename,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group);
+asmlinkage long sys_lchown_r(const char __user *filename,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group);
+asmlinkage long sys_fchown_r(unsigned int fd,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group);
+asmlinkage long sys_fchownat_r(int dfd, const char __user *filename,
+ const struct __kernel_uid_r __user *user,
+ const struct __kernel_uid_r __user *group,
+ int flag);
asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
asmlinkage long sys_open_tree_attr(int dfd, const char __user *path,
unsigned flags,
[-- Attachment #23: uid_r-0022-arch-wire-up-the-_r-uid-and-gid-system-calls.patch --]
[-- Type: text/x-patch, Size: 22493 bytes --]
From 1c5c7548a3a38582ad7e9127fa697189523cb4b3 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 05:46:05 +0200
Subject: [PATCH 22/43] arch: wire up the *_r uid and gid system calls
Assign 472-491 on all architectures (582-601 on alpha) to getuid_r,
geteuid_r, getgid_r, getegid_r, setuid_r, setgid_r, setreuid_r,
setregid_r, setresuid_r, getresuid_r, setresgid_r, getresgid_r,
setfsuid_r, setfsgid_r, getgroups_r, setgroups_r, chown_r, lchown_r,
fchown_r and fchownat_r.
__kernel_uid_r_t has the same layout on all ABIs, so the compat ABIs
use the native entry points.
Assisted-by: Claude:claude-opus-5-5
---
arch/alpha/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/arm/tools/syscall.tbl | 20 ++++++++++
arch/arm64/tools/syscall_32.tbl | 20 ++++++++++
arch/m68k/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/microblaze/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/mips/kernel/syscalls/syscall_n32.tbl | 20 ++++++++++
arch/mips/kernel/syscalls/syscall_n64.tbl | 20 ++++++++++
arch/mips/kernel/syscalls/syscall_o32.tbl | 20 ++++++++++
arch/parisc/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/powerpc/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/s390/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/sh/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/sparc/kernel/syscalls/syscall.tbl | 20 ++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 20 ++++++++++
arch/x86/entry/syscalls/syscall_64.tbl | 20 ++++++++++
arch/xtensa/kernel/syscalls/syscall.tbl | 20 ++++++++++
include/uapi/asm-generic/unistd.h | 42 ++++++++++++++++++++-
scripts/syscall.tbl | 20 ++++++++++
18 files changed, 381 insertions(+), 1 deletion(-)
diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl
index f31b7afff..272281891 100644
--- a/arch/alpha/kernel/syscalls/syscall.tbl
+++ b/arch/alpha/kernel/syscalls/syscall.tbl
@@ -511,3 +511,23 @@
579 common file_setattr sys_file_setattr
580 common listns sys_listns
581 common rseq_slice_yield sys_rseq_slice_yield
+582 common getuid_r sys_getuid_r
+583 common geteuid_r sys_geteuid_r
+584 common getgid_r sys_getgid_r
+585 common getegid_r sys_getegid_r
+586 common setuid_r sys_setuid_r
+587 common setgid_r sys_setgid_r
+588 common setreuid_r sys_setreuid_r
+589 common setregid_r sys_setregid_r
+590 common setresuid_r sys_setresuid_r
+591 common getresuid_r sys_getresuid_r
+592 common setresgid_r sys_setresgid_r
+593 common getresgid_r sys_getresgid_r
+594 common setfsuid_r sys_setfsuid_r
+595 common setfsgid_r sys_setfsgid_r
+596 common getgroups_r sys_getgroups_r
+597 common setgroups_r sys_setgroups_r
+598 common chown_r sys_chown_r
+599 common lchown_r sys_lchown_r
+600 common fchown_r sys_fchown_r
+601 common fchownat_r sys_fchownat_r
diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl
index 94351e22b..45273f44f 100644
--- a/arch/arm/tools/syscall.tbl
+++ b/arch/arm/tools/syscall.tbl
@@ -486,3 +486,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/arm64/tools/syscall_32.tbl b/arch/arm64/tools/syscall_32.tbl
index 62d93d88e..2f2fac533 100644
--- a/arch/arm64/tools/syscall_32.tbl
+++ b/arch/arm64/tools/syscall_32.tbl
@@ -483,3 +483,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl
index 248934257..bcfd36931 100644
--- a/arch/m68k/kernel/syscalls/syscall.tbl
+++ b/arch/m68k/kernel/syscalls/syscall.tbl
@@ -471,3 +471,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl
index 223d26303..915bc93ae 100644
--- a/arch/microblaze/kernel/syscalls/syscall.tbl
+++ b/arch/microblaze/kernel/syscalls/syscall.tbl
@@ -477,3 +477,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl
index 7430714e2..217333bb8 100644
--- a/arch/mips/kernel/syscalls/syscall_n32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n32.tbl
@@ -410,3 +410,23 @@
469 n32 file_setattr sys_file_setattr
470 n32 listns sys_listns
471 n32 rseq_slice_yield sys_rseq_slice_yield
+472 n32 getuid_r sys_getuid_r
+473 n32 geteuid_r sys_geteuid_r
+474 n32 getgid_r sys_getgid_r
+475 n32 getegid_r sys_getegid_r
+476 n32 setuid_r sys_setuid_r
+477 n32 setgid_r sys_setgid_r
+478 n32 setreuid_r sys_setreuid_r
+479 n32 setregid_r sys_setregid_r
+480 n32 setresuid_r sys_setresuid_r
+481 n32 getresuid_r sys_getresuid_r
+482 n32 setresgid_r sys_setresgid_r
+483 n32 getresgid_r sys_getresgid_r
+484 n32 setfsuid_r sys_setfsuid_r
+485 n32 setfsgid_r sys_setfsgid_r
+486 n32 getgroups_r sys_getgroups_r
+487 n32 setgroups_r sys_setgroups_r
+488 n32 chown_r sys_chown_r
+489 n32 lchown_r sys_lchown_r
+490 n32 fchown_r sys_fchown_r
+491 n32 fchownat_r sys_fchownat_r
diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl
index 630aab9e5..32016b1eb 100644
--- a/arch/mips/kernel/syscalls/syscall_n64.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n64.tbl
@@ -386,3 +386,23 @@
469 n64 file_setattr sys_file_setattr
470 n64 listns sys_listns
471 n64 rseq_slice_yield sys_rseq_slice_yield
+472 n64 getuid_r sys_getuid_r
+473 n64 geteuid_r sys_geteuid_r
+474 n64 getgid_r sys_getgid_r
+475 n64 getegid_r sys_getegid_r
+476 n64 setuid_r sys_setuid_r
+477 n64 setgid_r sys_setgid_r
+478 n64 setreuid_r sys_setreuid_r
+479 n64 setregid_r sys_setregid_r
+480 n64 setresuid_r sys_setresuid_r
+481 n64 getresuid_r sys_getresuid_r
+482 n64 setresgid_r sys_setresgid_r
+483 n64 getresgid_r sys_getresgid_r
+484 n64 setfsuid_r sys_setfsuid_r
+485 n64 setfsgid_r sys_setfsgid_r
+486 n64 getgroups_r sys_getgroups_r
+487 n64 setgroups_r sys_setgroups_r
+488 n64 chown_r sys_chown_r
+489 n64 lchown_r sys_lchown_r
+490 n64 fchown_r sys_fchown_r
+491 n64 fchownat_r sys_fchownat_r
diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl
index 128653112..baba1c0e6 100644
--- a/arch/mips/kernel/syscalls/syscall_o32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_o32.tbl
@@ -459,3 +459,23 @@
469 o32 file_setattr sys_file_setattr
470 o32 listns sys_listns
471 o32 rseq_slice_yield sys_rseq_slice_yield
+472 o32 getuid_r sys_getuid_r
+473 o32 geteuid_r sys_geteuid_r
+474 o32 getgid_r sys_getgid_r
+475 o32 getegid_r sys_getegid_r
+476 o32 setuid_r sys_setuid_r
+477 o32 setgid_r sys_setgid_r
+478 o32 setreuid_r sys_setreuid_r
+479 o32 setregid_r sys_setregid_r
+480 o32 setresuid_r sys_setresuid_r
+481 o32 getresuid_r sys_getresuid_r
+482 o32 setresgid_r sys_setresgid_r
+483 o32 getresgid_r sys_getresgid_r
+484 o32 setfsuid_r sys_setfsuid_r
+485 o32 setfsgid_r sys_setfsgid_r
+486 o32 getgroups_r sys_getgroups_r
+487 o32 setgroups_r sys_setgroups_r
+488 o32 chown_r sys_chown_r
+489 o32 lchown_r sys_lchown_r
+490 o32 fchown_r sys_fchown_r
+491 o32 fchownat_r sys_fchownat_r
diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl
index c6331dad9..c21ae0689 100644
--- a/arch/parisc/kernel/syscalls/syscall.tbl
+++ b/arch/parisc/kernel/syscalls/syscall.tbl
@@ -470,3 +470,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl
index 4fcc7c58a..29fcf6b29 100644
--- a/arch/powerpc/kernel/syscalls/syscall.tbl
+++ b/arch/powerpc/kernel/syscalls/syscall.tbl
@@ -562,3 +562,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 nospu rseq_slice_yield sys_rseq_slice_yield
+472 nospu getuid_r sys_getuid_r
+473 nospu geteuid_r sys_geteuid_r
+474 nospu getgid_r sys_getgid_r
+475 nospu getegid_r sys_getegid_r
+476 nospu setuid_r sys_setuid_r
+477 nospu setgid_r sys_setgid_r
+478 nospu setreuid_r sys_setreuid_r
+479 nospu setregid_r sys_setregid_r
+480 nospu setresuid_r sys_setresuid_r
+481 nospu getresuid_r sys_getresuid_r
+482 nospu setresgid_r sys_setresgid_r
+483 nospu getresgid_r sys_getresgid_r
+484 nospu setfsuid_r sys_setfsuid_r
+485 nospu setfsgid_r sys_setfsgid_r
+486 nospu getgroups_r sys_getgroups_r
+487 nospu setgroups_r sys_setgroups_r
+488 nospu chown_r sys_chown_r
+489 nospu lchown_r sys_lchown_r
+490 nospu fchown_r sys_fchown_r
+491 nospu fchownat_r sys_fchownat_r
diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl
index 09a7ef04d..4199958e7 100644
--- a/arch/s390/kernel/syscalls/syscall.tbl
+++ b/arch/s390/kernel/syscalls/syscall.tbl
@@ -398,3 +398,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl
index 70b315cbe..b5ed5b657 100644
--- a/arch/sh/kernel/syscalls/syscall.tbl
+++ b/arch/sh/kernel/syscalls/syscall.tbl
@@ -475,3 +475,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index 7e71bf7fc..07e143706 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -517,3 +517,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index f832ebd2d..f3890beb0 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -477,3 +477,23 @@
469 i386 file_setattr sys_file_setattr
470 i386 listns sys_listns
471 i386 rseq_slice_yield sys_rseq_slice_yield
+472 i386 getuid_r sys_getuid_r
+473 i386 geteuid_r sys_geteuid_r
+474 i386 getgid_r sys_getgid_r
+475 i386 getegid_r sys_getegid_r
+476 i386 setuid_r sys_setuid_r
+477 i386 setgid_r sys_setgid_r
+478 i386 setreuid_r sys_setreuid_r
+479 i386 setregid_r sys_setregid_r
+480 i386 setresuid_r sys_setresuid_r
+481 i386 getresuid_r sys_getresuid_r
+482 i386 setresgid_r sys_setresgid_r
+483 i386 getresgid_r sys_getresgid_r
+484 i386 setfsuid_r sys_setfsuid_r
+485 i386 setfsgid_r sys_setfsgid_r
+486 i386 getgroups_r sys_getgroups_r
+487 i386 setgroups_r sys_setgroups_r
+488 i386 chown_r sys_chown_r
+489 i386 lchown_r sys_lchown_r
+490 i386 fchown_r sys_fchown_r
+491 i386 fchownat_r sys_fchownat_r
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 524155d65..86a0fc606 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -396,6 +396,26 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
#
# Due to a historical design error, certain syscalls are numbered differently
diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl
index a9bca4e48..5cdc5d0c3 100644
--- a/arch/xtensa/kernel/syscalls/syscall.tbl
+++ b/arch/xtensa/kernel/syscalls/syscall.tbl
@@ -442,3 +442,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index a627acc8f..8f8b88e35 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -862,9 +862,49 @@ __SYSCALL(__NR_listns, sys_listns)
#define __NR_rseq_slice_yield 471
__SYSCALL(__NR_rseq_slice_yield, sys_rseq_slice_yield)
+#define __NR_getuid_r 472
+__SYSCALL(__NR_getuid_r, sys_getuid_r)
+#define __NR_geteuid_r 473
+__SYSCALL(__NR_geteuid_r, sys_geteuid_r)
+#define __NR_getgid_r 474
+__SYSCALL(__NR_getgid_r, sys_getgid_r)
+#define __NR_getegid_r 475
+__SYSCALL(__NR_getegid_r, sys_getegid_r)
+#define __NR_setuid_r 476
+__SYSCALL(__NR_setuid_r, sys_setuid_r)
+#define __NR_setgid_r 477
+__SYSCALL(__NR_setgid_r, sys_setgid_r)
+#define __NR_setreuid_r 478
+__SYSCALL(__NR_setreuid_r, sys_setreuid_r)
+#define __NR_setregid_r 479
+__SYSCALL(__NR_setregid_r, sys_setregid_r)
+#define __NR_setresuid_r 480
+__SYSCALL(__NR_setresuid_r, sys_setresuid_r)
+#define __NR_getresuid_r 481
+__SYSCALL(__NR_getresuid_r, sys_getresuid_r)
+#define __NR_setresgid_r 482
+__SYSCALL(__NR_setresgid_r, sys_setresgid_r)
+#define __NR_getresgid_r 483
+__SYSCALL(__NR_getresgid_r, sys_getresgid_r)
+#define __NR_setfsuid_r 484
+__SYSCALL(__NR_setfsuid_r, sys_setfsuid_r)
+#define __NR_setfsgid_r 485
+__SYSCALL(__NR_setfsgid_r, sys_setfsgid_r)
+#define __NR_getgroups_r 486
+__SYSCALL(__NR_getgroups_r, sys_getgroups_r)
+#define __NR_setgroups_r 487
+__SYSCALL(__NR_setgroups_r, sys_setgroups_r)
+#define __NR_chown_r 488
+__SYSCALL(__NR_chown_r, sys_chown_r)
+#define __NR_lchown_r 489
+__SYSCALL(__NR_lchown_r, sys_lchown_r)
+#define __NR_fchown_r 490
+__SYSCALL(__NR_fchown_r, sys_fchown_r)
+#define __NR_fchownat_r 491
+__SYSCALL(__NR_fchownat_r, sys_fchownat_r)
#undef __NR_syscalls
-#define __NR_syscalls 472
+#define __NR_syscalls 492
/*
* 32 bit systems traditionally used different
diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
index 7a42b32b6..62d8543c1 100644
--- a/scripts/syscall.tbl
+++ b/scripts/syscall.tbl
@@ -412,3 +412,23 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common getuid_r sys_getuid_r
+473 common geteuid_r sys_geteuid_r
+474 common getgid_r sys_getgid_r
+475 common getegid_r sys_getegid_r
+476 common setuid_r sys_setuid_r
+477 common setgid_r sys_setgid_r
+478 common setreuid_r sys_setreuid_r
+479 common setregid_r sys_setregid_r
+480 common setresuid_r sys_setresuid_r
+481 common getresuid_r sys_getresuid_r
+482 common setresgid_r sys_setresgid_r
+483 common getresgid_r sys_getresgid_r
+484 common setfsuid_r sys_setfsuid_r
+485 common setfsgid_r sys_setfsgid_r
+486 common getgroups_r sys_getgroups_r
+487 common setgroups_r sys_setgroups_r
+488 common chown_r sys_chown_r
+489 common lchown_r sys_lchown_r
+490 common fchown_r sys_fchown_r
+491 common fchownat_r sys_fchownat_r
[-- Attachment #24: uid_r-0023-statx-report-UUID-and-GUID-owners.patch --]
[-- Type: text/x-patch, Size: 4105 bytes --]
From 4e4523d671c6ea6eafe7b4dd31e0f9ff468b3381 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:01:52 +0200
Subject: [PATCH 23/43] statx: report UUID and GUID owners
Add stx_uid_r and stx_gid_r, the owner and group as __kernel_uid_r_t,
taking 40 of the 64 spare bytes at the end of struct statx, and
STATX_UID_R and STATX_GID_R for them. Every filesystem provides them,
from the kstat the VFS has anyway. stx_uid and stx_gid keep showing a
UUID or GUID as the overflow id.
struct statx is designed to be extended this way, so there is no new
system call: statx() with the new mask bits is the *_r variant.
Assisted-by: Claude:claude-opus-5-5
---
fs/stat.c | 24 +++++++++++++++++++++++-
include/uapi/linux/stat.h | 10 +++++++++-
2 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/fs/stat.c b/fs/stat.c
index c461c3054..993ca4907 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -185,7 +185,7 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
struct inode *inode = d_backing_inode(path->dentry);
memset(stat, 0, sizeof(*stat));
- stat->result_mask |= STATX_BASIC_STATS;
+ stat->result_mask |= STATX_BASIC_STATS | STATX_UID_R | STATX_GID_R;
query_flags &= AT_STATX_SYNC_TYPE;
/* allow the fs to override these if it really wants to */
@@ -206,6 +206,17 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT |
STATX_ATTR_DAX);
+ /*
+ * STATX_UID_R and STATX_GID_R are another view of the owner: ask the
+ * filesystem for STATX_UID and STATX_GID, and report them whenever
+ * those are valid, also from a ->getattr() that sets result_mask on
+ * its own.
+ */
+ if (request_mask & STATX_UID_R)
+ request_mask |= STATX_UID;
+ if (request_mask & STATX_GID_R)
+ request_mask |= STATX_GID;
+
idmap = mnt_idmap(path->mnt);
if (inode->i_op->getattr) {
int ret;
@@ -218,6 +229,15 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
generic_fillattr(idmap, request_mask, inode, stat);
}
+ if (stat->result_mask & STATX_UID)
+ stat->result_mask |= STATX_UID_R;
+ else
+ stat->result_mask &= ~STATX_UID_R;
+ if (stat->result_mask & STATX_GID)
+ stat->result_mask |= STATX_GID_R;
+ else
+ stat->result_mask &= ~STATX_GID_R;
+
/*
* If this is a block device inode, override the filesystem attributes
* with the block device specific parameters that need to be obtained
@@ -711,6 +731,8 @@ cp_statx(const struct kstat *stat, struct statx __user *buffer)
tmp.stx_nlink = stat->nlink;
tmp.stx_uid = from_kuid_munged(current_user_ns(), stat->uid);
tmp.stx_gid = from_kgid_munged(current_user_ns(), stat->gid);
+ from_kuid_r_munged(current_user_ns(), stat->uid, &tmp.stx_uid_r);
+ from_kgid_r_munged(current_user_ns(), stat->gid, &tmp.stx_gid_r);
tmp.stx_mode = stat->mode;
tmp.stx_ino = stat->ino;
tmp.stx_size = stat->size;
diff --git a/include/uapi/linux/stat.h b/include/uapi/linux/stat.h
index 1686861aa..689757d8a 100644
--- a/include/uapi/linux/stat.h
+++ b/include/uapi/linux/stat.h
@@ -3,6 +3,7 @@
#define _UAPI_LINUX_STAT_H
#include <linux/types.h>
+#include <linux/uidgid_r.h>
#if defined(__KERNEL__) || !defined(__GLIBC__) || (__GLIBC__ < 2)
@@ -187,7 +188,12 @@ struct statx {
__u32 __spare2[1];
/* 0xc0 */
- __u64 __spare3[8]; /* Spare space for future expansion */
+ /* Owner and group, also UUIDs and GUIDs [STATX_UID_R, STATX_GID_R] */
+ __kernel_uid_r_t stx_uid_r;
+ __kernel_gid_r_t stx_gid_r;
+
+ /* 0xe8 */
+ __u64 __spare3[3]; /* Spare space for future expansion */
/* 0x100 */
};
@@ -219,6 +225,8 @@ struct statx {
#define STATX_SUBVOL 0x00008000U /* Want/got stx_subvol */
#define STATX_WRITE_ATOMIC 0x00010000U /* Want/got atomic_write_* fields */
#define STATX_DIO_READ_ALIGN 0x00020000U /* Want/got dio read alignment info */
+#define STATX_UID_R 0x00040000U /* Want/got stx_uid_r */
+#define STATX_GID_R 0x00080000U /* Want/got stx_gid_r */
#define STATX__RESERVED 0x80000000U /* Reserved for future struct statx expansion */
[-- Attachment #25: uid_r-0024-shmem-ramfs-allow-UUID-and-GUID-owners.patch --]
[-- Type: text/x-patch, Size: 1373 bytes --]
From 29109d2223e9f7d212f0523442bd187a730b9bc4 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:01:52 +0200
Subject: [PATCH 24/43] shmem, ramfs: allow UUID and GUID owners
Both keep inode owners in memory only, so they can hold any kuid_t.
Set SB_I_UID_R. Quotas of tmpfs don't account such owners.
Assisted-by: Claude:claude-opus-5-5
---
fs/ramfs/inode.c | 1 +
mm/shmem.c | 2 ++
2 files changed, 3 insertions(+)
diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c
index 3987639ed..604349dbd 100644
--- a/fs/ramfs/inode.c
+++ b/fs/ramfs/inode.c
@@ -269,6 +269,7 @@ static int ramfs_fill_super(struct super_block *sb, struct fs_context *fc)
sb->s_op = &ramfs_ops;
sb->s_d_flags = DCACHE_DONTCACHE;
sb->s_time_gran = 1;
+ sb->s_iflags |= SB_I_UID_R;
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
sb->s_root = d_make_root(inode);
diff --git a/mm/shmem.c b/mm/shmem.c
index cfb546785..f6c60d7d3 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -4963,6 +4963,8 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
}
sb->s_export_op = &shmem_export_ops;
sb->s_flags |= SB_NOSEC;
+ /* owners live in the inode only: UUIDs and GUIDs need no format */
+ sb->s_iflags |= SB_I_UID_R;
#if IS_ENABLED(CONFIG_UNICODE)
if (!ctx->encoding && ctx->strict_encoding) {
[-- Attachment #26: uid_r-0025-uidgid-parse-and-print-ids-in-their-text-form.patch --]
[-- Type: text/x-patch, Size: 7715 bytes --]
From 9e46d0c433624ac3aa474a2f4b9e12dbe186aafa Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:14:40 +0200
Subject: [PATCH 25/43] uidgid: parse and print ids in their text form
Add helpers for interfaces that pass ids as text:
- make_kuid_str()/make_kgid_str() parse what kuid_str() writes: a
decimal number, a UUID, or a GUID in braces,
- kuid_str_munged()/kgid_str_munged() write an id as seen from a user
namespace, the overflow id if it has no mapping there,
- kuid_str_signed()/kgid_str_signed() write a legacy id as a signed
number, for output that used "%d",
- KUID_STR() and friends wrap them as expressions with a buffer of the
enclosing block, so that printf-like calls can replace
"%u", from_kuid(&init_user_ns, uid) by "%s", KUID_STR(uid) and print
legacy ids exactly as before.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/uidgid.h | 19 ++++++
kernel/uidgid_r.c | 152 +++++++++++++++++++++++++++++++++++++++--
2 files changed, 167 insertions(+), 4 deletions(-)
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index ac7786f76..03ece8a4c 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -190,6 +190,25 @@ static inline u32 kgid_hash(kgid_t gid)
char *kuid_str(kuid_t uid, char buf[KID_STR_LEN]);
char *kgid_str(kgid_t gid, char buf[KID_STR_LEN]);
+char *kuid_str_signed(kuid_t uid, char buf[KID_STR_LEN]);
+char *kgid_str_signed(kgid_t gid, char buf[KID_STR_LEN]);
+
+/*
+ * The text form of an id as an expression, in a buffer that lives until
+ * the end of the enclosing block -- for "%s" in place of "%u" (or "%d",
+ * with the _D variants, which write a legacy id as a signed number) of
+ * from_kuid(&init_user_ns, uid) in printf-like calls.
+ */
+#define KUID_STR(uid) kuid_str(uid, (char [KID_STR_LEN]) {})
+#define KGID_STR(gid) kgid_str(gid, (char [KID_STR_LEN]) {})
+#define KUID_STR_D(uid) kuid_str_signed(uid, (char [KID_STR_LEN]) {})
+#define KGID_STR_D(gid) kgid_str_signed(gid, (char [KID_STR_LEN]) {})
+char *kuid_str_munged(struct user_namespace *to, kuid_t uid,
+ char buf[KID_STR_LEN]);
+char *kgid_str_munged(struct user_namespace *to, kgid_t gid,
+ char buf[KID_STR_LEN]);
+kuid_t make_kuid_str(struct user_namespace *from, const char *str);
+kgid_t make_kgid_str(struct user_namespace *from, const char *str);
#ifdef CONFIG_USER_NS
diff --git a/kernel/uidgid_r.c b/kernel/uidgid_r.c
index d71aecddb..9bba60f41 100644
--- a/kernel/uidgid_r.c
+++ b/kernel/uidgid_r.c
@@ -12,6 +12,7 @@
#include <linux/uidgid.h>
#include <linux/uidgid_r.h>
#include <linux/user_namespace.h>
+#include <linux/uuid.h>
/*
* Whether @id is well formed: a known type, reserved bytes zero, a UUID of
@@ -278,11 +279,12 @@ int put_kgid_r(struct user_namespace *to, kgid_t kgid,
}
EXPORT_SYMBOL(put_kgid_r);
-static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
+static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf,
+ bool sgn)
{
switch (type) {
case KID_LEGACY:
- snprintf(buf, KID_STR_LEN, "%u", legacy);
+ snprintf(buf, KID_STR_LEN, sgn ? "%d" : "%u", legacy);
break;
case KID_UUID:
snprintf(buf, KID_STR_LEN, "%pUb", b);
@@ -304,7 +306,7 @@ static char *kid_str(u32 type, u32 legacy, const uuid_t *b, char *buf)
*/
char *kuid_str(kuid_t uid, char buf[KID_STR_LEN])
{
- return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf);
+ return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf, false);
}
EXPORT_SYMBOL(kuid_str);
@@ -318,6 +320,148 @@ EXPORT_SYMBOL(kuid_str);
*/
char *kgid_str(kgid_t gid, char buf[KID_STR_LEN])
{
- return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf);
+ return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf, false);
}
EXPORT_SYMBOL(kgid_str);
+
+/**
+ * kuid_str_signed - like kuid_str(), but a legacy uid as a signed number
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * For callers that used to print from_kuid() with "%d".
+ *
+ * Return: @buf.
+ */
+char *kuid_str_signed(kuid_t uid, char buf[KID_STR_LEN])
+{
+ return kid_str(uid.type, __kuid_val(uid), &uid.uuid, buf, true);
+}
+EXPORT_SYMBOL(kuid_str_signed);
+
+/**
+ * kgid_str_signed - like kgid_str(), but a legacy gid as a signed number
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * For callers that used to print from_kgid() with "%d".
+ *
+ * Return: @buf.
+ */
+char *kgid_str_signed(kgid_t gid, char buf[KID_STR_LEN])
+{
+ return kid_str(gid.type, __kgid_val(gid), &gid.uuid, buf, true);
+}
+EXPORT_SYMBOL(kgid_str_signed);
+
+/*
+ * Parses the text forms of kuid_str() -- a decimal number, a UUID, a GUID
+ * in braces -- with an optional trailing newline.
+ */
+static int kid_r_from_str(const char *str, struct __kernel_uid_r *id)
+{
+ char buf[KID_STR_LEN];
+ size_t len = strlen(str);
+
+ if (len && str[len - 1] == '\n')
+ len--;
+ if (!len || len >= sizeof(buf))
+ return -EINVAL;
+ memcpy(buf, str, len);
+ buf[len] = '\0';
+ memset(id, 0, sizeof(*id));
+
+ if (len == UUID_STRING_LEN) {
+ id->type = UID_R_UUID;
+ return uuid_parse(buf, (uuid_t *)id->uuid);
+ }
+ if (len == UUID_STRING_LEN + 2 && buf[0] == '{' && buf[len - 1] == '}') {
+ buf[len - 1] = '\0';
+ id->type = UID_R_GUID;
+ return guid_parse(buf + 1, (guid_t *)id->guid);
+ }
+ id->type = UID_R_LEGACY;
+ return kstrtou32(buf, 10, &id->legacy);
+}
+
+/**
+ * make_kuid_str - map the text form of a uid into a kuid
+ * @from: user namespace the uid is in
+ * @str: a decimal number, a UUID, or a GUID in braces; a trailing newline
+ * is allowed
+ *
+ * Return: the kuid, INVALID_UID if @str is malformed or has no mapping in
+ * @from.
+ */
+kuid_t make_kuid_str(struct user_namespace *from, const char *str)
+{
+ struct __kernel_uid_r id;
+
+ if (kid_r_from_str(str, &id))
+ return INVALID_UID;
+ return make_kuid_r(from, &id);
+}
+EXPORT_SYMBOL(make_kuid_str);
+
+/**
+ * make_kgid_str - map the text form of a gid into a kgid
+ * @from: user namespace the gid is in
+ * @str: a decimal number, a UUID, or a GUID in braces; a trailing newline
+ * is allowed
+ *
+ * Return: the kgid, INVALID_GID if @str is malformed or has no mapping in
+ * @from.
+ */
+kgid_t make_kgid_str(struct user_namespace *from, const char *str)
+{
+ struct __kernel_uid_r id;
+
+ if (kid_r_from_str(str, &id))
+ return INVALID_GID;
+ return make_kgid_r(from, &id);
+}
+EXPORT_SYMBOL(make_kgid_str);
+
+/**
+ * kuid_str_munged - text form of a kuid in a user namespace
+ * @to: user namespace the uid is wanted in
+ * @uid: the kuid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding the uid in @to like kuid_str() does, overflowuid if
+ * @uid has no mapping there.
+ */
+char *kuid_str_munged(struct user_namespace *to, kuid_t uid,
+ char buf[KID_STR_LEN])
+{
+ if (!kuid_has_mapping(to, uid))
+ snprintf(buf, KID_STR_LEN, "%u", overflowuid);
+ else if (uid_is_legacy(uid))
+ snprintf(buf, KID_STR_LEN, "%u", from_kuid(to, uid));
+ else
+ kuid_str(uid, buf);
+ return buf;
+}
+EXPORT_SYMBOL(kuid_str_munged);
+
+/**
+ * kgid_str_munged - text form of a kgid in a user namespace
+ * @to: user namespace the gid is wanted in
+ * @gid: the kgid
+ * @buf: buffer of KID_STR_LEN bytes
+ *
+ * Return: @buf, holding the gid in @to like kgid_str() does, overflowgid if
+ * @gid has no mapping there.
+ */
+char *kgid_str_munged(struct user_namespace *to, kgid_t gid,
+ char buf[KID_STR_LEN])
+{
+ if (!kgid_has_mapping(to, gid))
+ snprintf(buf, KID_STR_LEN, "%u", overflowgid);
+ else if (gid_is_legacy(gid))
+ snprintf(buf, KID_STR_LEN, "%u", from_kgid(to, gid));
+ else
+ kgid_str(gid, buf);
+ return buf;
+}
+EXPORT_SYMBOL(kgid_str_munged);
[-- Attachment #27: uid_r-0026-proc-show-UUID-and-GUID-ids-in-status-accept-them-fo.patch --]
[-- Type: text/x-patch, Size: 4910 bytes --]
From 94accd00741ae112870e5fa3cb2130abe7cf8dd3 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:14:41 +0200
Subject: [PATCH 26/43] proc: show UUID and GUID ids in status, accept them for
loginuid
/proc/<pid>/status shows ids without a 32 bit form as the overflow id.
Add Uid_r:, Gid_r: and Groups_r: lines with the same ids as text
(kuid_str()): a number, a UUID, or a GUID in braces, munged as usual
for ids the reader's user namespace can't see.
/proc/<pid>/loginuid accepts the text form of a UUID or GUID, so login
services can record such users, and shows such a loginuid as text.
Classic loginuids read and write as before, including 4294967295 for
"unset".
Assisted-by: Claude:claude-opus-5-5
---
fs/proc/array.c | 17 ++++++++++++++++-
fs/proc/base.c | 38 ++++++++++++++++++++++++--------------
2 files changed, 40 insertions(+), 15 deletions(-)
diff --git a/fs/proc/array.c b/fs/proc/array.c
index 479ea8cb4..ff61ab1bc 100644
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -156,6 +156,7 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
const struct cred *cred;
pid_t ppid, tpid = 0, tgid, ngid;
unsigned int max_fds = 0;
+ char idbuf[KID_STR_LEN];
rcu_read_lock();
tracer = ptrace_parent(p);
@@ -200,10 +201,24 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
for (g = 0; g < group_info->ngroups; g++)
seq_put_decimal_ull(m, g ? " " : "",
from_kgid_munged(user_ns, group_info->gid[g]));
- put_cred(cred);
/* Trailing space shouldn't have been added in the first place. */
seq_putc(m, ' ');
+ /* The same ids including UUIDs and GUIDs, as kuid_str() writes them */
+ seq_printf(m, "\nUid_r:\t%s", kuid_str_munged(user_ns, cred->uid, idbuf));
+ seq_printf(m, "\t%s", kuid_str_munged(user_ns, cred->euid, idbuf));
+ seq_printf(m, "\t%s", kuid_str_munged(user_ns, cred->suid, idbuf));
+ seq_printf(m, "\t%s", kuid_str_munged(user_ns, cred->fsuid, idbuf));
+ seq_printf(m, "\nGid_r:\t%s", kgid_str_munged(user_ns, cred->gid, idbuf));
+ seq_printf(m, "\t%s", kgid_str_munged(user_ns, cred->egid, idbuf));
+ seq_printf(m, "\t%s", kgid_str_munged(user_ns, cred->sgid, idbuf));
+ seq_printf(m, "\t%s", kgid_str_munged(user_ns, cred->fsgid, idbuf));
+ seq_puts(m, "\nGroups_r:\t");
+ for (g = 0; g < group_info->ngroups; g++)
+ seq_printf(m, "%s%s", g ? " " : "",
+ kgid_str_munged(user_ns, group_info->gid[g], idbuf));
+ put_cred(cred);
+
#ifdef CONFIG_PID_NS
seq_puts(m, "\nNStgid:");
for (g = ns->level; g <= pid->level; g++)
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 780f81259..b7f9fc83e 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1290,20 +1290,26 @@ static const struct file_operations proc_oom_score_adj_operations = {
};
#ifdef CONFIG_AUDIT
-#define TMPBUFLEN 11
+#define TMPBUFLEN KID_STR_LEN
static ssize_t proc_loginuid_read(struct file * file, char __user * buf,
size_t count, loff_t *ppos)
{
struct inode * inode = file_inode(file);
struct task_struct *task = get_proc_task(inode);
+ struct user_namespace *ns = file->f_cred->user_ns;
ssize_t length;
char tmpbuf[TMPBUFLEN];
+ kuid_t loginuid;
if (!task)
return -ESRCH;
- length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
- from_kuid(file->f_cred->user_ns,
- audit_get_loginuid(task)));
+ loginuid = audit_get_loginuid(task);
+ /* a UUID or GUID loginuid as text, if @ns can see it */
+ if (!uid_is_legacy(loginuid) && kuid_has_mapping(ns, loginuid))
+ length = strlen(kuid_str(loginuid, tmpbuf));
+ else
+ length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
+ from_kuid(ns, loginuid));
put_task_struct(task);
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
}
@@ -1312,7 +1318,7 @@ static ssize_t proc_loginuid_write(struct file * file, const char __user * buf,
size_t count, loff_t *ppos)
{
struct inode * inode = file_inode(file);
- uid_t loginuid;
+ char tmpbuf[TMPBUFLEN];
kuid_t kloginuid;
int rv;
@@ -1332,16 +1338,20 @@ static ssize_t proc_loginuid_write(struct file * file, const char __user * buf,
return -EINVAL;
}
- rv = kstrtou32_from_user(buf, count, 10, &loginuid);
- if (rv < 0)
- return rv;
+ /* a decimal uid, a UUID or a GUID in braces */
+ if (count >= sizeof(tmpbuf))
+ return -EINVAL;
+ if (copy_from_user(tmpbuf, buf, count))
+ return -EFAULT;
+ tmpbuf[count] = '\0';
- /* is userspace tring to explicitly UNSET the loginuid? */
- if (loginuid == AUDIT_UID_UNSET) {
- kloginuid = INVALID_UID;
- } else {
- kloginuid = make_kuid(file->f_cred->user_ns, loginuid);
- if (!uid_valid(kloginuid))
+ kloginuid = make_kuid_str(file->f_cred->user_ns, tmpbuf);
+ if (!uid_valid(kloginuid)) {
+ u32 loginuid;
+
+ /* is userspace tring to explicitly UNSET the loginuid? */
+ if (kstrtou32(tmpbuf, 10, &loginuid) ||
+ loginuid != AUDIT_UID_UNSET)
return -EINVAL;
}
[-- Attachment #28: uid_r-0027-audit-log-UUID-and-GUID-ids-in-full.patch --]
[-- Type: text/x-patch, Size: 17259 bytes --]
From 729596c48d80aea72214439e0dc2d53acd6e42f3 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:14:42 +0200
Subject: [PATCH 27/43] audit: log UUID and GUID ids in full
Audit records print ids with from_kuid(&init_user_ns, ...), which gives
-1 for a UUID or GUID. 4294967295 is what audit uses for "unset", so a
UUID user's actions would be logged as if there were no login user.
Print all ids in audit records with KUID_STR()/KGID_STR(): classic ids
come out exactly as before (signed where the format was "%d"), UUIDs and
GUIDs in their text form. The binary AUDIT_SIGNAL_INFO reply keeps its
32 bit field.
Assisted-by: Claude:claude-opus-5-5
---
drivers/tty/tty_audit.c | 7 ++--
kernel/audit.c | 50 ++++++++++++-------------
kernel/auditsc.c | 55 ++++++++++++++--------------
net/core/dev.c | 8 ++--
net/netlabel/netlabel_user.c | 4 +-
security/apparmor/file.c | 8 ++--
security/integrity/integrity_audit.c | 6 +--
security/ipe/audit.c | 12 +++---
security/selinux/selinuxfs.c | 8 ++--
security/selinux/ss/services.c | 4 +-
10 files changed, 78 insertions(+), 84 deletions(-)
diff --git a/drivers/tty/tty_audit.c b/drivers/tty/tty_audit.c
index d014af6ab..03ad4961b 100644
--- a/drivers/tty/tty_audit.c
+++ b/drivers/tty/tty_audit.c
@@ -65,8 +65,6 @@ static void tty_audit_log(const char *description, dev_t dev,
{
struct audit_buffer *ab;
pid_t pid = task_pid_nr(current);
- uid_t uid = from_kuid(&init_user_ns, task_uid(current));
- uid_t loginuid = from_kuid(&init_user_ns, audit_get_loginuid(current));
unsigned int sessionid = audit_get_sessionid(current);
char name[TASK_COMM_LEN];
@@ -74,8 +72,9 @@ static void tty_audit_log(const char *description, dev_t dev,
if (!ab)
return;
- audit_log_format(ab, "%s pid=%u uid=%u auid=%u ses=%u major=%d minor=%d comm=",
- description, pid, uid, loginuid, sessionid,
+ audit_log_format(ab, "%s pid=%u uid=%s auid=%s ses=%u major=%d minor=%d comm=",
+ description, pid, KUID_STR(task_uid(current)),
+ KUID_STR(audit_get_loginuid(current)), sessionid,
MAJOR(dev), MINOR(dev));
get_task_comm(name, current);
audit_log_untrustedstring(ab, name);
diff --git a/kernel/audit.c b/kernel/audit.c
index 9412af914..ed4ac5c88 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1128,7 +1128,6 @@ static int audit_netlink_ok(struct sk_buff *skb, u16 msg_type)
static void audit_log_common_recv_msg(struct audit_context *context,
struct audit_buffer **ab, u16 msg_type)
{
- uid_t uid = from_kuid(&init_user_ns, current_uid());
pid_t pid = task_tgid_nr(current);
if (!audit_enabled && msg_type != AUDIT_USER_AVC) {
@@ -1139,7 +1138,7 @@ static void audit_log_common_recv_msg(struct audit_context *context,
*ab = audit_log_start(context, GFP_KERNEL, msg_type);
if (unlikely(!*ab))
return;
- audit_log_format(*ab, "pid=%d uid=%u ", pid, uid);
+ audit_log_format(*ab, "pid=%d uid=%s ", pid, KUID_STR(current_uid()));
audit_log_session_info(*ab);
audit_log_task_context(*ab);
}
@@ -1658,10 +1657,10 @@ static void audit_log_multicast(int group, const char *op, int err)
cred = current_cred();
tty = audit_get_tty();
- audit_log_format(ab, "pid=%u uid=%u auid=%u tty=%s ses=%u",
+ audit_log_format(ab, "pid=%u uid=%s auid=%s tty=%s ses=%u",
task_tgid_nr(current),
- from_kuid(&init_user_ns, cred->uid),
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ KUID_STR(cred->uid),
+ KUID_STR(audit_get_loginuid(current)),
tty ? tty_name(tty) : "(none)",
audit_get_sessionid(current));
audit_put_tty(tty);
@@ -2230,9 +2229,8 @@ void audit_log_d_path(struct audit_buffer *ab, const char *prefix,
void audit_log_session_info(struct audit_buffer *ab)
{
unsigned int sessionid = audit_get_sessionid(current);
- uid_t auid = from_kuid(&init_user_ns, audit_get_loginuid(current));
-
- audit_log_format(ab, "auid=%u ses=%u", auid, sessionid);
+ audit_log_format(ab, "auid=%s ses=%u",
+ KUID_STR(audit_get_loginuid(current)), sessionid);
}
void audit_log_key(struct audit_buffer *ab, char *key)
@@ -2465,20 +2463,20 @@ void audit_log_task_info(struct audit_buffer *ab)
cred = current_cred();
tty = audit_get_tty();
audit_log_format(ab,
- " ppid=%d pid=%d auid=%u uid=%u gid=%u"
- " euid=%u suid=%u fsuid=%u"
- " egid=%u sgid=%u fsgid=%u tty=%s ses=%u",
+ " ppid=%d pid=%d auid=%s uid=%s gid=%s"
+ " euid=%s suid=%s fsuid=%s"
+ " egid=%s sgid=%s fsgid=%s tty=%s ses=%u",
task_ppid_nr(current),
task_tgid_nr(current),
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
- from_kuid(&init_user_ns, cred->uid),
- from_kgid(&init_user_ns, cred->gid),
- from_kuid(&init_user_ns, cred->euid),
- from_kuid(&init_user_ns, cred->suid),
- from_kuid(&init_user_ns, cred->fsuid),
- from_kgid(&init_user_ns, cred->egid),
- from_kgid(&init_user_ns, cred->sgid),
- from_kgid(&init_user_ns, cred->fsgid),
+ KUID_STR(audit_get_loginuid(current)),
+ KUID_STR(cred->uid),
+ KGID_STR(cred->gid),
+ KUID_STR(cred->euid),
+ KUID_STR(cred->suid),
+ KUID_STR(cred->fsuid),
+ KGID_STR(cred->egid),
+ KGID_STR(cred->sgid),
+ KGID_STR(cred->fsgid),
tty ? tty_name(tty) : "(none)",
audit_get_sessionid(current));
audit_put_tty(tty);
@@ -2693,7 +2691,6 @@ static void audit_log_set_loginuid(kuid_t koldloginuid, kuid_t kloginuid,
unsigned int sessionid, int rc)
{
struct audit_buffer *ab;
- uid_t uid, oldloginuid, loginuid;
struct tty_struct *tty;
if (!audit_enabled)
@@ -2703,15 +2700,14 @@ static void audit_log_set_loginuid(kuid_t koldloginuid, kuid_t kloginuid,
if (!ab)
return;
- uid = from_kuid(&init_user_ns, task_uid(current));
- oldloginuid = from_kuid(&init_user_ns, koldloginuid);
- loginuid = from_kuid(&init_user_ns, kloginuid);
tty = audit_get_tty();
- audit_log_format(ab, "pid=%d uid=%u", task_tgid_nr(current), uid);
+ audit_log_format(ab, "pid=%d uid=%s", task_tgid_nr(current),
+ KUID_STR(task_uid(current)));
audit_log_task_context(ab);
- audit_log_format(ab, " old-auid=%u auid=%u tty=%s old-ses=%u ses=%u res=%d",
- oldloginuid, loginuid, tty ? tty_name(tty) : "(none)",
+ audit_log_format(ab, " old-auid=%s auid=%s tty=%s old-ses=%u ses=%u res=%d",
+ KUID_STR(koldloginuid), KUID_STR(kloginuid),
+ tty ? tty_name(tty) : "(none)",
oldsessionid, sessionid, !rc);
audit_put_tty(tty);
audit_log_end(ab);
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 6610e667c..ed35bdfd7 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -1104,9 +1104,9 @@ static int audit_log_pid_context(struct audit_context *context, pid_t pid,
if (!ab)
return rc;
- audit_log_format(ab, "opid=%d oauid=%d ouid=%d oses=%d", pid,
- from_kuid(&init_user_ns, auid),
- from_kuid(&init_user_ns, uid), sessionid);
+ audit_log_format(ab, "opid=%d oauid=%s ouid=%s oses=%d", pid,
+ KUID_STR_D(auid),
+ KUID_STR_D(uid), sessionid);
if (lsmprop_is_set(prop) && audit_log_obj_ctx(ab, prop))
rc = 1;
@@ -1309,9 +1309,9 @@ static void audit_log_fcaps(struct audit_buffer *ab, struct audit_names *name)
}
audit_log_cap(ab, "cap_fp", &name->fcap.permitted);
audit_log_cap(ab, "cap_fi", &name->fcap.inheritable);
- audit_log_format(ab, " cap_fe=%d cap_fver=%x cap_frootid=%d",
+ audit_log_format(ab, " cap_fe=%d cap_fver=%x cap_frootid=%s",
name->fcap.fE, name->fcap_ver,
- from_kuid(&init_user_ns, name->fcap.rootid));
+ KUID_STR_D(name->fcap.rootid));
}
static void audit_log_time(struct audit_context *context, struct audit_buffer **ab)
@@ -1380,9 +1380,9 @@ static void show_special(struct audit_context *context, int *call_panic)
context->socketcall.args[i]);
break; }
case AUDIT_IPC:
- audit_log_format(ab, "ouid=%u ogid=%u mode=%#ho",
- from_kuid(&init_user_ns, context->ipc.uid),
- from_kgid(&init_user_ns, context->ipc.gid),
+ audit_log_format(ab, "ouid=%s ogid=%s mode=%#ho",
+ KUID_STR(context->ipc.uid),
+ KGID_STR(context->ipc.gid),
context->ipc.mode);
if (lsmprop_is_set(&context->ipc.oprop)) {
if (audit_log_obj_ctx(ab, &context->ipc.oprop))
@@ -1534,13 +1534,13 @@ static void audit_log_name(struct audit_context *context, struct audit_names *n,
audit_log_format(ab, " name=(null)");
if (n->ino != AUDIT_INO_UNSET)
- audit_log_format(ab, " inode=%llu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
+ audit_log_format(ab, " inode=%llu dev=%02x:%02x mode=%#ho ouid=%s ogid=%s rdev=%02x:%02x",
n->ino,
MAJOR(n->dev),
MINOR(n->dev),
n->mode,
- from_kuid(&init_user_ns, n->uid),
- from_kgid(&init_user_ns, n->gid),
+ KUID_STR(n->uid),
+ KGID_STR(n->gid),
MAJOR(n->rdev),
MINOR(n->rdev));
if (lsmprop_is_set(&n->oprop) &&
@@ -1632,18 +1632,18 @@ static void audit_log_uring(struct audit_context *ctx)
ctx->return_code);
audit_log_format(ab,
" items=%d"
- " ppid=%d pid=%d uid=%u gid=%u euid=%u suid=%u"
- " fsuid=%u egid=%u sgid=%u fsgid=%u",
+ " ppid=%d pid=%d uid=%s gid=%s euid=%s suid=%s"
+ " fsuid=%s egid=%s sgid=%s fsgid=%s",
ctx->name_count,
task_ppid_nr(current), task_tgid_nr(current),
- from_kuid(&init_user_ns, cred->uid),
- from_kgid(&init_user_ns, cred->gid),
- from_kuid(&init_user_ns, cred->euid),
- from_kuid(&init_user_ns, cred->suid),
- from_kuid(&init_user_ns, cred->fsuid),
- from_kgid(&init_user_ns, cred->egid),
- from_kgid(&init_user_ns, cred->sgid),
- from_kgid(&init_user_ns, cred->fsgid));
+ KUID_STR(cred->uid),
+ KGID_STR(cred->gid),
+ KUID_STR(cred->euid),
+ KUID_STR(cred->suid),
+ KUID_STR(cred->fsuid),
+ KGID_STR(cred->egid),
+ KGID_STR(cred->sgid),
+ KGID_STR(cred->fsgid));
audit_log_task_context(ab);
audit_log_key(ab, ctx->filterkey);
audit_log_end(ab);
@@ -1715,9 +1715,8 @@ static void audit_log_exit(void)
audit_log_cap(ab, "pi", &axs->new_pcap.inheritable);
audit_log_cap(ab, "pe", &axs->new_pcap.effective);
audit_log_cap(ab, "pa", &axs->new_pcap.ambient);
- audit_log_format(ab, " frootid=%d",
- from_kuid(&init_user_ns,
- axs->fcap.rootid));
+ audit_log_format(ab, " frootid=%s",
+ KUID_STR_D(axs->fcap.rootid));
break; }
}
@@ -2893,10 +2892,10 @@ static void audit_log_task(struct audit_buffer *ab)
sessionid = audit_get_sessionid(current);
current_uid_gid(&uid, &gid);
- audit_log_format(ab, "auid=%u uid=%u gid=%u ses=%u",
- from_kuid(&init_user_ns, auid),
- from_kuid(&init_user_ns, uid),
- from_kgid(&init_user_ns, gid),
+ audit_log_format(ab, "auid=%s uid=%s gid=%s ses=%u",
+ KUID_STR(auid),
+ KUID_STR(uid),
+ KGID_STR(gid),
sessionid);
audit_log_task_context(ab);
audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current));
diff --git a/net/core/dev.c b/net/core/dev.c
index 1a06976e1..394866cf3 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -9712,12 +9712,12 @@ int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
current_uid_gid(&uid, &gid);
audit_log(audit_context(), GFP_ATOMIC,
AUDIT_ANOM_PROMISCUOUS,
- "dev=%s prom=%d old_prom=%d auid=%u uid=%u gid=%u ses=%u",
+ "dev=%s prom=%d old_prom=%d auid=%s uid=%s gid=%s ses=%u",
dev->name, (dev->flags & IFF_PROMISC),
(old_flags & IFF_PROMISC),
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
- from_kuid(&init_user_ns, uid),
- from_kgid(&init_user_ns, gid),
+ KUID_STR(audit_get_loginuid(current)),
+ KUID_STR(uid),
+ KGID_STR(gid),
audit_get_sessionid(current));
}
diff --git a/net/netlabel/netlabel_user.c b/net/netlabel/netlabel_user.c
index 0da652844..31ac4a0fc 100644
--- a/net/netlabel/netlabel_user.c
+++ b/net/netlabel/netlabel_user.c
@@ -92,8 +92,8 @@ struct audit_buffer *netlbl_audit_start_common(int type,
if (audit_buf == NULL)
return NULL;
- audit_log_format(audit_buf, "netlabel: auid=%u ses=%u",
- from_kuid(&init_user_ns, audit_info->loginuid),
+ audit_log_format(audit_buf, "netlabel: auid=%s ses=%u",
+ KUID_STR(audit_info->loginuid),
audit_info->sessionid);
audit_log_subj_ctx(audit_buf, &audit_info->prop);
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index c9d55fe10..b0afd5289 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -60,10 +60,10 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
audit_log_format(ab, " denied_mask=\"%s\"", str);
}
if (ad->request & AA_AUDIT_FILE_MASK) {
- audit_log_format(ab, " fsuid=%d",
- from_kuid(&init_user_ns, fsuid));
- audit_log_format(ab, " ouid=%d",
- from_kuid(&init_user_ns, ad->fs.ouid));
+ audit_log_format(ab, " fsuid=%s",
+ KUID_STR_D(fsuid));
+ audit_log_format(ab, " ouid=%s",
+ KUID_STR_D(ad->fs.ouid));
}
if (ad->peer) {
diff --git a/security/integrity/integrity_audit.c b/security/integrity/integrity_audit.c
index d8d9e5ff1..cc4959182 100644
--- a/security/integrity/integrity_audit.c
+++ b/security/integrity/integrity_audit.c
@@ -47,10 +47,10 @@ void integrity_audit_message(int audit_msgno, struct inode *inode,
ab = audit_log_start(audit_context(), GFP_KERNEL, audit_msgno);
if (!ab)
return;
- audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u",
+ audit_log_format(ab, "pid=%d uid=%s auid=%s ses=%u",
task_pid_nr(current),
- from_kuid(&init_user_ns, current_uid()),
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ KUID_STR(current_uid()),
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current));
audit_log_task_context(ab);
audit_log_format(ab, " op=%s cause=%s comm=", op, cause);
diff --git a/security/ipe/audit.c b/security/ipe/audit.c
index 93fb59fbd..ace64cf21 100644
--- a/security/ipe/audit.c
+++ b/security/ipe/audit.c
@@ -220,8 +220,8 @@ void ipe_audit_policy_activation(const struct ipe_policy *const op,
audit_log_format(ab, " ");
}
audit_policy(ab, AUDIT_NEW_ACTIVE_POLICY_FMT, np);
- audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1",
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ audit_log_format(ab, " auid=%s ses=%u lsm=ipe res=1",
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current));
audit_log_end(ab);
@@ -248,8 +248,8 @@ void ipe_audit_policy_load(const struct ipe_policy *const p)
err = PTR_ERR(p);
}
- audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=%d errno=%d",
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ audit_log_format(ab, " auid=%s ses=%u lsm=ipe res=%d errno=%d",
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current), !err, err);
audit_log_end(ab);
@@ -269,10 +269,10 @@ void ipe_audit_enforce(bool new_enforce, bool old_enforce)
return;
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
- "enforcing=%d old_enforcing=%d auid=%u ses=%u"
+ "enforcing=%d old_enforcing=%d auid=%s ses=%u"
" enabled=1 old-enabled=1 lsm=ipe res=1",
new_enforce, old_enforce,
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current));
audit_log_end(ab);
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 5aaaf6941..813f69169 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -163,10 +163,10 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
if (length)
goto out;
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
- "enforcing=%d old_enforcing=%d auid=%u ses=%u"
+ "enforcing=%d old_enforcing=%d auid=%s ses=%u"
" enabled=1 old-enabled=1 lsm=selinux res=1",
new_value, old_value,
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current));
enforcing_set(new_value);
if (new_value)
@@ -581,8 +581,8 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
selinux_policy_commit(&load_state);
length = count;
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_POLICY_LOAD,
- "auid=%u ses=%u lsm=selinux res=1",
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ "auid=%s ses=%u lsm=selinux res=1",
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current));
out_unlock:
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 7afce9754..dd0d397b4 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -3012,11 +3012,11 @@ int security_set_bools(u32 len, const int *values)
if (new_state != old_state) {
audit_log(audit_context(), GFP_ATOMIC,
AUDIT_MAC_CONFIG_CHANGE,
- "bool=%s val=%d old_val=%d auid=%u ses=%u",
+ "bool=%s val=%d old_val=%d auid=%s ses=%u",
sym_name(&newpolicy->policydb, SYM_BOOLS, i),
new_state,
old_state,
- from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ KUID_STR(audit_get_loginuid(current)),
audit_get_sessionid(current));
newpolicy->policydb.bool_val_to_struct[i]->state = new_state;
}
[-- Attachment #29: uid_r-0028-net-add-SO_PEERCRED_R-SO_PEERGROUPS_R-and-SCM_CREDEN.patch --]
[-- Type: text/x-patch, Size: 13868 bytes --]
From 3ebb2b61c7d33fdd8c6e8715ca3724f7ce09bffd Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 06:31:32 +0200
Subject: [PATCH 28/43] net: add SO_PEERCRED_R, SO_PEERGROUPS_R and
SCM_CREDENTIALS_R
Services authenticate AF_UNIX peers with SO_PEERCRED, SO_PEERGROUPS and
SCM_CREDENTIALS, which carry 32 bit ids: a UUID or GUID user shows up
as the overflow id there.
Add variants with __kernel_uid_r_t ids:
- SO_PEERCRED_R returns a struct __kernel_ucred_r of the peer,
- SO_PEERGROUPS_R its supplementary groups as __kernel_gid_r_t,
- SO_PASSCRED_R asks for SCM_CREDENTIALS_R messages, with a struct
__kernel_ucred_r, next to or instead of SCM_CREDENTIALS (a separate
option, so receivers sizing their control buffer for SCM_CREDENTIALS
don't get truncated), and SCM_CREDENTIALS_R can be sent with the same
privilege rules as SCM_CREDENTIALS.
The classic SCM_CREDENTIALS sending path now shares the checks and pid
handling with it. Netlink keeps classic ids in its skbs, so it hands out
the overflow id for UUID senders in SCM_CREDENTIALS_R too.
Assisted-by: Claude:claude-opus-5-5
---
arch/alpha/include/uapi/asm/socket.h | 4 ++
arch/mips/include/uapi/asm/socket.h | 4 ++
arch/parisc/include/uapi/asm/socket.h | 4 ++
arch/sparc/include/uapi/asm/socket.h | 4 ++
include/linux/socket.h | 1 +
include/net/sock.h | 4 +-
include/uapi/asm-generic/socket.h | 4 ++
include/uapi/linux/socket.h | 9 +++
net/core/scm.c | 94 ++++++++++++++++++---------
net/core/sock.c | 84 ++++++++++++++++++++++++
net/unix/af_unix.c | 3 +-
11 files changed, 181 insertions(+), 34 deletions(-)
diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5ef57f88d..fa1ee54b6 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -153,6 +153,10 @@
#define SO_PASSRIGHTS 83
#define SO_INQ 84
+
+#define SO_PEERCRED_R 85
+#define SO_PEERGROUPS_R 86
+#define SO_PASSCRED_R 87
#define SCM_INQ SO_INQ
#if !defined(__KERNEL__)
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 72fb1b006..5c9e7ba73 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -164,6 +164,10 @@
#define SO_PASSRIGHTS 83
#define SO_INQ 84
+
+#define SO_PEERCRED_R 85
+#define SO_PEERGROUPS_R 86
+#define SO_PASSCRED_R 87
#define SCM_INQ SO_INQ
#if !defined(__KERNEL__)
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index c16ec36df..5a93cdbfa 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -145,6 +145,10 @@
#define SO_PASSRIGHTS 0x4051
#define SO_INQ 0x4052
+
+#define SO_PEERCRED_R 0x4053
+#define SO_PEERGROUPS_R 0x4054
+#define SO_PASSCRED_R 0x4055
#define SCM_INQ SO_INQ
#if !defined(__KERNEL__)
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index 71befa109..3ca685533 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -146,6 +146,10 @@
#define SO_PASSRIGHTS 0x005c
#define SO_INQ 0x005d
+
+#define SO_PEERCRED_R 0x005e
+#define SO_PEERGROUPS_R 0x005f
+#define SO_PASSCRED_R 0x0060
#define SCM_INQ SO_INQ
#if !defined(__KERNEL__)
diff --git a/include/linux/socket.h b/include/linux/socket.h
index 2a8d7b14f..89484bc6d 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -193,6 +193,7 @@ static inline size_t msg_data_left(const struct msghdr *msg)
#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */
#define SCM_SECURITY 0x03 /* rw: security label */
#define SCM_PIDFD 0x04 /* ro: pidfd (int) */
+#define SCM_CREDENTIALS_R 0x05 /* rw: struct __kernel_ucred_r */
struct ucred {
__u32 pid;
diff --git a/include/net/sock.h b/include/net/sock.h
index 0986d9466..7055afcf0 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -358,6 +358,7 @@ struct sk_filter;
* @sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY
* @sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD
* @sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS
+ * @sk_scm_credentials_r: flagged by SO_PASSCRED_R to recv SCM_CREDENTIALS_R
* @sk_scm_unused: unused flags for scm_recv()
* @ns_tracker: tracker for netns reference
* @sk_user_frags: xarray of pages the user is holding a reference on.
@@ -565,7 +566,8 @@ struct sock {
sk_scm_security : 1,
sk_scm_pidfd : 1,
sk_scm_rights : 1,
- sk_scm_unused : 4;
+ sk_scm_credentials_r : 1,
+ sk_scm_unused : 3;
};
};
u8 sk_clockid;
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 53b5a8c00..89c927eb0 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -148,6 +148,10 @@
#define SO_PASSRIGHTS 83
#define SO_INQ 84
+
+#define SO_PEERCRED_R 85
+#define SO_PEERGROUPS_R 86
+#define SO_PASSCRED_R 87
#define SCM_INQ SO_INQ
#if !defined(__KERNEL__)
diff --git a/include/uapi/linux/socket.h b/include/uapi/linux/socket.h
index d3fcd3b5e..38d3cd814 100644
--- a/include/uapi/linux/socket.h
+++ b/include/uapi/linux/socket.h
@@ -2,6 +2,8 @@
#ifndef _UAPI_LINUX_SOCKET_H
#define _UAPI_LINUX_SOCKET_H
+#include <linux/uidgid_r.h>
+
/*
* Desired design of maximum size and alignment (see RFC2553)
*/
@@ -35,4 +37,11 @@ struct __kernel_sockaddr_storage {
#define SOCK_TXREHASH_DISABLED 0
#define SOCK_TXREHASH_ENABLED 1
+/* SO_PEERCRED_R and SCM_CREDENTIALS_R: struct ucred with UUIDs and GUIDs */
+struct __kernel_ucred_r {
+ __u32 pid;
+ __kernel_uid_r_t uid;
+ __kernel_gid_r_t gid;
+};
+
#endif /* _UAPI_LINUX_SOCKET_H */
diff --git a/net/core/scm.c b/net/core/scm.c
index eec13f50e..899db7b56 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -40,22 +40,19 @@
#include <net/cls_cgroup.h>
#include <net/af_unix.h>
-
/*
* Only allow a user to send credentials, that they could set with
* setu(g)id.
*/
-static __inline__ int scm_check_creds(struct ucred *creds)
+static int scm_check_kcreds(u32 pid, kuid_t uid, kgid_t gid)
{
const struct cred *cred = current_cred();
- kuid_t uid = make_kuid(cred->user_ns, creds->uid);
- kgid_t gid = make_kgid(cred->user_ns, creds->gid);
if (!uid_valid(uid) || !gid_valid(gid))
return -EINVAL;
- if ((creds->pid == task_tgid_vnr(current) ||
+ if ((pid == task_tgid_vnr(current) ||
ns_capable(task_active_pid_ns(current)->user_ns, CAP_SYS_ADMIN)) &&
((uid_eq(uid, cred->uid) || uid_eq(uid, cred->euid) ||
uid_eq(uid, cred->suid)) || ns_capable(cred->user_ns, CAP_SETUID)) &&
@@ -163,6 +160,30 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
return 0;
}
+/* The sender's pid in @p is @pid, and the sender ids @uid and @gid */
+static int scm_set_creds(struct scm_cookie *p, u32 pid, kuid_t uid, kgid_t gid)
+{
+ int err;
+
+ if (!p->pid || pid_vnr(p->pid) != pid) {
+ struct pid *kpid = find_get_pid(pid);
+
+ if (!kpid)
+ return -ESRCH;
+ /* pass a struct pid reference from find_get_pid() to
+ * scm_replace_pid().
+ */
+ err = scm_replace_pid(p, kpid);
+ if (err) {
+ put_pid(kpid);
+ return err;
+ }
+ }
+ p->creds.uid = uid;
+ p->creds.gid = gid;
+ return 0;
+}
+
int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
{
const struct proto_ops *ops = READ_ONCE(sock->ops);
@@ -203,35 +224,33 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
goto error;
memcpy(&creds, CMSG_DATA(cmsg), sizeof(struct ucred));
- err = scm_check_creds(&creds);
- if (err)
- goto error;
-
- if (!p->pid || pid_vnr(p->pid) != creds.pid) {
- struct pid *pid;
- err = -ESRCH;
- pid = find_get_pid(creds.pid);
- if (!pid)
- goto error;
-
- /* pass a struct pid reference from
- * find_get_pid() to scm_replace_pid().
- */
- err = scm_replace_pid(p, pid);
- if (err) {
- put_pid(pid);
- goto error;
- }
- }
-
- err = -EINVAL;
uid = make_kuid(current_user_ns(), creds.uid);
gid = make_kgid(current_user_ns(), creds.gid);
- if (!uid_valid(uid) || !gid_valid(gid))
+ err = scm_check_kcreds(creds.pid, uid, gid);
+ if (!err)
+ err = scm_set_creds(p, creds.pid, uid, gid);
+ if (err)
goto error;
+ break;
+ }
+ /* SCM_CREDENTIALS with __kernel_uid_r_t ids */
+ case SCM_CREDENTIALS_R:
+ {
+ struct __kernel_ucred_r creds;
+ struct user_namespace *ns = current_user_ns();
+ kuid_t uid;
+ kgid_t gid;
- p->creds.uid = uid;
- p->creds.gid = gid;
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(creds)))
+ goto error;
+ memcpy(&creds, CMSG_DATA(cmsg), sizeof(creds));
+ uid = make_kuid_r(ns, &creds.uid);
+ gid = make_kgid_r(ns, &creds.gid);
+ err = scm_check_kcreds(creds.pid, uid, gid);
+ if (!err)
+ err = scm_set_creds(p, creds.pid, uid, gid);
+ if (err)
+ goto error;
break;
}
default:
@@ -502,8 +521,8 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!msg->msg_control) {
- if (sk->sk_scm_credentials || sk->sk_scm_pidfd ||
- scm->fp || scm_has_secdata(sk))
+ if (sk->sk_scm_credentials || sk->sk_scm_credentials_r ||
+ sk->sk_scm_pidfd || scm->fp || scm_has_secdata(sk))
msg->msg_flags |= MSG_CTRUNC;
scm_destroy(scm);
@@ -521,6 +540,17 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
put_cmsg(msg, SOL_SOCKET, SCM_CREDENTIALS, sizeof(ucreds), &ucreds);
}
+ if (sk->sk_scm_credentials_r) {
+ struct user_namespace *current_ns = current_user_ns();
+ struct __kernel_ucred_r ucreds;
+
+ memset(&ucreds, 0, sizeof(ucreds));
+ ucreds.pid = scm->creds.pid;
+ from_kuid_r_munged(current_ns, scm->creds.uid, &ucreds.uid);
+ from_kgid_r_munged(current_ns, scm->creds.gid, &ucreds.gid);
+ put_cmsg(msg, SOL_SOCKET, SCM_CREDENTIALS_R, sizeof(ucreds), &ucreds);
+ }
+
scm_passec(sk, msg, scm);
if (scm->fp)
diff --git a/net/core/sock.c b/net/core/sock.c
index e814828e4..1e3d15f3b 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1585,6 +1585,13 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
ret = -EOPNOTSUPP;
break;
+ case SO_PASSCRED_R:
+ if (sk_may_scm_recv(sk))
+ sk->sk_scm_credentials_r = valbool;
+ else
+ ret = -EOPNOTSUPP;
+ break;
+
case SO_INCOMING_CPU:
reuseport_update_incoming_cpu(sk, val);
break;
@@ -1714,6 +1721,34 @@ static void cred_to_ucred(struct pid *pid, const struct cred *cred,
}
}
+static void cred_to_ucred_r(struct pid *pid, const struct cred *cred,
+ struct __kernel_ucred_r *ucred)
+{
+ struct user_namespace *current_ns = current_user_ns();
+
+ memset(ucred, 0, sizeof(*ucred));
+ ucred->pid = pid_vnr(pid);
+ if (cred) {
+ from_kuid_r_munged(current_ns, cred->euid, &ucred->uid);
+ from_kgid_r_munged(current_ns, cred->egid, &ucred->gid);
+ }
+}
+
+static int groups_to_user_r(sockptr_t dst, const struct group_info *src)
+{
+ struct user_namespace *user_ns = current_user_ns();
+ __kernel_gid_r_t gid;
+ int i;
+
+ for (i = 0; i < src->ngroups; i++) {
+ from_kgid_r_munged(user_ns, src->gid[i], &gid);
+ if (copy_to_sockptr_offset(dst, i * sizeof(gid), &gid, sizeof(gid)))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
static int groups_to_user(sockptr_t dst, const struct group_info *src)
{
struct user_namespace *user_ns = current_user_ns();
@@ -1900,6 +1935,13 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
v.val = sk->sk_scm_rights;
break;
+ case SO_PASSCRED_R:
+ if (!sk_may_scm_recv(sk))
+ return -EOPNOTSUPP;
+
+ v.val = sk->sk_scm_credentials_r;
+ break;
+
case SO_PEERCRED:
{
struct ucred peercred;
@@ -1915,6 +1957,23 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
goto lenout;
}
+ /* Like SO_PEERCRED, with uid and gid as __kernel_uid_r_t */
+ case SO_PEERCRED_R:
+ {
+ struct __kernel_ucred_r peercred;
+
+ if (len > sizeof(peercred))
+ len = sizeof(peercred);
+
+ spin_lock(&sk->sk_peer_lock);
+ cred_to_ucred_r(sk->sk_peer_pid, sk->sk_peer_cred, &peercred);
+ spin_unlock(&sk->sk_peer_lock);
+
+ if (copy_to_sockptr(optval, &peercred, len))
+ return -EFAULT;
+ goto lenout;
+ }
+
case SO_PEERPIDFD:
{
struct pid *peer_pid;
@@ -1980,6 +2039,31 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
goto lenout;
}
+ /* Like SO_PEERGROUPS, with an array of __kernel_gid_r_t */
+ case SO_PEERGROUPS_R:
+ {
+ const struct cred *cred;
+ int ret, n;
+
+ cred = sk_get_peer_cred(sk);
+ if (!cred)
+ return -ENODATA;
+
+ n = cred->group_info->ngroups;
+ if (len < n * sizeof(__kernel_gid_r_t)) {
+ len = n * sizeof(__kernel_gid_r_t);
+ put_cred(cred);
+ return copy_to_sockptr(optlen, &len, sizeof(int)) ? -EFAULT : -ERANGE;
+ }
+ len = n * sizeof(__kernel_gid_r_t);
+
+ ret = groups_to_user_r(optval, cred->group_info);
+ put_cred(cred);
+ if (ret)
+ return ret;
+ goto lenout;
+ }
+
case SO_PEERNAME:
{
struct sockaddr_storage address;
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index e7af9f870..8523158f7 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -803,7 +803,8 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
static bool unix_may_passcred(const struct sock *sk)
{
- return sk->sk_scm_credentials || sk->sk_scm_pidfd;
+ return sk->sk_scm_credentials || sk->sk_scm_credentials_r ||
+ sk->sk_scm_pidfd;
}
static int unix_listen(struct socket *sock, int backlog)
[-- Attachment #30: uid_r-0029-signal-report-the-sender-s-UUID-or-GUID-uid-in-si_ui.patch --]
[-- Type: text/x-patch, Size: 21954 bytes --]
From e4bac2399b74616af0e7c8572b0ac46ffec74db4 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 08:46:03 +0200
Subject: [PATCH 29/43] signal: report the sender's UUID or GUID uid in
si_uid_r
si_uid is 32 bits, so a signal from a UUID or GUID user names the
overflow uid as its sender, and so do SIGCHLD and waitid() for such a
child.
Add si_uid_r, the sender's uid as __kernel_uid_r_t, to siginfo_t. union
__sifields has no room that all kinds of signals with si_uid share, so
it lives at a fixed offset (96) in the padding of siginfo_t, the same
in every layout and ABI, compat_siginfo included; kernel_siginfo grows
to cover it. The kernel sets it wherever it sets si_uid, through
siginfo_set_uid() and siginfo_set_kernel_uid(), maps it into the
receiver's user namespace like si_uid, and it is UID_R_NONE where
nobody set it. Signals queued from userspace carry what the sender put
there, exactly like si_uid.
signalfd_siginfo gets ssi_uid_r from its padding, and waitid() (also
through io_uring, which keeps its waitid_info in the async data now, as
it no longer fits into the command) fills in si_uid_r.
Assisted-by: Claude:claude-opus-5-5
---
fs/signalfd.c | 3 ++
include/linux/compat.h | 6 ++++
include/linux/ptrace.h | 2 +-
include/linux/signal.h | 22 ++++++++++++++
include/linux/signal_types.h | 5 +++-
include/uapi/asm-generic/siginfo.h | 17 +++++++++++
include/uapi/linux/signalfd.h | 4 ++-
io_uring/waitid.c | 32 +++++++++++---------
io_uring/waitid.h | 1 +
ipc/mqueue.c | 4 +--
kernel/exit.c | 18 ++++++++++--
kernel/exit.h | 3 ++
kernel/signal.c | 47 ++++++++++++++++++++----------
13 files changed, 128 insertions(+), 36 deletions(-)
diff --git a/fs/signalfd.c b/fs/signalfd.c
index dff53745e..8bca9d7f0 100644
--- a/fs/signalfd.c
+++ b/fs/signalfd.c
@@ -90,6 +90,7 @@ static int signalfd_copyinfo(struct iov_iter *to, kernel_siginfo_t const *kinfo)
case SIL_KILL:
new.ssi_pid = kinfo->si_pid;
new.ssi_uid = kinfo->si_uid;
+ new.ssi_uid_r = kinfo->si_uid_r;
break;
case SIL_TIMER:
new.ssi_tid = kinfo->si_tid;
@@ -125,6 +126,7 @@ static int signalfd_copyinfo(struct iov_iter *to, kernel_siginfo_t const *kinfo)
case SIL_CHLD:
new.ssi_pid = kinfo->si_pid;
new.ssi_uid = kinfo->si_uid;
+ new.ssi_uid_r = kinfo->si_uid_r;
new.ssi_status = kinfo->si_status;
new.ssi_utime = kinfo->si_utime;
new.ssi_stime = kinfo->si_stime;
@@ -135,6 +137,7 @@ static int signalfd_copyinfo(struct iov_iter *to, kernel_siginfo_t const *kinfo)
*/
new.ssi_pid = kinfo->si_pid;
new.ssi_uid = kinfo->si_uid;
+ new.ssi_uid_r = kinfo->si_uid_r;
new.ssi_ptr = (long) kinfo->si_ptr;
new.ssi_int = kinfo->si_int;
break;
diff --git a/include/linux/compat.h b/include/linux/compat.h
index 8da0a15c9..4b8505900 100644
--- a/include/linux/compat.h
+++ b/include/linux/compat.h
@@ -169,6 +169,12 @@ typedef struct compat_siginfo {
union {
int _pad[128/sizeof(int) - 3];
+ /* si_uid_r, at __SI_UID_R_OFFSET like in siginfo_t */
+ struct {
+ int _uid_r_pad[__SI_UID_R_OFFSET / sizeof(int) - 3];
+ __kernel_uid_r_t _uid_r;
+ } _sir;
+
/* kill() */
struct {
compat_pid_t _pid; /* sender's pid */
diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h
index ef314f7a9..b213b7385 100644
--- a/include/linux/ptrace.h
+++ b/include/linux/ptrace.h
@@ -347,7 +347,7 @@ static inline void user_single_step_report(struct pt_regs *regs)
info.si_errno = 0;
info.si_code = SI_USER;
info.si_pid = 0;
- info.si_uid = 0;
+ siginfo_set_kernel_uid(&info);
force_sig_info(&info);
}
#endif
diff --git a/include/linux/signal.h b/include/linux/signal.h
index f19816832..928e67e28 100644
--- a/include/linux/signal.h
+++ b/include/linux/signal.h
@@ -6,6 +6,7 @@
#include <linux/list.h>
#include <linux/signal_types.h>
#include <linux/string.h>
+#include <linux/uidgid.h>
struct task_struct;
@@ -23,6 +24,27 @@ static inline void clear_siginfo(kernel_siginfo_t *info)
memset(info, 0, sizeof(*info));
}
+struct user_namespace;
+
+/*
+ * Sets si_uid and si_uid_r to @uid as seen from @ns (the receiver's user
+ * namespace), the overflow id if it has no mapping there.
+ */
+static inline void siginfo_set_uid(kernel_siginfo_t *info,
+ struct user_namespace *ns, kuid_t uid)
+{
+ info->si_uid = from_kuid_munged(ns, uid);
+ from_kuid_r_munged(ns, uid, &info->si_uid_r);
+}
+
+/* si_uid and si_uid_r of a signal from the kernel: 0 */
+static inline void siginfo_set_kernel_uid(kernel_siginfo_t *info)
+{
+ info->si_uid = 0;
+ memset(&info->si_uid_r, 0, sizeof(info->si_uid_r));
+ info->si_uid_r.type = UID_R_LEGACY;
+}
+
#define SI_EXPANSION_SIZE (sizeof(struct siginfo) - sizeof(struct kernel_siginfo))
static inline void copy_siginfo_to_external(siginfo_t *to,
diff --git a/include/linux/signal_types.h b/include/linux/signal_types.h
index caf4f7a59..eaf922e44 100644
--- a/include/linux/signal_types.h
+++ b/include/linux/signal_types.h
@@ -10,7 +10,10 @@
#include <uapi/linux/signal.h>
typedef struct kernel_siginfo {
- __SIGINFO;
+ union {
+ __SIGINFO;
+ __SIGINFO_UID_R;
+ };
} kernel_siginfo_t;
struct ucounts;
diff --git a/include/uapi/asm-generic/siginfo.h b/include/uapi/asm-generic/siginfo.h
index 5a1ca43b5..e8f32c942 100644
--- a/include/uapi/asm-generic/siginfo.h
+++ b/include/uapi/asm-generic/siginfo.h
@@ -4,6 +4,7 @@
#include <linux/compiler.h>
#include <linux/types.h>
+#include <linux/uidgid_r.h>
typedef union sigval {
int sival_int;
@@ -131,9 +132,24 @@ struct { \
}
#endif /* __ARCH_HAS_SWAPPED_SIGINFO */
+/*
+ * The sender's uid as __kernel_uid_r_t (a UUID or GUID too), for the
+ * signals with si_pid and si_uid. It lives at the same offset in every
+ * layout and ABI, past the end of union __sifields; UID_R_NONE where the
+ * kernel doesn't set it.
+ */
+#define __SI_UID_R_OFFSET 96
+
+#define __SIGINFO_UID_R \
+struct { \
+ int _uid_r_pad[__SI_UID_R_OFFSET / sizeof(int)]; \
+ __kernel_uid_r_t _uid_r; \
+} _sir
+
typedef struct siginfo {
union {
__SIGINFO;
+ __SIGINFO_UID_R;
int _si_pad[SI_MAX_SIZE/sizeof(int)];
};
} __ARCH_SI_ATTRIBUTES siginfo_t;
@@ -143,6 +159,7 @@ typedef struct siginfo {
*/
#define si_pid _sifields._kill._pid
#define si_uid _sifields._kill._uid
+#define si_uid_r _sir._uid_r
#define si_tid _sifields._timer._tid
#define si_overrun _sifields._timer._overrun
#define si_sys_private _sifields._timer._sys_private
diff --git a/include/uapi/linux/signalfd.h b/include/uapi/linux/signalfd.h
index 83429a05b..471bafc53 100644
--- a/include/uapi/linux/signalfd.h
+++ b/include/uapi/linux/signalfd.h
@@ -10,6 +10,7 @@
#define _UAPI_LINUX_SIGNALFD_H
#include <linux/types.h>
+#include <linux/uidgid_r.h>
/* For O_CLOEXEC and O_NONBLOCK */
#include <linux/fcntl.h>
@@ -39,6 +40,7 @@ struct signalfd_siginfo {
__s32 ssi_syscall;
__u64 ssi_call_addr;
__u32 ssi_arch;
+ __kernel_uid_r_t ssi_uid_r; /* sender's uid, also a UUID or GUID */
/*
* Pad strcture to 128 bytes. Remember to update the
@@ -49,7 +51,7 @@ struct signalfd_siginfo {
* comes out of a read(2) and we really don't want to have
* a compat on read(2).
*/
- __u8 __pad[28];
+ __u8 __pad[8];
};
diff --git a/io_uring/waitid.c b/io_uring/waitid.c
index 2b7560174..24f27b67d 100644
--- a/io_uring/waitid.c
+++ b/io_uring/waitid.c
@@ -29,7 +29,6 @@ struct io_waitid {
atomic_t refs;
struct wait_queue_head *head;
struct siginfo __user *infop;
- struct waitid_info info;
};
static void io_waitid_free(struct io_kiocb *req)
@@ -40,8 +39,10 @@ static void io_waitid_free(struct io_kiocb *req)
io_req_async_data_free(req);
}
-static bool io_waitid_compat_copy_si(struct io_waitid *iw, int signo)
+static bool io_waitid_compat_copy_si(struct io_kiocb *req, int signo)
{
+ struct io_waitid *iw = io_kiocb_to_cmd(req, struct io_waitid);
+ struct io_waitid_async *iwa = req->async_data;
struct compat_siginfo __user *infop;
bool ret;
@@ -52,10 +53,12 @@ static bool io_waitid_compat_copy_si(struct io_waitid *iw, int signo)
unsafe_put_user(signo, &infop->si_signo, Efault);
unsafe_put_user(0, &infop->si_errno, Efault);
- unsafe_put_user(iw->info.cause, &infop->si_code, Efault);
- unsafe_put_user(iw->info.pid, &infop->si_pid, Efault);
- unsafe_put_user(iw->info.uid, &infop->si_uid, Efault);
- unsafe_put_user(iw->info.status, &infop->si_status, Efault);
+ unsafe_put_user(iwa->info.cause, &infop->si_code, Efault);
+ unsafe_put_user(iwa->info.pid, &infop->si_pid, Efault);
+ unsafe_put_user(iwa->info.uid, &infop->si_uid, Efault);
+ unsafe_copy_to_user((char __user *)&infop->_sifields._sir._uid_r,
+ (const char *)&iwa->info.uid_r, sizeof(iwa->info.uid_r), Efault);
+ unsafe_put_user(iwa->info.status, &infop->si_status, Efault);
ret = true;
done:
user_write_access_end();
@@ -68,23 +71,26 @@ static bool io_waitid_compat_copy_si(struct io_waitid *iw, int signo)
static bool io_waitid_copy_si(struct io_kiocb *req, int signo)
{
struct io_waitid *iw = io_kiocb_to_cmd(req, struct io_waitid);
+ struct io_waitid_async *iwa = req->async_data;
bool ret;
if (!iw->infop)
return true;
if (io_is_compat(req->ctx))
- return io_waitid_compat_copy_si(iw, signo);
+ return io_waitid_compat_copy_si(req, signo);
if (!user_write_access_begin(iw->infop, sizeof(*iw->infop)))
return false;
unsafe_put_user(signo, &iw->infop->si_signo, Efault);
unsafe_put_user(0, &iw->infop->si_errno, Efault);
- unsafe_put_user(iw->info.cause, &iw->infop->si_code, Efault);
- unsafe_put_user(iw->info.pid, &iw->infop->si_pid, Efault);
- unsafe_put_user(iw->info.uid, &iw->infop->si_uid, Efault);
- unsafe_put_user(iw->info.status, &iw->infop->si_status, Efault);
+ unsafe_put_user(iwa->info.cause, &iw->infop->si_code, Efault);
+ unsafe_put_user(iwa->info.pid, &iw->infop->si_pid, Efault);
+ unsafe_put_user(iwa->info.uid, &iw->infop->si_uid, Efault);
+ unsafe_copy_to_user((char __user *)&iw->infop->si_uid_r,
+ (const char *)&iwa->info.uid_r, sizeof(iwa->info.uid_r), Efault);
+ unsafe_put_user(iwa->info.status, &iw->infop->si_status, Efault);
ret = true;
done:
user_write_access_end();
@@ -294,7 +300,7 @@ int io_waitid_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
iw->options = READ_ONCE(sqe->file_index);
iw->head = NULL;
iw->infop = u64_to_user_ptr(READ_ONCE(sqe->addr2));
- memset(&iw->info, 0, sizeof(iw->info));
+ memset(&iwa->info, 0, sizeof(iwa->info));
return 0;
}
@@ -305,7 +311,7 @@ int io_waitid(struct io_kiocb *req, unsigned int issue_flags)
struct io_ring_ctx *ctx = req->ctx;
int ret;
- ret = kernel_waitid_prepare(&iwa->wo, iw->which, iw->upid, &iw->info,
+ ret = kernel_waitid_prepare(&iwa->wo, iw->which, iw->upid, &iwa->info,
iw->options, NULL);
if (ret)
goto done;
diff --git a/io_uring/waitid.h b/io_uring/waitid.h
index d5544aaf3..60b744a0e 100644
--- a/io_uring/waitid.h
+++ b/io_uring/waitid.h
@@ -5,6 +5,7 @@
struct io_waitid_async {
struct io_kiocb *req;
struct wait_opts wo;
+ struct waitid_info info;
};
int io_waitid_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
diff --git a/ipc/mqueue.c b/ipc/mqueue.c
index 4798b3759..261315ede 100644
--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -803,8 +803,8 @@ static void __do_notify(struct mqueue_inode_info *info)
/* map current pid/uid into info->owner's namespaces */
sig_i.si_pid = task_tgid_nr_ns(current,
ns_of_pid(info->notify_owner));
- sig_i.si_uid = from_kuid_munged(info->notify_user_ns,
- current_uid());
+ siginfo_set_uid(&sig_i, info->notify_user_ns,
+ current_uid());
/*
* We can't use kill_pid_info(), this signal should
* bypass check_kill_permission(). It is from kernel
diff --git a/kernel/exit.c b/kernel/exit.c
index 27e5cf81e..885348cb9 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1186,7 +1186,8 @@ static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
{
int state, status;
pid_t pid = task_pid_vnr(p);
- uid_t uid = from_kuid_munged(current_user_ns(), task_uid(p));
+ kuid_t kuid = task_uid(p);
+ uid_t uid = from_kuid_munged(current_user_ns(), kuid);
struct waitid_info *infop;
if (!likely(wo->wo_flags & WEXITED))
@@ -1303,6 +1304,7 @@ static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
}
infop->pid = pid;
infop->uid = uid;
+ from_kuid_r_munged(current_user_ns(), kuid, &infop->uid_r);
}
return pid;
@@ -1344,6 +1346,7 @@ static int wait_task_stopped(struct wait_opts *wo,
struct waitid_info *infop;
int exit_code, *p_code, why;
uid_t uid = 0; /* unneeded, required by compiler */
+ kuid_t kuid = INVALID_UID;
pid_t pid;
/*
@@ -1369,7 +1372,8 @@ static int wait_task_stopped(struct wait_opts *wo,
if (!unlikely(wo->wo_flags & WNOWAIT))
*p_code = 0;
- uid = from_kuid_munged(current_user_ns(), task_uid(p));
+ kuid = task_uid(p);
+ uid = from_kuid_munged(current_user_ns(), kuid);
unlock_sig:
spin_unlock_irq(&p->sighand->siglock);
if (!exit_code)
@@ -1400,6 +1404,7 @@ static int wait_task_stopped(struct wait_opts *wo,
infop->status = exit_code;
infop->pid = pid;
infop->uid = uid;
+ from_kuid_r_munged(current_user_ns(), kuid, &infop->uid_r);
}
return pid;
}
@@ -1415,6 +1420,7 @@ static int wait_task_continued(struct wait_opts *wo, struct task_struct *p)
struct waitid_info *infop;
pid_t pid;
uid_t uid;
+ kuid_t kuid;
if (!unlikely(wo->wo_flags & WCONTINUED))
return 0;
@@ -1430,7 +1436,8 @@ static int wait_task_continued(struct wait_opts *wo, struct task_struct *p)
}
if (!unlikely(wo->wo_flags & WNOWAIT))
p->signal->flags &= ~SIGNAL_STOP_CONTINUED;
- uid = from_kuid_munged(current_user_ns(), task_uid(p));
+ kuid = task_uid(p);
+ uid = from_kuid_munged(current_user_ns(), kuid);
spin_unlock_irq(&p->sighand->siglock);
pid = task_pid_vnr(p);
@@ -1448,6 +1455,7 @@ static int wait_task_continued(struct wait_opts *wo, struct task_struct *p)
infop->cause = CLD_CONTINUED;
infop->pid = pid;
infop->uid = uid;
+ from_kuid_r_munged(current_user_ns(), kuid, &infop->uid_r);
infop->status = SIGCONT;
}
return pid;
@@ -1847,6 +1855,8 @@ SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
unsafe_put_user(info.cause, &infop->si_code, Efault);
unsafe_put_user(info.pid, &infop->si_pid, Efault);
unsafe_put_user(info.uid, &infop->si_uid, Efault);
+ unsafe_copy_to_user((char __user *)&infop->si_uid_r,
+ (const char *)&info.uid_r, sizeof(info.uid_r), Efault);
unsafe_put_user(info.status, &infop->si_status, Efault);
user_write_access_end();
return err;
@@ -1990,6 +2000,8 @@ COMPAT_SYSCALL_DEFINE5(waitid,
unsafe_put_user(info.cause, &infop->si_code, Efault);
unsafe_put_user(info.pid, &infop->si_pid, Efault);
unsafe_put_user(info.uid, &infop->si_uid, Efault);
+ unsafe_copy_to_user((char __user *)&infop->_sifields._sir._uid_r,
+ (const char *)&info.uid_r, sizeof(info.uid_r), Efault);
unsafe_put_user(info.status, &infop->si_status, Efault);
user_write_access_end();
return err;
diff --git a/kernel/exit.h b/kernel/exit.h
index 278faa26a..f24a6d691 100644
--- a/kernel/exit.h
+++ b/kernel/exit.h
@@ -2,9 +2,12 @@
#ifndef LINUX_WAITID_H
#define LINUX_WAITID_H
+#include <linux/uidgid_r.h>
+
struct waitid_info {
pid_t pid;
uid_t uid;
+ __kernel_uid_r_t uid_r; /* uid as __kernel_uid_r_t */
int status;
int cause;
};
diff --git a/kernel/signal.c b/kernel/signal.c
index 808980d08..507cf5b99 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -620,7 +620,7 @@ static void collect_signal(int sig, struct sigpending *list, kernel_siginfo_t *i
info->si_errno = 0;
info->si_code = SI_USER;
info->si_pid = 0;
- info->si_uid = 0;
+ siginfo_set_kernel_uid(info);
}
}
@@ -1138,9 +1138,8 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info,
q->info.si_pid = task_tgid_nr_ns(current,
task_active_pid_ns(t));
rcu_read_lock();
- q->info.si_uid =
- from_kuid_munged(task_cred_xxx(t, user_ns),
- current_uid());
+ siginfo_set_uid(&q->info, task_cred_xxx(t, user_ns),
+ current_uid());
rcu_read_unlock();
break;
case (unsigned long) SEND_SIG_PRIV:
@@ -1149,7 +1148,7 @@ static int __send_signal_locked(int sig, struct kernel_siginfo *info,
q->info.si_errno = 0;
q->info.si_code = SI_KERNEL;
q->info.si_pid = 0;
- q->info.si_uid = 0;
+ siginfo_set_kernel_uid(&q->info);
break;
default:
copy_siginfo(&q->info, info);
@@ -1245,7 +1244,13 @@ int send_signal_locked(int sig, struct kernel_siginfo *info,
t_user_ns = task_cred_xxx(t, user_ns);
if (current_user_ns() != t_user_ns) {
kuid_t uid = make_kuid(current_user_ns(), info->si_uid);
+ kuid_t uid_r = make_kuid_r(current_user_ns(),
+ &info->si_uid_r);
+
info->si_uid = from_kuid_munged(t_user_ns, uid);
+ if (info->si_uid_r.type != UID_R_NONE)
+ from_kuid_r_munged(t_user_ns, uid_r,
+ &info->si_uid_r);
}
rcu_read_unlock();
@@ -1695,7 +1700,7 @@ void force_sig(int sig)
info.si_errno = 0;
info.si_code = SI_KERNEL;
info.si_pid = 0;
- info.si_uid = 0;
+ siginfo_set_kernel_uid(&info);
force_sig_info(&info);
}
EXPORT_SYMBOL(force_sig);
@@ -1709,7 +1714,7 @@ void force_fatal_sig(int sig)
info.si_errno = 0;
info.si_code = SI_KERNEL;
info.si_pid = 0;
- info.si_uid = 0;
+ siginfo_set_kernel_uid(&info);
force_sig_info_to_task(&info, current, HANDLER_SIG_DFL);
}
@@ -1722,7 +1727,7 @@ void force_exit_sig(int sig)
info.si_errno = 0;
info.si_code = SI_KERNEL;
info.si_pid = 0;
- info.si_uid = 0;
+ siginfo_set_kernel_uid(&info);
force_sig_info_to_task(&info, current, HANDLER_EXIT);
}
@@ -2293,8 +2298,8 @@ bool do_notify_parent(struct task_struct *tsk, int sig)
*/
rcu_read_lock();
info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent));
- info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns),
- task_uid(tsk));
+ siginfo_set_uid(&info, task_cred_xxx(tsk->parent, user_ns),
+ task_uid(tsk));
rcu_read_unlock();
task_cputime(tsk, &utime, &stime);
@@ -2388,7 +2393,7 @@ static void do_notify_parent_cldstop(struct task_struct *tsk,
*/
rcu_read_lock();
info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(parent));
- info.si_uid = from_kuid_munged(task_cred_xxx(parent, user_ns), task_uid(tsk));
+ siginfo_set_uid(&info, task_cred_xxx(parent, user_ns), task_uid(tsk));
rcu_read_unlock();
task_cputime(tsk, &utime, &stime);
@@ -2593,7 +2598,7 @@ static int ptrace_do_notify(int signr, int exit_code, int why, unsigned long mes
info.si_signo = signr;
info.si_code = exit_code;
info.si_pid = task_pid_vnr(current);
- info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
+ siginfo_set_uid(&info, current_user_ns(), current_uid());
/* Let the debugger run. */
return ptrace_stop(exit_code, why, message, &info);
@@ -2847,8 +2852,8 @@ static int ptrace_signal(int signr, kernel_siginfo_t *info, enum pid_type type)
info->si_code = SI_USER;
rcu_read_lock();
info->si_pid = task_pid_vnr(current->parent);
- info->si_uid = from_kuid_munged(current_user_ns(),
- task_uid(current->parent));
+ siginfo_set_uid(info, current_user_ns(),
+ task_uid(current->parent));
rcu_read_unlock();
}
@@ -3650,6 +3655,7 @@ void copy_siginfo_to_external32(struct compat_siginfo *to,
case SIL_KILL:
to->si_pid = from->si_pid;
to->si_uid = from->si_uid;
+ to->_sifields._sir._uid_r = from->si_uid_r;
break;
case SIL_TIMER:
to->si_tid = from->si_tid;
@@ -3689,6 +3695,7 @@ void copy_siginfo_to_external32(struct compat_siginfo *to,
case SIL_CHLD:
to->si_pid = from->si_pid;
to->si_uid = from->si_uid;
+ to->_sifields._sir._uid_r = from->si_uid_r;
to->si_status = from->si_status;
to->si_utime = from->si_utime;
to->si_stime = from->si_stime;
@@ -3696,6 +3703,7 @@ void copy_siginfo_to_external32(struct compat_siginfo *to,
case SIL_RT:
to->si_pid = from->si_pid;
to->si_uid = from->si_uid;
+ to->_sifields._sir._uid_r = from->si_uid_r;
to->si_int = from->si_int;
break;
case SIL_SYS:
@@ -3728,6 +3736,7 @@ static int post_copy_siginfo_from_user32(kernel_siginfo_t *to,
case SIL_KILL:
to->si_pid = from->si_pid;
to->si_uid = from->si_uid;
+ to->si_uid_r = from->_sifields._sir._uid_r;
break;
case SIL_TIMER:
to->si_tid = from->si_tid;
@@ -3767,6 +3776,7 @@ static int post_copy_siginfo_from_user32(kernel_siginfo_t *to,
case SIL_CHLD:
to->si_pid = from->si_pid;
to->si_uid = from->si_uid;
+ to->si_uid_r = from->_sifields._sir._uid_r;
to->si_status = from->si_status;
#ifdef CONFIG_X86_X32_ABI
if (in_x32_syscall()) {
@@ -3782,6 +3792,7 @@ static int post_copy_siginfo_from_user32(kernel_siginfo_t *to,
case SIL_RT:
to->si_pid = from->si_pid;
to->si_uid = from->si_uid;
+ to->si_uid_r = from->_sifields._sir._uid_r;
to->si_int = from->si_int;
break;
case SIL_SYS:
@@ -4019,7 +4030,7 @@ static void prepare_kill_siginfo(int sig, struct kernel_siginfo *info,
info->si_errno = 0;
info->si_code = (type == PIDTYPE_PID) ? SI_TKILL : SI_USER;
info->si_pid = task_tgid_vnr(current);
- info->si_uid = from_kuid_munged(current_user_ns(), current_uid());
+ siginfo_set_uid(info, current_user_ns(), current_uid());
}
/**
@@ -4991,6 +5002,12 @@ static inline void siginfo_buildtime_checks(void)
/* kill */
CHECK_OFFSET(si_pid);
CHECK_OFFSET(si_uid);
+ CHECK_OFFSET(si_uid_r);
+ BUILD_BUG_ON(offsetof(siginfo_t, si_uid_r) != __SI_UID_R_OFFSET);
+#ifdef CONFIG_COMPAT
+ BUILD_BUG_ON(offsetof(struct compat_siginfo, _sifields._sir._uid_r) !=
+ __SI_UID_R_OFFSET);
+#endif
/* timer */
CHECK_OFFSET(si_tid);
[-- Attachment #31: uid_r-0030-ipc-add-IPC_STAT_R-and-IPC_SET_R.patch --]
[-- Type: text/x-patch, Size: 21066 bytes --]
From 4f09f46d6e44650f8dba0cd827d8cc5ea98f6764 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 08:58:25 +0200
Subject: [PATCH 30/43] ipc: add IPC_STAT_R and IPC_SET_R
The ids of SysV message queues, semaphore sets and shared memory
segments reach userspace in struct ipc64_perm, 32 bits each, so a UUID
or GUID owner shows up as the overflow id and can't be set at all.
Rather than doubling every *id64_ds, add two commands shared by
msgctl(), semctl() and shmctl() that work on a struct ipc_perm_r with
key, mode, and uid, gid, cuid and cgid as __kernel_uid_r_t:
- IPC_STAT_R reads it, with the permission checks of IPC_STAT,
- IPC_SET_R sets uid, gid and mode like IPC_SET does (a message queue
keeps its size).
The IPC_SET path now works on the kernel ids of a struct ipc_set_ids,
which the classic command fills from its ipc64_perm. The audit record
of IPC_SET has the new owner as a kuid as well and logs it like the
other audit ids, in the initial user namespace.
Assisted-by: Claude:claude-opus-5-5
---
include/linux/audit.h | 8 +--
include/uapi/linux/ipc.h | 17 ++++++
ipc/msg.c | 40 ++++++++++----
ipc/sem.c | 31 ++++++++---
ipc/shm.c | 32 ++++++++---
ipc/util.c | 112 +++++++++++++++++++++++++++++++++++----
ipc/util.h | 23 +++++++-
kernel/audit.h | 4 +-
kernel/auditsc.c | 8 +--
9 files changed, 226 insertions(+), 49 deletions(-)
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 45abb3722..a769f04e4 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -428,7 +428,7 @@ static inline void audit_ptrace(struct task_struct *t)
/* Private API (for audit.c only) */
extern void __audit_ipc_obj(struct kern_ipc_perm *ipcp);
-extern void __audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, umode_t mode);
+extern void __audit_ipc_set_perm(unsigned long qbytes, kuid_t uid, kgid_t gid, umode_t mode);
extern void __audit_bprm(struct linux_binprm *bprm);
extern int __audit_socketcall(int nargs, unsigned long *args);
extern int __audit_sockaddr(int len, void *addr);
@@ -460,7 +460,7 @@ static inline void audit_fd_pair(int fd1, int fd2)
if (unlikely(!audit_dummy_context()))
__audit_fd_pair(fd1, fd2);
}
-static inline void audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, umode_t mode)
+static inline void audit_ipc_set_perm(unsigned long qbytes, kuid_t uid, kgid_t gid, umode_t mode)
{
if (unlikely(!audit_dummy_context()))
__audit_ipc_set_perm(qbytes, uid, gid, mode);
@@ -652,8 +652,8 @@ static inline void audit_seccomp_actions_logged(const char *names,
{ }
static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
{ }
-static inline void audit_ipc_set_perm(unsigned long qbytes, uid_t uid,
- gid_t gid, umode_t mode)
+static inline void audit_ipc_set_perm(unsigned long qbytes, kuid_t uid,
+ kgid_t gid, umode_t mode)
{ }
static inline void audit_bprm(struct linux_binprm *bprm)
{ }
diff --git a/include/uapi/linux/ipc.h b/include/uapi/linux/ipc.h
index 5995fc9d6..e927564fb 100644
--- a/include/uapi/linux/ipc.h
+++ b/include/uapi/linux/ipc.h
@@ -3,6 +3,7 @@
#define _UAPI_LINUX_IPC_H
#include <linux/types.h>
+#include <linux/uidgid_r.h>
#define IPC_PRIVATE ((__kernel_key_t) 0)
@@ -40,6 +41,22 @@ struct ipc_perm
#define IPC_SET 1 /* set ipc_perm options */
#define IPC_STAT 2 /* get ipc_perm options */
#define IPC_INFO 3 /* see ipcs */
+#define IPC_SET_R 21 /* set uid, gid and mode from struct ipc_perm_r */
+#define IPC_STAT_R 22 /* get struct ipc_perm_r */
+
+/*
+ * The ids and mode of an ipc object for IPC_STAT_R and IPC_SET_R, which
+ * also hold UUIDs and GUIDs; IPC_SET_R uses uid, gid and mode. The other
+ * attributes of the object come from IPC_STAT.
+ */
+struct ipc_perm_r {
+ __kernel_key_t key;
+ __u32 mode;
+ __kernel_uid_r_t uid;
+ __kernel_gid_r_t gid;
+ __kernel_uid_r_t cuid;
+ __kernel_gid_r_t cgid;
+};
/*
* Version flags for semctl, msgctl, and shmctl commands
diff --git a/ipc/msg.c b/ipc/msg.c
index 62996b97f..a3f5287e5 100644
--- a/ipc/msg.c
+++ b/ipc/msg.c
@@ -398,8 +398,10 @@ copy_msqid_from_user(struct msqid64_ds *out, void __user *buf, int version)
* to be held in write mode.
* NOTE: no locks must be held, the rwsem is taken inside this function.
*/
+/* IPC_SET_R is IPC_SET without a new queue size */
static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
- struct ipc64_perm *perm, int msg_qbytes)
+ const struct ipc_set_ids *set, int msg_qbytes,
+ bool set_qbytes)
{
struct kern_ipc_perm *ipcp;
struct msg_queue *msq;
@@ -409,7 +411,7 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
rcu_read_lock();
ipcp = ipcctl_obtain_check(ns, &msg_ids(ns), msqid, cmd,
- perm, msg_qbytes);
+ set, msg_qbytes);
if (IS_ERR(ipcp)) {
err = PTR_ERR(ipcp);
goto out_unlock1;
@@ -431,18 +433,19 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
{
DEFINE_WAKE_Q(wake_q);
- if (msg_qbytes > ns->msg_ctlmnb &&
+ if (set_qbytes && msg_qbytes > ns->msg_ctlmnb &&
!capable(CAP_SYS_RESOURCE)) {
err = -EPERM;
goto out_unlock1;
}
ipc_lock_object(&msq->q_perm);
- err = ipc_update_perm(perm, ipcp);
+ err = ipc_update_perm(set, ipcp);
if (err)
goto out_unlock0;
- msq->q_qbytes = msg_qbytes;
+ if (set_qbytes)
+ msq->q_qbytes = msg_qbytes;
msq->q_ctime = ktime_get_real_seconds();
/*
@@ -598,6 +601,7 @@ static long ksys_msgctl(int msqid, int cmd, struct msqid_ds __user *buf, int ver
{
struct ipc_namespace *ns;
struct msqid64_ds msqid64;
+ struct ipc_set_ids set;
int err;
if (msqid < 0 || cmd < 0)
@@ -628,10 +632,17 @@ static long ksys_msgctl(int msqid, int cmd, struct msqid_ds __user *buf, int ver
case IPC_SET:
if (copy_msqid_from_user(&msqid64, buf, version))
return -EFAULT;
- return msgctl_down(ns, msqid, cmd, &msqid64.msg_perm,
- msqid64.msg_qbytes);
+ ipc64_perm_to_set_ids(&msqid64.msg_perm, &set);
+ return msgctl_down(ns, msqid, cmd, &set, msqid64.msg_qbytes, true);
+ case IPC_SET_R:
+ if (ipc_perm_r_to_set_ids((void __user *)buf, &set))
+ return -EFAULT;
+ return msgctl_down(ns, msqid, IPC_SET, &set, 0, false);
+ case IPC_STAT_R:
+ return ipc_stat_r(ns, &msg_ids(ns), msqid,
+ security_msg_queue_msgctl, (void __user *)buf);
case IPC_RMID:
- return msgctl_down(ns, msqid, cmd, NULL, 0);
+ return msgctl_down(ns, msqid, cmd, NULL, 0, false);
default:
return -EINVAL;
}
@@ -734,6 +745,7 @@ static long compat_ksys_msgctl(int msqid, int cmd, void __user *uptr, int versio
struct ipc_namespace *ns;
int err;
struct msqid64_ds msqid64;
+ struct ipc_set_ids set;
ns = current->nsproxy->ipc_ns;
@@ -763,9 +775,17 @@ static long compat_ksys_msgctl(int msqid, int cmd, void __user *uptr, int versio
case IPC_SET:
if (copy_compat_msqid_from_user(&msqid64, uptr, version))
return -EFAULT;
- return msgctl_down(ns, msqid, cmd, &msqid64.msg_perm, msqid64.msg_qbytes);
+ ipc64_perm_to_set_ids(&msqid64.msg_perm, &set);
+ return msgctl_down(ns, msqid, cmd, &set, msqid64.msg_qbytes, true);
+ case IPC_SET_R:
+ if (ipc_perm_r_to_set_ids(uptr, &set))
+ return -EFAULT;
+ return msgctl_down(ns, msqid, IPC_SET, &set, 0, false);
+ case IPC_STAT_R:
+ return ipc_stat_r(ns, &msg_ids(ns), msqid,
+ security_msg_queue_msgctl, uptr);
case IPC_RMID:
- return msgctl_down(ns, msqid, cmd, NULL, 0);
+ return msgctl_down(ns, msqid, cmd, NULL, 0, false);
default:
return -EINVAL;
}
diff --git a/ipc/sem.c b/ipc/sem.c
index 5ec41de7e..ab456991e 100644
--- a/ipc/sem.c
+++ b/ipc/sem.c
@@ -1599,7 +1599,7 @@ copy_semid_from_user(struct semid64_ds *out, void __user *buf, int version)
* NOTE: no locks must be held, the rwsem is taken inside this function.
*/
static int semctl_down(struct ipc_namespace *ns, int semid,
- int cmd, struct semid64_ds *semid64)
+ int cmd, const struct ipc_set_ids *set)
{
struct sem_array *sma;
int err;
@@ -1608,8 +1608,7 @@ static int semctl_down(struct ipc_namespace *ns, int semid,
down_write(&sem_ids(ns).rwsem);
rcu_read_lock();
- ipcp = ipcctl_obtain_check(ns, &sem_ids(ns), semid, cmd,
- &semid64->sem_perm, 0);
+ ipcp = ipcctl_obtain_check(ns, &sem_ids(ns), semid, cmd, set, 0);
if (IS_ERR(ipcp)) {
err = PTR_ERR(ipcp);
goto out_unlock1;
@@ -1629,7 +1628,7 @@ static int semctl_down(struct ipc_namespace *ns, int semid,
goto out_up;
case IPC_SET:
sem_lock(sma, NULL, -1);
- err = ipc_update_perm(&semid64->sem_perm, ipcp);
+ err = ipc_update_perm(set, ipcp);
if (err)
goto out_unlock0;
sma->sem_ctime = ktime_get_real_seconds();
@@ -1653,6 +1652,7 @@ static long ksys_semctl(int semid, int semnum, int cmd, unsigned long arg, int v
struct ipc_namespace *ns;
void __user *p = (void __user *)arg;
struct semid64_ds semid64;
+ struct ipc_set_ids set;
int err;
if (semid < 0)
@@ -1694,9 +1694,16 @@ static long ksys_semctl(int semid, int semnum, int cmd, unsigned long arg, int v
case IPC_SET:
if (copy_semid_from_user(&semid64, p, version))
return -EFAULT;
- fallthrough;
+ ipc64_perm_to_set_ids(&semid64.sem_perm, &set);
+ return semctl_down(ns, semid, cmd, &set);
+ case IPC_SET_R:
+ if (ipc_perm_r_to_set_ids(p, &set))
+ return -EFAULT;
+ return semctl_down(ns, semid, IPC_SET, &set);
+ case IPC_STAT_R:
+ return ipc_stat_r(ns, &sem_ids(ns), semid, security_sem_semctl, p);
case IPC_RMID:
- return semctl_down(ns, semid, cmd, &semid64);
+ return semctl_down(ns, semid, cmd, NULL);
default:
return -EINVAL;
}
@@ -1776,6 +1783,7 @@ static long compat_ksys_semctl(int semid, int semnum, int cmd, int arg, int vers
void __user *p = compat_ptr(arg);
struct ipc_namespace *ns;
struct semid64_ds semid64;
+ struct ipc_set_ids set;
int err;
ns = current->nsproxy->ipc_ns;
@@ -1808,9 +1816,16 @@ static long compat_ksys_semctl(int semid, int semnum, int cmd, int arg, int vers
case IPC_SET:
if (copy_compat_semid_from_user(&semid64, p, version))
return -EFAULT;
- fallthrough;
+ ipc64_perm_to_set_ids(&semid64.sem_perm, &set);
+ return semctl_down(ns, semid, cmd, &set);
+ case IPC_SET_R:
+ if (ipc_perm_r_to_set_ids(p, &set))
+ return -EFAULT;
+ return semctl_down(ns, semid, IPC_SET, &set);
+ case IPC_STAT_R:
+ return ipc_stat_r(ns, &sem_ids(ns), semid, security_sem_semctl, p);
case IPC_RMID:
- return semctl_down(ns, semid, cmd, &semid64);
+ return semctl_down(ns, semid, cmd, NULL);
default:
return -EINVAL;
}
diff --git a/ipc/shm.c b/ipc/shm.c
index b3e8a58e1..dc50f800f 100644
--- a/ipc/shm.c
+++ b/ipc/shm.c
@@ -993,7 +993,7 @@ static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss,
* NOTE: no locks must be held, the rwsem is taken inside this function.
*/
static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
- struct shmid64_ds *shmid64)
+ const struct ipc_set_ids *set)
{
struct kern_ipc_perm *ipcp;
struct shmid_kernel *shp;
@@ -1002,8 +1002,7 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
down_write(&shm_ids(ns).rwsem);
rcu_read_lock();
- ipcp = ipcctl_obtain_check(ns, &shm_ids(ns), shmid, cmd,
- &shmid64->shm_perm, 0);
+ ipcp = ipcctl_obtain_check(ns, &shm_ids(ns), shmid, cmd, set, 0);
if (IS_ERR(ipcp)) {
err = PTR_ERR(ipcp);
goto out_unlock1;
@@ -1023,7 +1022,7 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
goto out_up;
case IPC_SET:
ipc_lock_object(&shp->shm_perm);
- err = ipc_update_perm(&shmid64->shm_perm, ipcp);
+ err = ipc_update_perm(set, ipcp);
if (err)
goto out_unlock0;
shp->shm_ctim = ktime_get_real_seconds();
@@ -1246,6 +1245,7 @@ static long ksys_shmctl(int shmid, int cmd, struct shmid_ds __user *buf, int ver
int err;
struct ipc_namespace *ns;
struct shmid64_ds sem64;
+ struct ipc_set_ids set;
if (cmd < 0 || shmid < 0)
return -EINVAL;
@@ -1284,9 +1284,17 @@ static long ksys_shmctl(int shmid, int cmd, struct shmid_ds __user *buf, int ver
case IPC_SET:
if (copy_shmid_from_user(&sem64, buf, version))
return -EFAULT;
- fallthrough;
+ ipc64_perm_to_set_ids(&sem64.shm_perm, &set);
+ return shmctl_down(ns, shmid, cmd, &set);
+ case IPC_SET_R:
+ if (ipc_perm_r_to_set_ids((void __user *)buf, &set))
+ return -EFAULT;
+ return shmctl_down(ns, shmid, IPC_SET, &set);
+ case IPC_STAT_R:
+ return ipc_stat_r(ns, &shm_ids(ns), shmid, security_shm_shmctl,
+ (void __user *)buf);
case IPC_RMID:
- return shmctl_down(ns, shmid, cmd, &sem64);
+ return shmctl_down(ns, shmid, cmd, NULL);
case SHM_LOCK:
case SHM_UNLOCK:
return shmctl_do_lock(ns, shmid, cmd);
@@ -1440,6 +1448,7 @@ static long compat_ksys_shmctl(int shmid, int cmd, void __user *uptr, int versio
{
struct ipc_namespace *ns;
struct shmid64_ds sem64;
+ struct ipc_set_ids set;
int err;
ns = current->nsproxy->ipc_ns;
@@ -1479,9 +1488,16 @@ static long compat_ksys_shmctl(int shmid, int cmd, void __user *uptr, int versio
case IPC_SET:
if (copy_compat_shmid_from_user(&sem64, uptr, version))
return -EFAULT;
- fallthrough;
+ ipc64_perm_to_set_ids(&sem64.shm_perm, &set);
+ return shmctl_down(ns, shmid, cmd, &set);
+ case IPC_SET_R:
+ if (ipc_perm_r_to_set_ids(uptr, &set))
+ return -EFAULT;
+ return shmctl_down(ns, shmid, IPC_SET, &set);
+ case IPC_STAT_R:
+ return ipc_stat_r(ns, &shm_ids(ns), shmid, security_shm_shmctl, uptr);
case IPC_RMID:
- return shmctl_down(ns, shmid, cmd, &sem64);
+ return shmctl_down(ns, shmid, cmd, NULL);
case SHM_LOCK:
case SHM_UNLOCK:
return shmctl_do_lock(ns, shmid, cmd);
diff --git a/ipc/util.c b/ipc/util.c
index 1737d776b..bc4df5619 100644
--- a/ipc/util.c
+++ b/ipc/util.c
@@ -681,28 +681,117 @@ int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
* @in: the permission given as input.
* @out: the permission of the ipc to set.
*/
-int ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out)
+int ipc_update_perm(const struct ipc_set_ids *in, struct kern_ipc_perm *out)
{
- kuid_t uid = make_kuid(current_user_ns(), in->uid);
- kgid_t gid = make_kgid(current_user_ns(), in->gid);
- if (!uid_valid(uid) || !gid_valid(gid))
- return -EINVAL;
+ if (in->err)
+ return in->err;
- out->uid = uid;
- out->gid = gid;
+ out->uid = in->uid;
+ out->gid = in->gid;
out->mode = (out->mode & ~S_IRWXUGO)
| (in->mode & S_IRWXUGO);
return 0;
}
+/**
+ * ipc64_perm_to_set_ids - the IPC_SET ids and mode of a classic ipc64_perm
+ * @in: user permissions
+ * @out: kernel ids and mode, -EINVAL in @out->err if an id is invalid
+ */
+void ipc64_perm_to_set_ids(const struct ipc64_perm *in, struct ipc_set_ids *out)
+{
+ out->uid = make_kuid(current_user_ns(), in->uid);
+ out->gid = make_kgid(current_user_ns(), in->gid);
+ out->mode = in->mode;
+ out->err = uid_valid(out->uid) && gid_valid(out->gid) ? 0 : -EINVAL;
+}
+
+/**
+ * ipc_perm_r_to_set_ids - the IPC_SET_R ids and mode of a struct ipc_perm_r
+ * @in: user permissions
+ * @out: kernel ids and mode, -EINVAL in @out->err if an id is invalid
+ *
+ * Return: 0 or -EFAULT.
+ */
+int ipc_perm_r_to_set_ids(const struct ipc_perm_r __user *in,
+ struct ipc_set_ids *out)
+{
+ struct ipc_perm_r perm;
+
+ if (copy_from_user(&perm, in, sizeof(perm)))
+ return -EFAULT;
+ out->uid = make_kuid_r(current_user_ns(), &perm.uid);
+ out->gid = make_kgid_r(current_user_ns(), &perm.gid);
+ out->mode = perm.mode;
+ out->err = uid_valid(out->uid) && gid_valid(out->gid) ? 0 : -EINVAL;
+ return 0;
+}
+
+/**
+ * ipc_stat_r - IPC_STAT_R of an ipc object
+ * @ns: ipc namespace
+ * @ids: the table of ids where to look for the object
+ * @id: the id of the object
+ * @security: the security hook of the object's kind, asked for IPC_STAT
+ * @buf: where to put the struct ipc_perm_r
+ *
+ * Return: 0 or a negative error code.
+ */
+int ipc_stat_r(struct ipc_namespace *ns, struct ipc_ids *ids, int id,
+ int (*security)(struct kern_ipc_perm *, int),
+ struct ipc_perm_r __user *buf)
+{
+ struct user_namespace *user_ns = current_user_ns();
+ struct kern_ipc_perm *ipcp;
+ struct ipc_perm_r perm;
+ int err;
+
+ memset(&perm, 0, sizeof(perm));
+ rcu_read_lock();
+ ipcp = ipc_obtain_object_check(ids, id);
+ if (IS_ERR(ipcp)) {
+ err = PTR_ERR(ipcp);
+ goto out_unlock;
+ }
+
+ err = -EACCES;
+ if (ipcperms(ns, ipcp, S_IRUGO))
+ goto out_unlock;
+
+ err = security(ipcp, IPC_STAT);
+ if (err)
+ goto out_unlock;
+
+ ipc_lock_object(ipcp);
+ if (!ipc_valid_object(ipcp)) {
+ ipc_unlock_object(ipcp);
+ err = -EIDRM;
+ goto out_unlock;
+ }
+ perm.key = ipcp->key;
+ perm.mode = ipcp->mode;
+ from_kuid_r_munged(user_ns, ipcp->uid, &perm.uid);
+ from_kgid_r_munged(user_ns, ipcp->gid, &perm.gid);
+ from_kuid_r_munged(user_ns, ipcp->cuid, &perm.cuid);
+ from_kgid_r_munged(user_ns, ipcp->cgid, &perm.cgid);
+ ipc_unlock_object(ipcp);
+ rcu_read_unlock();
+
+ return copy_to_user(buf, &perm, sizeof(perm)) ? -EFAULT : 0;
+
+out_unlock:
+ rcu_read_unlock();
+ return err;
+}
+
/**
* ipcctl_obtain_check - retrieve an ipc object and check permissions
* @ns: ipc namespace
* @ids: the table of ids where to look for the ipc
* @id: the id of the ipc to retrieve
* @cmd: the cmd to check
- * @perm: the permission to set
+ * @set: the ids and mode to set
* @extra_perm: one extra permission parameter used by msq
*
* This function does some common audit and permissions check for some IPC_XXX
@@ -718,7 +807,8 @@ int ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out)
*/
struct kern_ipc_perm *ipcctl_obtain_check(struct ipc_namespace *ns,
struct ipc_ids *ids, int id, int cmd,
- struct ipc64_perm *perm, int extra_perm)
+ const struct ipc_set_ids *set,
+ int extra_perm)
{
kuid_t euid;
int err = -EPERM;
@@ -732,8 +822,8 @@ struct kern_ipc_perm *ipcctl_obtain_check(struct ipc_namespace *ns,
audit_ipc_obj(ipcp);
if (cmd == IPC_SET)
- audit_ipc_set_perm(extra_perm, perm->uid,
- perm->gid, perm->mode);
+ audit_ipc_set_perm(extra_perm, set->uid,
+ set->gid, set->mode);
euid = current_euid();
if (uid_eq(euid, ipcp->cuid) || uid_eq(euid, ipcp->uid) ||
diff --git a/ipc/util.h b/ipc/util.h
index a55d6cebe..b0d57f4e6 100644
--- a/ipc/util.h
+++ b/ipc/util.h
@@ -177,10 +177,29 @@ struct kern_ipc_perm *ipc_obtain_object_idr(struct ipc_ids *ids, int id);
void kernel_to_ipc64_perm(struct kern_ipc_perm *in, struct ipc64_perm *out);
void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out);
-int ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out);
+
+/*
+ * The new owner, group and mode of IPC_SET/IPC_SET_R. @err is an error of
+ * their conversion, reported once the object has been found.
+ */
+struct ipc_set_ids {
+ kuid_t uid;
+ kgid_t gid;
+ umode_t mode;
+ int err;
+};
+
+void ipc64_perm_to_set_ids(const struct ipc64_perm *in, struct ipc_set_ids *out);
+int ipc_perm_r_to_set_ids(const struct ipc_perm_r __user *in,
+ struct ipc_set_ids *out);
+int ipc_update_perm(const struct ipc_set_ids *in, struct kern_ipc_perm *out);
struct kern_ipc_perm *ipcctl_obtain_check(struct ipc_namespace *ns,
struct ipc_ids *ids, int id, int cmd,
- struct ipc64_perm *perm, int extra_perm);
+ const struct ipc_set_ids *set,
+ int extra_perm);
+int ipc_stat_r(struct ipc_namespace *ns, struct ipc_ids *ids, int id,
+ int (*security)(struct kern_ipc_perm *, int),
+ struct ipc_perm_r __user *buf);
static inline void ipc_update_pid(struct pid **pos, struct pid *pid)
{
diff --git a/kernel/audit.h b/kernel/audit.h
index 92d5e723d..6195f1388 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -168,8 +168,8 @@ struct audit_context {
umode_t mode;
struct lsm_prop oprop;
int has_perm;
- uid_t perm_uid;
- gid_t perm_gid;
+ kuid_t perm_uid;
+ kgid_t perm_gid;
umode_t perm_mode;
unsigned long qbytes;
} ipc;
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index ed35bdfd7..518eb0b4e 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -1395,10 +1395,10 @@ static void show_special(struct audit_context *context, int *call_panic)
if (unlikely(!ab))
return;
audit_log_format(ab,
- "qbytes=%lx ouid=%u ogid=%u mode=%#ho",
+ "qbytes=%lx ouid=%s ogid=%s mode=%#ho",
context->ipc.qbytes,
- context->ipc.perm_uid,
- context->ipc.perm_gid,
+ KUID_STR(context->ipc.perm_uid),
+ KGID_STR(context->ipc.perm_gid),
context->ipc.perm_mode);
}
break;
@@ -2585,7 +2585,7 @@ void __audit_ipc_obj(struct kern_ipc_perm *ipcp)
*
* Called only after audit_ipc_obj().
*/
-void __audit_ipc_set_perm(unsigned long qbytes, uid_t uid, gid_t gid, umode_t mode)
+void __audit_ipc_set_perm(unsigned long qbytes, kuid_t uid, kgid_t gid, umode_t mode)
{
struct audit_context *context = audit_context();
[-- Attachment #32: uid_r-0031-ext4-add-the-uid_r-feature-for-UUID-and-GUID-owners.patch --]
[-- Type: text/x-patch, Size: 11045 bytes --]
From fc5d29ea3c2bb44d9b8c547d79d06eda12d3da29 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:18:36 +0200
Subject: [PATCH 31/43] ext4: add the uid_r feature for UUID and GUID owners
Add a read-only compatible feature, uid_r, that lets inodes have UUID
and GUID owners and groups. The inode gains i_uid_r_type and
i_gid_r_type (0: the classic i_uid/i_gid apply) and 16 bytes for each
id after i_projid, which grows i_extra_isize from 32 to 68 bytes; only
filesystems with the feature reserve the room in new inodes, others
keep the old size.
For a UUID or GUID the classic fields hold 0xffffffff. Kernels without
the feature may only mount read-only, and see such files as owned by an
id nobody has.
With the feature the superblock gets SB_I_UID_R. Inodes from before it,
or with in-inode xattrs in the way, are expanded when they get such an
owner, as for project ids; -EOVERFLOW if that fails. The feature needs
inodes of at least 196 bytes, so in practice 256.
Assisted-by: Claude:claude-opus-5-5
---
fs/ext4/ext4.h | 26 ++++++++++-
fs/ext4/inode.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++-
fs/ext4/super.c | 16 ++++++-
3 files changed, 152 insertions(+), 5 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index ed4241797..ff82a054a 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -850,8 +850,29 @@ struct ext4_inode {
__le32 i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
__le32 i_version_hi; /* high 32 bits for 64-bit version */
__le32 i_projid; /* Project ID */
+ /* uid_r feature: a UUID or GUID owner and group, see EXT4_ID_R_* */
+ __u8 i_uid_r_type; /* kind of i_uid_r, EXT4_ID_R_LEGACY: i_uid */
+ __u8 i_gid_r_type; /* kind of i_gid_r, EXT4_ID_R_LEGACY: i_gid */
+ __le16 i_id_r_reserved;
+ __u8 i_uid_r[16]; /* UUID (RFC 9562 order) or GUID owner */
+ __u8 i_gid_r[16]; /* UUID (RFC 9562 order) or GUID group */
};
+/*
+ * Kinds of i_uid_r/i_gid_r. For a UUID or GUID the classic i_uid/i_gid
+ * fields hold 0xffffffff, which kernels without uid_r -- they may only
+ * mount read-only -- see as an id nobody has.
+ */
+#define EXT4_ID_R_LEGACY 0
+#define EXT4_ID_R_UUID 1
+#define EXT4_ID_R_GUID 2
+
+/* i_extra_isize of inodes without and with the uid_r fields */
+#define EXT4_EXTRA_ISIZE_BASE (offsetofend(struct ext4_inode, i_projid) - \
+ EXT4_GOOD_OLD_INODE_SIZE)
+#define EXT4_EXTRA_ISIZE_UID_R (sizeof(struct ext4_inode) - \
+ EXT4_GOOD_OLD_INODE_SIZE)
+
#define EXT4_EPOCH_BITS 2
#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
#define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
@@ -2192,6 +2213,7 @@ static inline bool ext4_inode_orphan_tracked(struct inode *inode)
#define EXT4_FEATURE_RO_COMPAT_VERITY 0x8000
#define EXT4_FEATURE_RO_COMPAT_ORPHAN_PRESENT 0x10000 /* Orphan file may be
non-empty */
+#define EXT4_FEATURE_RO_COMPAT_UID_R 0x20000 /* UUID and GUID owners */
#define EXT4_FEATURE_INCOMPAT_COMPRESSION 0x0001
#define EXT4_FEATURE_INCOMPAT_FILETYPE 0x0002
@@ -2291,6 +2313,7 @@ EXT4_FEATURE_RO_COMPAT_FUNCS(readonly, READONLY)
EXT4_FEATURE_RO_COMPAT_FUNCS(project, PROJECT)
EXT4_FEATURE_RO_COMPAT_FUNCS(verity, VERITY)
EXT4_FEATURE_RO_COMPAT_FUNCS(orphan_present, ORPHAN_PRESENT)
+EXT4_FEATURE_RO_COMPAT_FUNCS(uid_r, UID_R)
EXT4_FEATURE_INCOMPAT_FUNCS(compression, COMPRESSION)
EXT4_FEATURE_INCOMPAT_FUNCS(filetype, FILETYPE)
@@ -2351,7 +2374,8 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold, CASEFOLD)
EXT4_FEATURE_RO_COMPAT_QUOTA |\
EXT4_FEATURE_RO_COMPAT_PROJECT |\
EXT4_FEATURE_RO_COMPAT_VERITY |\
- EXT4_FEATURE_RO_COMPAT_ORPHAN_PRESENT)
+ EXT4_FEATURE_RO_COMPAT_ORPHAN_PRESENT |\
+ EXT4_FEATURE_RO_COMPAT_UID_R)
#define EXTN_FEATURE_FUNCS(ver) \
static inline bool ext4_has_unknown_ext##ver##_compat_features(struct super_block *sb) \
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 32f79f48a..869e072b8 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4773,6 +4773,75 @@ static int ext4_inode_blocks_set(struct ext4_inode *raw_inode,
return 0;
}
+/*
+ * uid_r: a UUID or GUID owner and group live in i_uid_r and i_gid_r of
+ * inodes with room for them; i_uid and i_gid then hold 0xffffffff.
+ */
+static int ext4_inode_set_id_r(struct inode *inode, struct ext4_inode *raw_inode)
+{
+ struct ext4_inode_info *ei = EXT4_I(inode);
+ bool fits = EXT4_INODE_SIZE(inode->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
+ EXT4_FITS_IN_INODE(raw_inode, ei, i_gid_r);
+ bool legacy = uid_is_legacy(inode->i_uid) && gid_is_legacy(inode->i_gid);
+
+ if (!legacy && (!ext4_has_feature_uid_r(inode->i_sb) || !fits))
+ return -EOVERFLOW;
+ if (!fits || !ext4_has_feature_uid_r(inode->i_sb))
+ return 0;
+
+ raw_inode->i_id_r_reserved = 0;
+ if (uid_is_legacy(inode->i_uid)) {
+ raw_inode->i_uid_r_type = EXT4_ID_R_LEGACY;
+ memset(raw_inode->i_uid_r, 0, sizeof(raw_inode->i_uid_r));
+ } else {
+ BUILD_BUG_ON(KID_UUID != EXT4_ID_R_UUID ||
+ KID_GUID != EXT4_ID_R_GUID);
+ raw_inode->i_uid_r_type = inode->i_uid.type;
+ memcpy(raw_inode->i_uid_r, inode->i_uid.uuid.b,
+ sizeof(raw_inode->i_uid_r));
+ }
+ if (gid_is_legacy(inode->i_gid)) {
+ raw_inode->i_gid_r_type = EXT4_ID_R_LEGACY;
+ memset(raw_inode->i_gid_r, 0, sizeof(raw_inode->i_gid_r));
+ } else {
+ raw_inode->i_gid_r_type = inode->i_gid.type;
+ memcpy(raw_inode->i_gid_r, inode->i_gid.uuid.b,
+ sizeof(raw_inode->i_gid_r));
+ }
+ return 0;
+}
+
+static int ext4_inode_get_id_r(struct inode *inode, struct ext4_inode *raw_inode)
+{
+ struct ext4_inode_info *ei = EXT4_I(inode);
+ uuid_t id;
+
+ if (!ext4_has_feature_uid_r(inode->i_sb) ||
+ EXT4_INODE_SIZE(inode->i_sb) <= EXT4_GOOD_OLD_INODE_SIZE ||
+ !EXT4_FITS_IN_INODE(raw_inode, ei, i_gid_r))
+ return 0;
+
+ if (raw_inode->i_uid_r_type > EXT4_ID_R_GUID ||
+ raw_inode->i_gid_r_type > EXT4_ID_R_GUID) {
+ ext4_error_inode(inode, __func__, __LINE__, 0,
+ "iget: bad uid_r kind %u/%u",
+ raw_inode->i_uid_r_type,
+ raw_inode->i_gid_r_type);
+ return -EFSCORRUPTED;
+ }
+ if (raw_inode->i_uid_r_type != EXT4_ID_R_LEGACY) {
+ memcpy(id.b, raw_inode->i_uid_r, sizeof(id.b));
+ inode->i_uid = raw_inode->i_uid_r_type == EXT4_ID_R_UUID ?
+ KUIDT_INIT_UUID(id) : KUIDT_INIT_GUID(*(guid_t *)&id);
+ }
+ if (raw_inode->i_gid_r_type != EXT4_ID_R_LEGACY) {
+ memcpy(id.b, raw_inode->i_gid_r, sizeof(id.b));
+ inode->i_gid = raw_inode->i_gid_r_type == EXT4_ID_R_UUID ?
+ KGIDT_INIT_UUID(id) : KGIDT_INIT_GUID(*(guid_t *)&id);
+ }
+ return 0;
+}
+
static int ext4_fill_raw_inode(struct inode *inode, struct ext4_inode *raw_inode)
{
struct ext4_inode_info *ei = EXT4_I(inode);
@@ -4785,8 +4854,10 @@ static int ext4_fill_raw_inode(struct inode *inode, struct ext4_inode *raw_inode
err = ext4_inode_blocks_set(raw_inode, ei);
raw_inode->i_mode = cpu_to_le16(inode->i_mode);
+ /* a UUID or GUID owner: 0xffffffff here, the id in i_uid_r */
i_uid = i_uid_read(inode);
i_gid = i_gid_read(inode);
+ err = err ?: ext4_inode_set_id_r(inode, raw_inode);
i_projid = from_kprojid(&init_user_ns, ei->i_projid);
if (!(test_opt(inode->i_sb, NO_UID32))) {
raw_inode->i_uid_low = cpu_to_le16(low_16_bits(i_uid));
@@ -5429,6 +5500,9 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
}
i_uid_write(inode, i_uid);
i_gid_write(inode, i_gid);
+ ret = ext4_inode_get_id_r(inode, raw_inode);
+ if (ret)
+ goto bad_inode;
ei->i_projid = make_kprojid(&init_user_ns, i_projid);
set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
@@ -5541,8 +5615,7 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
if (ei->i_extra_isize == 0) {
/* The extra space is currently unused. Use it. */
BUILD_BUG_ON(sizeof(struct ext4_inode) & 3);
- ei->i_extra_isize = sizeof(struct ext4_inode) -
- EXT4_GOOD_OLD_INODE_SIZE;
+ ei->i_extra_isize = EXT4_SB(inode->i_sb)->s_want_extra_isize;
} else {
ret = ext4_iget_extra_inode(inode, raw_inode, ei);
if (ret)
@@ -5984,6 +6057,40 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode)
*
* Called with inode->i_rwsem down.
*/
+/*
+ * An inode from before uid_r, or with in-inode xattrs in the way, may
+ * lack room for i_uid_r and i_gid_r: make it, before it gets a UUID or
+ * GUID owner or group.
+ */
+static int ext4_make_room_for_id_r(struct mnt_idmap *idmap,
+ struct inode *inode, struct iattr *attr)
+{
+ struct ext4_iloc iloc;
+ bool legacy = true;
+ int err;
+
+ if (attr->ia_valid & ATTR_UID)
+ legacy = uid_is_legacy(from_vfsuid(idmap, i_user_ns(inode),
+ attr->ia_vfsuid));
+ if (attr->ia_valid & ATTR_GID)
+ legacy &= gid_is_legacy(from_vfsgid(idmap, i_user_ns(inode),
+ attr->ia_vfsgid));
+ if (legacy)
+ return 0;
+ if (!ext4_has_feature_uid_r(inode->i_sb))
+ return -EOVERFLOW;
+
+ err = ext4_get_inode_loc(inode, &iloc);
+ if (err)
+ return err;
+ if (EXT4_FITS_IN_INODE(ext4_raw_inode(&iloc), EXT4_I(inode), i_gid_r)) {
+ brelse(iloc.bh);
+ return 0;
+ }
+ err = ext4_expand_extra_isize(inode, EXT4_EXTRA_ISIZE_UID_R, &iloc);
+ return err ? -EOVERFLOW : 0;
+}
+
int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr)
{
@@ -6023,6 +6130,10 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
i_gid_needs_update(idmap, attr, inode)) {
handle_t *handle;
+ error = ext4_make_room_for_id_r(idmap, inode, attr);
+ if (error)
+ return error;
+
/* (user+group)*(old+new) structure, inode write (sb,
* inode block, ? - but truncate inode update has it) */
handle = ext4_journal_start(inode, EXT4_HT_QUOTA,
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 63de62a75..6e034672f 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -4596,9 +4596,17 @@ static int ext4_inode_info_init(struct super_block *sb,
sb->s_time_min = EXT4_TIMESTAMP_MIN;
}
+ if (ext4_has_feature_uid_r(sb) &&
+ sbi->s_inode_size < EXT4_GOOD_OLD_INODE_SIZE + EXT4_EXTRA_ISIZE_UID_R) {
+ ext4_msg(sb, KERN_ERR, "uid_r needs inodes of at least %zu bytes",
+ EXT4_GOOD_OLD_INODE_SIZE + EXT4_EXTRA_ISIZE_UID_R);
+ return -EINVAL;
+ }
+
if (sbi->s_inode_size > EXT4_GOOD_OLD_INODE_SIZE) {
- sbi->s_want_extra_isize = sizeof(struct ext4_inode) -
- EXT4_GOOD_OLD_INODE_SIZE;
+ /* room for the uid_r fields in new inodes only if needed */
+ sbi->s_want_extra_isize = ext4_has_feature_uid_r(sb) ?
+ EXT4_EXTRA_ISIZE_UID_R : EXT4_EXTRA_ISIZE_BASE;
if (ext4_has_feature_extra_isize(sb)) {
unsigned v, max = (sbi->s_inode_size -
EXT4_GOOD_OLD_INODE_SIZE);
@@ -5417,6 +5425,10 @@ static int __ext4_fill_super(struct fs_context *fc, struct super_block *sb)
/* HSM events are allowed by default. */
sb->s_iflags |= SB_I_ALLOW_HSM;
+ /* uid_r: inodes can have UUID and GUID owners */
+ if (ext4_has_feature_uid_r(sb) && !test_opt(sb, NO_UID32))
+ sb->s_iflags |= SB_I_UID_R;
+
err = ext4_check_feature_compatibility(sb, es, silent);
if (err)
goto failed_mount;
[-- Attachment #33: uid_r-0032-ext4-let-uid_r-be-turned-on-while-the-filesystem-is-.patch --]
[-- Type: text/x-patch, Size: 5664 bytes --]
From 0cd5682e8a7b9ccab4a77308d7ef8e267a39e5a4 Mon Sep 17 00:00:00 2001
From: Jan Koester <jan.koester@tuxist.de>
Date: Sun, 11 Oct 2026 06:36:04 +0200
Subject: [PATCH 32/43] ext4: let uid_r be turned on while the filesystem is
mounted
SB_I_UID_R was only set when the filesystem was mounted, so after
"tune2fs -O uid_r" on a mounted filesystem chown() to a UUID or GUID
owner kept failing with -EOVERFLOW until it was unmounted and mounted
again.
Take the feature up on remount, and accept it in
EXT4_IOC_SET_TUNE_SB_PARAM, which then also raises s_want_extra_isize
like tune2fs does and makes it usable at once. The ioctl also takes it
when the feature bit is already on disk but not in use yet, which is
the case after an older tune2fs wrote the superblock directly.
Once on, it stays on until the filesystem is unmounted: cached inodes
may have such owners by then.
Assisted-by: Claude:claude-opus-5-5
---
fs/ext4/ext4.h | 1 +
fs/ext4/ioctl.c | 30 +++++++++++++++++++++++++++++-
fs/ext4/super.c | 29 +++++++++++++++++++++++++++++
3 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index ff82a054a..fec9ed52f 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3297,6 +3297,7 @@ extern struct buffer_head *ext4_sb_bread_unmovable(struct super_block *sb,
sector_t block);
extern struct buffer_head *ext4_sb_bread_nofail(struct super_block *sb,
sector_t block);
+void ext4_enable_uid_r(struct super_block *sb);
void ext4_read_bh_nowait(struct buffer_head *bh, blk_opf_t op_flags,
bio_end_io_t end_io, bool simu_fail);
int ext4_read_bh(struct buffer_head *bh, blk_opf_t op_flags,
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c
index c8387e6a2..89bf1d36e 100644
--- a/fs/ext4/ioctl.c
+++ b/fs/ext4/ioctl.c
@@ -1269,7 +1269,8 @@ static int ext4_ioctl_setuuid(struct file *filp,
EXT4_FEATURE_RO_COMPAT_DIR_NLINK | \
EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE | \
EXT4_FEATURE_RO_COMPAT_PROJECT | \
- EXT4_FEATURE_RO_COMPAT_VERITY)
+ EXT4_FEATURE_RO_COMPAT_VERITY | \
+ EXT4_FEATURE_RO_COMPAT_UID_R)
#define EXT4_TUNE_CLEAR_COMPAT_SUPP (0)
#define EXT4_TUNE_CLEAR_INCOMPAT_SUPP (0)
@@ -1382,6 +1383,13 @@ static void ext4_sb_setparams(struct ext4_sb_info *sbi,
if (params->set_feature_incompat_mask &
EXT4_FEATURE_INCOMPAT_CSUM_SEED)
es->s_checksum_seed = cpu_to_le32(sbi->s_csum_seed);
+ /* as tune2fs does: room for the uid_r fields in new inodes */
+ if ((params->set_feature_ro_compat_mask &
+ EXT4_FEATURE_RO_COMPAT_UID_R) &&
+ ext4_has_feature_extra_isize(sbi->s_sb) &&
+ le16_to_cpu(es->s_want_extra_isize) < EXT4_EXTRA_ISIZE_UID_R)
+ es->s_want_extra_isize =
+ cpu_to_le16(EXT4_EXTRA_ISIZE_UID_R);
}
if (params->set_flags & EXT4_TUNE_FL_FORCE_FSCK)
es->s_state |= cpu_to_le16(EXT4_ERROR_FS);
@@ -1395,6 +1403,7 @@ static int ext4_ioctl_set_tune_sb(struct file *filp,
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct ext4_super_block *es = sbi->s_es;
int enabling_casefold = 0;
+ bool enabling_uid_r = false;
int ret;
if (!capable(CAP_SYS_ADMIN))
@@ -1461,6 +1470,18 @@ static int ext4_ioctl_set_tune_sb(struct file *filp,
~EXT4_TUNE_CLEAR_RO_COMPAT_SUPP))
return -EOPNOTSUPP;
+ /*
+ * uid_r may already be on disk but not in use yet, when it
+ * was set while the filesystem was mounted: take that up too.
+ */
+ if (params.set_feature_ro_compat_mask &
+ EXT4_FEATURE_RO_COMPAT_UID_R) {
+ if (sbi->s_inode_size < EXT4_GOOD_OLD_INODE_SIZE +
+ EXT4_EXTRA_ISIZE_UID_R)
+ return -EINVAL;
+ enabling_uid_r = true;
+ }
+
/*
* Filter out the features that are already set from
* the set_mask.
@@ -1532,6 +1553,13 @@ static int ext4_ioctl_set_tune_sb(struct file *filp,
if (params.set_flags & EXT4_TUNE_FL_DEF_HASH_ALG)
sbi->s_def_hash_version = params.def_hash_alg;
+ /* not under mnt_want_write_file(), freeze_super() would wait for us */
+ if (!ret && enabling_uid_r) {
+ down_write(&sb->s_umount);
+ ext4_enable_uid_r(sb);
+ up_write(&sb->s_umount);
+ }
+
return ret;
}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 6e034672f..5393a500f 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -6551,6 +6551,32 @@ static int ext4_unfreeze(struct super_block *sb)
return 0;
}
+/*
+ * Let the filesystem take UUID and GUID owners when the uid_r feature was
+ * turned on after the mount, by a remount or EXT4_IOC_SET_TUNE_SB_PARAM.
+ * It is never turned off again, as cached inodes may have such owners by
+ * now. Called with s_umount held for writing.
+ */
+void ext4_enable_uid_r(struct super_block *sb)
+{
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+
+ if (!ext4_has_feature_uid_r(sb) || test_opt(sb, NO_UID32) ||
+ (sb->s_iflags & SB_I_UID_R))
+ return;
+ if (sbi->s_inode_size <
+ EXT4_GOOD_OLD_INODE_SIZE + EXT4_EXTRA_ISIZE_UID_R) {
+ ext4_msg(sb, KERN_WARNING,
+ "uid_r needs inodes of at least %zu bytes, not enabled",
+ EXT4_GOOD_OLD_INODE_SIZE + EXT4_EXTRA_ISIZE_UID_R);
+ return;
+ }
+ if (sbi->s_want_extra_isize < EXT4_EXTRA_ISIZE_UID_R)
+ WRITE_ONCE(sbi->s_want_extra_isize, EXT4_EXTRA_ISIZE_UID_R);
+ sb->s_iflags |= SB_I_UID_R;
+ ext4_msg(sb, KERN_INFO, "uid_r enabled");
+}
+
/*
* Structure to save mount options for ext4_remount's benefit
*/
@@ -6832,6 +6858,9 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb)
if (!test_opt(sb, BLOCK_VALIDITY) && sbi->s_system_blks)
ext4_release_system_zone(sb);
+ /* tune2fs may have turned on uid_r since the mount */
+ ext4_enable_uid_r(sb);
+
/*
* Reinitialize lazy itable initialization thread based on
* current settings
[-- Attachment #34: uid_r-0033-posix_acl-support-UUID-and-GUID-entries.patch --]
[-- Type: text/x-patch, Size: 8910 bytes --]
From cebc5009d5b7bc5cbf7ac2cc2d4c31fa7745df40 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:42:13 +0200
Subject: [PATCH 33/43] posix_acl: support UUID and GUID entries
The ACL xattrs carry 32 bit ids, so ACL_USER and ACL_GROUP entries for
UUID and GUID users and groups could neither be set nor shown.
Add version 3 of the xattr format, whose entries hold an id kind and
16 bytes of id. setxattr() takes version 2 and 3; getxattr() returns
version 3 only if the ACL has such an entry the caller can see, so
existing tools keep getting version 2 for everything else.
set_posix_acl() refuses UUID and GUID entries with -EOVERFLOW on
filesystems without SB_I_UID_R, whose ACL formats can't store them.
Assisted-by: Claude:claude-opus-5-5
---
fs/posix_acl.c | 123 ++++++++++++++++++++++++++-
include/linux/posix_acl_xattr.h | 31 +++++++
include/uapi/linux/posix_acl_xattr.h | 19 +++++
3 files changed, 171 insertions(+), 2 deletions(-)
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 3dc62c1c2..dd9fd6879 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -19,6 +19,8 @@
#include <linux/cred.h>
#include <linux/posix_acl.h>
#include <linux/posix_acl_xattr.h>
+#include <linux/uidgid_r.h>
+#include <linux/unaligned.h>
#include <linux/xattr.h>
#include <linux/export.h>
#include <linux/user_namespace.h>
@@ -741,10 +743,13 @@ static int posix_acl_fix_xattr_common(const void *value, size_t size)
return -EINVAL;
if (size < sizeof(struct posix_acl_xattr_header))
return -EINVAL;
- if (header->a_version != cpu_to_le32(POSIX_ACL_XATTR_VERSION))
+ if (header->a_version == cpu_to_le32(POSIX_ACL_XATTR_VERSION_R))
+ count = posix_acl_xattr_count_r(size);
+ else if (header->a_version == cpu_to_le32(POSIX_ACL_XATTR_VERSION))
+ count = posix_acl_xattr_count(size);
+ else
return -EOPNOTSUPP;
- count = posix_acl_xattr_count(size);
if (count < 0)
return -EINVAL;
if (count == 0)
@@ -753,6 +758,49 @@ static int posix_acl_fix_xattr_common(const void *value, size_t size)
return count;
}
+/* The entries of a version 3 ACL xattr into @acl; -EINVAL if malformed */
+static int posix_acl_from_xattr_r(struct user_namespace *userns,
+ const struct posix_acl_xattr_entry_r *entry,
+ struct posix_acl *acl)
+{
+ struct __kernel_uid_r id;
+
+ for (int n = 0; n < acl->a_count; n++, entry++) {
+ struct posix_acl_entry *acl_e = &acl->a_entries[n];
+
+ acl_e->e_tag = le16_to_cpu(entry->e_tag);
+ acl_e->e_perm = le16_to_cpu(entry->e_perm);
+
+ memset(&id, 0, sizeof(id));
+ id.type = le32_to_cpu(entry->e_type);
+ if (id.type == UID_R_LEGACY)
+ id.legacy = get_unaligned_le32(entry->e_id);
+ else
+ memcpy(id.uuid, entry->e_id, sizeof(id.uuid));
+
+ switch (acl_e->e_tag) {
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ break;
+ case ACL_USER:
+ acl_e->e_uid = make_kuid_r(userns, &id);
+ if (!uid_valid(acl_e->e_uid))
+ return -EINVAL;
+ break;
+ case ACL_GROUP:
+ acl_e->e_gid = make_kgid_r(userns, &id);
+ if (!gid_valid(acl_e->e_gid))
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
/**
* posix_acl_from_xattr - convert POSIX ACLs from backing store to VFS format
* @userns: the filesystem's idmapping
@@ -797,6 +845,13 @@ struct posix_acl *posix_acl_from_xattr(struct user_namespace *userns,
return ERR_PTR(-ENOMEM);
acl_e = acl->a_entries;
+ if (header->a_version == cpu_to_le32(POSIX_ACL_XATTR_VERSION_R)) {
+ if (posix_acl_from_xattr_r(userns, (const void *)(header + 1),
+ acl))
+ goto fail;
+ return acl;
+ }
+
for (end = entry + count; entry != end; acl_e++, entry++) {
acl_e->e_tag = le16_to_cpu(entry->e_tag);
acl_e->e_perm = le16_to_cpu(entry->e_perm);
@@ -876,6 +931,61 @@ posix_acl_to_xattr(struct user_namespace *user_ns, const struct posix_acl *acl,
}
EXPORT_SYMBOL (posix_acl_to_xattr);
+/* Like vfs_posix_acl_to_xattr(), in version 3 */
+static ssize_t vfs_posix_acl_to_xattr_r(struct mnt_idmap *idmap,
+ struct inode *inode,
+ const struct posix_acl *acl,
+ void *buffer, size_t size)
+{
+ struct posix_acl_xattr_header *ext_acl = buffer;
+ struct posix_acl_xattr_entry_r *ext_entry;
+ struct user_namespace *fs_userns = i_user_ns(inode);
+ struct user_namespace *caller_userns = current_user_ns();
+ struct __kernel_uid_r id;
+ ssize_t real_size;
+ bool mapped;
+
+ real_size = posix_acl_xattr_size_r(acl->a_count);
+ if (!buffer)
+ return real_size;
+ if (real_size > size)
+ return -ERANGE;
+
+ ext_entry = (void *)(ext_acl + 1);
+ ext_acl->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION_R);
+
+ for (int n = 0; n < acl->a_count; n++, ext_entry++) {
+ const struct posix_acl_entry *acl_e = &acl->a_entries[n];
+
+ ext_entry->e_tag = cpu_to_le16(acl_e->e_tag);
+ ext_entry->e_perm = cpu_to_le16(acl_e->e_perm);
+ memset(&id, 0, sizeof(id));
+ mapped = true;
+ switch (acl_e->e_tag) {
+ case ACL_USER:
+ mapped = from_kuid_r(caller_userns, vfsuid_into_kuid(
+ make_vfsuid(idmap, fs_userns, acl_e->e_uid)), &id);
+ break;
+ case ACL_GROUP:
+ mapped = from_kgid_r(caller_userns, vfsgid_into_kgid(
+ make_vfsgid(idmap, fs_userns, acl_e->e_gid)), &id);
+ break;
+ }
+ /* no mapping: the classic -1, as in version 2 */
+ if (!mapped) {
+ id.type = UID_R_LEGACY;
+ id.legacy = ACL_UNDEFINED_ID;
+ }
+ ext_entry->e_type = cpu_to_le32(id.type);
+ memset(ext_entry->e_id, 0, sizeof(ext_entry->e_id));
+ if (id.type == UID_R_LEGACY)
+ put_unaligned_le32(id.legacy, ext_entry->e_id);
+ else if (id.type != UID_R_NONE)
+ memcpy(ext_entry->e_id, id.uuid, sizeof(ext_entry->e_id));
+ }
+ return real_size;
+}
+
/**
* vfs_posix_acl_to_xattr - convert from kernel to userspace representation
* @idmap: idmap of the mount
@@ -904,6 +1014,11 @@ static ssize_t vfs_posix_acl_to_xattr(struct mnt_idmap *idmap,
vfsuid_t vfsuid;
vfsgid_t vfsgid;
+ /* UUIDs and GUIDs the caller can see need version 3 */
+ if (posix_acl_has_id_r(acl) && current_user_ns() == &init_user_ns)
+ return vfs_posix_acl_to_xattr_r(idmap, inode, acl, buffer,
+ size);
+
real_size = posix_acl_xattr_size(acl->a_count);
if (!buffer)
return real_size;
@@ -958,6 +1073,10 @@ set_posix_acl(struct mnt_idmap *idmap, struct dentry *dentry,
int ret = posix_acl_valid(inode->i_sb->s_user_ns, acl);
if (ret)
return ret;
+ /* UUID and GUID entries only where the filesystem stores them */
+ if (posix_acl_has_id_r(acl) &&
+ !(inode->i_sb->s_iflags & SB_I_UID_R))
+ return -EOVERFLOW;
}
return inode->i_op->set_acl(idmap, dentry, acl, type);
}
diff --git a/include/linux/posix_acl_xattr.h b/include/linux/posix_acl_xattr.h
index 9e1892525..307d543bc 100644
--- a/include/linux/posix_acl_xattr.h
+++ b/include/linux/posix_acl_xattr.h
@@ -32,6 +32,37 @@ posix_acl_xattr_count(size_t size)
return size / sizeof(struct posix_acl_xattr_entry);
}
+static inline size_t
+posix_acl_xattr_size_r(int count)
+{
+ return (sizeof(struct posix_acl_xattr_header) +
+ (count * sizeof(struct posix_acl_xattr_entry_r)));
+}
+
+static inline int
+posix_acl_xattr_count_r(size_t size)
+{
+ if (size < sizeof(struct posix_acl_xattr_header))
+ return -1;
+ size -= sizeof(struct posix_acl_xattr_header);
+ if (size % sizeof(struct posix_acl_xattr_entry_r))
+ return -1;
+ return size / sizeof(struct posix_acl_xattr_entry_r);
+}
+
+/* Whether an ACL_USER or ACL_GROUP entry of @acl is a UUID or GUID */
+static inline bool posix_acl_has_id_r(const struct posix_acl *acl)
+{
+ for (int n = 0; n < acl->a_count; n++) {
+ const struct posix_acl_entry *e = &acl->a_entries[n];
+
+ if ((e->e_tag == ACL_USER && !uid_is_legacy(e->e_uid)) ||
+ (e->e_tag == ACL_GROUP && !gid_is_legacy(e->e_gid)))
+ return true;
+ }
+ return false;
+}
+
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *posix_acl_from_xattr(struct user_namespace *user_ns,
const void *value, size_t size);
diff --git a/include/uapi/linux/posix_acl_xattr.h b/include/uapi/linux/posix_acl_xattr.h
index 506380f0b..a44a51507 100644
--- a/include/uapi/linux/posix_acl_xattr.h
+++ b/include/uapi/linux/posix_acl_xattr.h
@@ -36,4 +36,23 @@ struct posix_acl_xattr_header {
__le32 a_version;
};
+/*
+ * Version 3: the ids of ACL_USER and ACL_GROUP entries can also be UUIDs
+ * and GUIDs. e_type is a UID_R_* kind (<linux/uidgid_r.h>); a classic id
+ * is in the first four bytes of e_id, little endian, a UUID or GUID takes
+ * all of it in the byte order of __kernel_uid_r_t. Other entries have
+ * e_type UID_R_NONE.
+ *
+ * getxattr() returns version 2 unless an entry has a UUID or GUID;
+ * setxattr() takes both.
+ */
+#define POSIX_ACL_XATTR_VERSION_R 0x0003
+
+struct posix_acl_xattr_entry_r {
+ __le16 e_tag;
+ __le16 e_perm;
+ __le32 e_type;
+ __u8 e_id[16];
+};
+
#endif /* __UAPI_POSIX_ACL_XATTR_H */
[-- Attachment #35: uid_r-0034-ext4-store-ACLs-with-UUID-and-GUID-entries-on-uid_r-.patch --]
[-- Type: text/x-patch, Size: 8122 bytes --]
From 4730a39af742eb48cba608f533650fa7b745d075 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:42:13 +0200
Subject: [PATCH 34/43] ext4: store ACLs with UUID and GUID entries on uid_r
filesystems
ACLs whose ACL_USER or ACL_GROUP entries are UUIDs or GUIDs are written
in a new on-disk format, EXT4_ACL_VERSION_R: the short entries stay as
they are, the user and group entries carry an EXT4_ID_R_* kind and 16
bytes of id. Only such ACLs use it, and only uid_r filesystems can have
them (set_posix_acl() refuses them elsewhere); all other ACLs keep
EXT4_ACL_VERSION.
Assisted-by: Claude:claude-opus-5-5
---
fs/ext4/acl.c | 109 +++++++++++++++++++++++++++++++++++++++-----------
fs/ext4/acl.h | 34 +++++++++++++---
2 files changed, 115 insertions(+), 28 deletions(-)
diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c
index 3bffe862f..27f8a1b50 100644
--- a/fs/ext4/acl.c
+++ b/fs/ext4/acl.c
@@ -6,6 +6,7 @@
*/
#include <linux/quotaops.h>
+#include <linux/unaligned.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
@@ -14,22 +15,41 @@
/*
* Convert from filesystem to in-memory representation.
*/
+/* The id of an ACL_USER or ACL_GROUP entry of EXT4_ACL_VERSION_R */
+static int ext4_acl_id_from_disk(const ext4_acl_entry_r *entry, u32 *type,
+ u32 *legacy, uuid_t *id)
+{
+ *type = le32_to_cpu(entry->e_type);
+ if (*type > EXT4_ID_R_GUID)
+ return -EINVAL;
+ *legacy = get_unaligned_le32(entry->e_id);
+ memcpy(id->b, entry->e_id, sizeof(id->b));
+ return 0;
+}
+
static struct posix_acl *
ext4_acl_from_disk(const void *value, size_t size)
{
const char *end = (char *)value + size;
int n, count;
struct posix_acl *acl;
+ size_t entry_size = sizeof(ext4_acl_entry);
+ bool r = false;
if (!value)
return NULL;
if (size < sizeof(ext4_acl_header))
return ERR_PTR(-EINVAL);
- if (((ext4_acl_header *)value)->a_version !=
- cpu_to_le32(EXT4_ACL_VERSION))
+ if (((ext4_acl_header *)value)->a_version ==
+ cpu_to_le32(EXT4_ACL_VERSION_R)) {
+ r = true;
+ entry_size = sizeof(ext4_acl_entry_r);
+ } else if (((ext4_acl_header *)value)->a_version !=
+ cpu_to_le32(EXT4_ACL_VERSION)) {
return ERR_PTR(-EINVAL);
+ }
value = (char *)value + sizeof(ext4_acl_header);
- count = ext4_acl_count(size);
+ count = ext4_acl_count_e(size, entry_size);
if (count < 0)
return ERR_PTR(-EINVAL);
if (count == 0)
@@ -55,21 +75,33 @@ ext4_acl_from_disk(const void *value, size_t size)
break;
case ACL_USER:
- value = (char *)value + sizeof(ext4_acl_entry);
+ case ACL_GROUP: {
+ u32 type = EXT4_ID_R_LEGACY, legacy;
+ uuid_t id;
+
+ value = (char *)value + entry_size;
if ((char *)value > end)
goto fail;
- acl->a_entries[n].e_uid =
- make_kuid(&init_user_ns,
- le32_to_cpu(entry->e_id));
- break;
- case ACL_GROUP:
- value = (char *)value + sizeof(ext4_acl_entry);
- if ((char *)value > end)
+ legacy = le32_to_cpu(entry->e_id);
+ if (r && ext4_acl_id_from_disk((void *)entry, &type,
+ &legacy, &id))
goto fail;
- acl->a_entries[n].e_gid =
- make_kgid(&init_user_ns,
- le32_to_cpu(entry->e_id));
+ if (acl->a_entries[n].e_tag == ACL_USER)
+ acl->a_entries[n].e_uid =
+ type == EXT4_ID_R_LEGACY ?
+ make_kuid(&init_user_ns, legacy) :
+ type == EXT4_ID_R_UUID ?
+ KUIDT_INIT_UUID(id) :
+ KUIDT_INIT_GUID(*(guid_t *)&id);
+ else
+ acl->a_entries[n].e_gid =
+ type == EXT4_ID_R_LEGACY ?
+ make_kgid(&init_user_ns, legacy) :
+ type == EXT4_ID_R_UUID ?
+ KGIDT_INIT_UUID(id) :
+ KGIDT_INIT_GUID(*(guid_t *)&id);
break;
+ }
default:
goto fail;
@@ -87,19 +119,38 @@ ext4_acl_from_disk(const void *value, size_t size)
/*
* Convert from in-memory to filesystem representation.
*/
+/* An ACL_USER or ACL_GROUP id into an entry of EXT4_ACL_VERSION_R */
+static void ext4_acl_id_to_disk(ext4_acl_entry_r *entry, u32 type, u32 legacy,
+ const uuid_t *id)
+{
+ entry->e_type = cpu_to_le32(type);
+ memset(entry->e_id, 0, sizeof(entry->e_id));
+ if (type == EXT4_ID_R_LEGACY)
+ put_unaligned_le32(legacy, entry->e_id);
+ else
+ memcpy(entry->e_id, id->b, sizeof(entry->e_id));
+}
+
+/*
+ * ACLs with UUID or GUID entries (only on uid_r filesystems, see
+ * set_posix_acl()) are written as EXT4_ACL_VERSION_R, others as before.
+ */
static void *
ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
{
ext4_acl_header *ext_acl;
char *e;
size_t n;
+ bool r = posix_acl_has_id_r(acl);
+ size_t entry_size = r ? sizeof(ext4_acl_entry_r) : sizeof(ext4_acl_entry);
- *size = ext4_acl_size(acl->a_count);
+ *size = ext4_acl_size_e(acl->a_count, entry_size);
ext_acl = kmalloc(sizeof(ext4_acl_header) + acl->a_count *
- sizeof(ext4_acl_entry), GFP_NOFS);
+ entry_size, GFP_NOFS);
if (!ext_acl)
return ERR_PTR(-ENOMEM);
- ext_acl->a_version = cpu_to_le32(EXT4_ACL_VERSION);
+ ext_acl->a_version = cpu_to_le32(r ? EXT4_ACL_VERSION_R :
+ EXT4_ACL_VERSION);
e = (char *)ext_acl + sizeof(ext4_acl_header);
for (n = 0; n < acl->a_count; n++) {
const struct posix_acl_entry *acl_e = &acl->a_entries[n];
@@ -108,14 +159,26 @@ ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
entry->e_perm = cpu_to_le16(acl_e->e_perm);
switch (acl_e->e_tag) {
case ACL_USER:
- entry->e_id = cpu_to_le32(
- from_kuid(&init_user_ns, acl_e->e_uid));
- e += sizeof(ext4_acl_entry);
+ if (r)
+ ext4_acl_id_to_disk((void *)entry,
+ acl_e->e_uid.type,
+ from_kuid(&init_user_ns, acl_e->e_uid),
+ &acl_e->e_uid.uuid);
+ else
+ entry->e_id = cpu_to_le32(
+ from_kuid(&init_user_ns, acl_e->e_uid));
+ e += entry_size;
break;
case ACL_GROUP:
- entry->e_id = cpu_to_le32(
- from_kgid(&init_user_ns, acl_e->e_gid));
- e += sizeof(ext4_acl_entry);
+ if (r)
+ ext4_acl_id_to_disk((void *)entry,
+ acl_e->e_gid.type,
+ from_kgid(&init_user_ns, acl_e->e_gid),
+ &acl_e->e_gid.uuid);
+ else
+ entry->e_id = cpu_to_le32(
+ from_kgid(&init_user_ns, acl_e->e_gid));
+ e += entry_size;
break;
case ACL_USER_OBJ:
diff --git a/fs/ext4/acl.h b/fs/ext4/acl.h
index 0c5a79c3b..48bc352ec 100644
--- a/fs/ext4/acl.h
+++ b/fs/ext4/acl.h
@@ -8,6 +8,8 @@
#include <linux/posix_acl_xattr.h>
#define EXT4_ACL_VERSION 0x0001
+/* uid_r: ACL_USER and ACL_GROUP entries are ext4_acl_entry_r */
+#define EXT4_ACL_VERSION_R 0x0002
typedef struct {
__le16 e_tag;
@@ -20,11 +22,23 @@ typedef struct {
__le16 e_perm;
} ext4_acl_entry_short;
+/*
+ * EXT4_ACL_VERSION_R: e_type an EXT4_ID_R_* kind, a classic id in the
+ * first four bytes of e_id (little endian), a UUID or GUID all of it
+ */
+typedef struct {
+ __le16 e_tag;
+ __le16 e_perm;
+ __le32 e_type;
+ __u8 e_id[16];
+} ext4_acl_entry_r;
+
typedef struct {
__le32 a_version;
} ext4_acl_header;
-static inline size_t ext4_acl_size(int count)
+/* @entry: sizeof(ext4_acl_entry) or, for EXT4_ACL_VERSION_R, of _r */
+static inline size_t ext4_acl_size_e(int count, size_t entry)
{
if (count <= 4) {
return sizeof(ext4_acl_header) +
@@ -32,11 +46,11 @@ static inline size_t ext4_acl_size(int count)
} else {
return sizeof(ext4_acl_header) +
4 * sizeof(ext4_acl_entry_short) +
- (count - 4) * sizeof(ext4_acl_entry);
+ (count - 4) * entry;
}
}
-static inline int ext4_acl_count(size_t size)
+static inline int ext4_acl_count_e(size_t size, size_t entry)
{
ssize_t s;
size -= sizeof(ext4_acl_header);
@@ -46,12 +60,22 @@ static inline int ext4_acl_count(size_t size)
return -1;
return size / sizeof(ext4_acl_entry_short);
} else {
- if (s % sizeof(ext4_acl_entry))
+ if (s % entry)
return -1;
- return s / sizeof(ext4_acl_entry) + 4;
+ return s / entry + 4;
}
}
+static inline size_t ext4_acl_size(int count)
+{
+ return ext4_acl_size_e(count, sizeof(ext4_acl_entry));
+}
+
+static inline int ext4_acl_count(size_t size)
+{
+ return ext4_acl_count_e(size, sizeof(ext4_acl_entry));
+}
+
#ifdef CONFIG_EXT4_FS_POSIX_ACL
/* acl.c */
[-- Attachment #36: uid_r-0035-Documentation-describe-UUID-and-GUID-user-and-group-.patch --]
[-- Type: text/x-patch, Size: 8198 bytes --]
From 825df690080fcdde8137d756313edb34ca09fa64 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:46:09 +0200
Subject: [PATCH 35/43] Documentation: describe UUID and GUID user and group
ids
Describe __kernel_uid_r_t, the rules for UUID and GUID ids (overflow
ids in classic interfaces, the initial user namespace only, filesystems
with SB_I_UID_R), the *_r system calls, the extended interfaces (statx,
socket credentials, siginfo, SysV IPC, keyctl, POSIX ACLs, procfs,
audit) and the current limitations.
Assisted-by: Claude:claude-opus-5-5
---
Documentation/userspace-api/index.rst | 1 +
Documentation/userspace-api/uid_r.rst | 169 ++++++++++++++++++++++++++
2 files changed, 170 insertions(+)
create mode 100644 Documentation/userspace-api/uid_r.rst
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index a68b1bea5..fcac7703f 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -22,6 +22,7 @@ System calls
ioctl/index
mseal
rseq
+ uid_r
Security-related interfaces
===========================
diff --git a/Documentation/userspace-api/uid_r.rst b/Documentation/userspace-api/uid_r.rst
new file mode 100644
index 000000000..c08048545
--- /dev/null
+++ b/Documentation/userspace-api/uid_r.rst
@@ -0,0 +1,169 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===========================================
+UUID and GUID user and group ids (``*_r``)
+===========================================
+
+Besides the classic 32 bit numbers, user and group ids can be UUIDs
+(RFC 9562, version 4) or Microsoft GUIDs. A process can run as such a
+user, files can be owned by one, and ACLs, keys, IPC objects, sockets and
+signals carry them. The classic system calls keep their 32 bit ids; a new
+family of calls, named after them with an ``_r`` suffix, and extensions of
+existing interfaces handle all kinds of id.
+
+The id type
+===========
+
+``<linux/uidgid_r.h>`` defines ``__kernel_uid_r_t`` (and
+``__kernel_gid_r_t``, the same type)::
+
+ typedef struct __kernel_uid_r {
+ __u32 type;
+ union {
+ __u32 legacy;
+ __u8 uuid[16];
+ __u8 guid[16];
+ };
+ } __kernel_uid_r_t;
+
+``type`` is one of:
+
+``UID_R_NONE`` (0)
+ No id. An all-zero id is never root. The ``set*id_r()`` and
+ ``chown_r()`` calls take it, like a ``NULL`` pointer or a classic
+ ``-1``, as "leave unchanged".
+
+``UID_R_LEGACY`` (1)
+ A classic id in ``legacy``.
+
+``UID_R_UUID`` (2)
+ A UUID in ``uuid``, in network byte order. It must be version 4 with
+ the RFC 9562 variant.
+
+``UID_R_GUID`` (3)
+ A GUID in ``guid`` as Windows stores it: Data1, Data2 and Data3
+ little endian. The nil GUID is not allowed.
+
+Bytes past the member ``type`` selects must be zero. The struct is 20
+bytes on every ABI, so none of the calls needs a compat variant.
+
+The text form of an id (in procfs, audit records, key descriptions and
+``/proc/<pid>/loginuid``) is a decimal number, a UUID
+``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`` or a GUID in braces
+``{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}``.
+
+Rules
+=====
+
+- Interfaces with 32 bit ids show a UUID or GUID as the overflow id
+ (``/proc/sys/kernel/overflowuid`` and ``overflowgid``), as the 16 bit
+ system calls do for ids above 65535.
+
+- UUIDs and GUIDs are not part of any ``uid_map`` or ``gid_map``: they
+ exist in the initial user namespace only. Elsewhere they show as the
+ overflow id, can't be set, and no capability in another namespace gives
+ privilege over them. Only ``CAP_SETUID``/``CAP_SETGID`` in the initial
+ namespace can switch to one.
+
+- Idmapped mounts map classic ids and pass UUIDs and GUIDs unchanged, on
+ filesystems of the initial user namespace.
+
+- Only filesystems that can store them accept UUID and GUID owners and ACL
+ entries; others refuse with ``EOVERFLOW``, for ``chown`` as well as for
+ creating a file as such a user. tmpfs and ramfs do, and ext4 with the
+ ``uid_r`` feature (``mke2fs -O uid_r``; a read-only compatible feature,
+ inodes of at least 256 bytes). ``tune2fs -O uid_r`` turns it on later; on
+ a mounted filesystem through ``EXT4_IOC_SET_TUNE_SB_PARAM``, which makes
+ it usable at once, otherwise at the next mount or remount.
+
+System calls
+============
+
+All take pointers to ``__kernel_uid_r_t`` and return 0 or a negative
+error, unless noted otherwise.
+
+================= =======================================================
+``getuid_r`` ``(uid_r *uid)``, also ``geteuid_r``, ``getgid_r``,
+ ``getegid_r``
+``setuid_r`` ``(const uid_r *uid)``, also ``setgid_r``
+``setreuid_r`` ``(const uid_r *ruid, const uid_r *euid)``, also
+ ``setregid_r``
+``setresuid_r`` ``(const uid_r *ruid, const uid_r *euid, const uid_r
+ *suid)``, also ``setresgid_r``
+``getresuid_r`` ``(uid_r *ruid, uid_r *euid, uid_r *suid)``, also
+ ``getresgid_r``; ``NULL`` pointers are skipped
+``setfsuid_r`` ``(const uid_r *uid, uid_r *old)``, also
+ ``setfsgid_r``. Unlike ``setfsuid()`` it fails with
+ ``EPERM`` instead of silently, and returns the old id
+ in ``*old``; a ``NULL`` *uid* only queries it.
+``getgroups_r`` ``(int size, gid_r *list)``, returns the number of
+ groups like ``getgroups()``
+``setgroups_r`` ``(int size, const gid_r *list)``; "no id" is not
+ allowed in the list
+``chown_r`` ``(const char *path, const uid_r *user, const gid_r
+ *group)``, also ``lchown_r``, ``fchown_r(int fd, ...)``
+ and ``fchownat_r(int dfd, const char *path, ..., int
+ flags)``
+================= =======================================================
+
+Supplementary groups are sorted by kind (classic, UUID, GUID) and value.
+
+Other interfaces
+================
+
+``statx()``
+ ``STATX_UID_R`` and ``STATX_GID_R`` report ``stx_uid_r`` and
+ ``stx_gid_r``.
+
+Sockets
+ ``SO_PEERCRED_R`` returns a ``struct __kernel_ucred_r`` (pid, uid,
+ gid), ``SO_PEERGROUPS_R`` the peer's groups as ``__kernel_gid_r_t``.
+ ``SO_PASSCRED_R`` asks for ``SCM_CREDENTIALS_R`` messages with a
+ ``struct __kernel_ucred_r``; they can be sent with the rules of
+ ``SCM_CREDENTIALS``. Netlink keeps classic ids in its messages.
+
+Signals
+ ``siginfo_t`` has ``si_uid_r``, the sender's uid, at offset 96 in
+ every layout; ``signalfd_siginfo`` has ``ssi_uid_r``. It is
+ ``UID_R_NONE`` where the kernel doesn't set it; ``waitid()`` fills it
+ in.
+
+System V IPC
+ ``IPC_STAT_R`` and ``IPC_SET_R`` for ``msgctl()``, ``semctl()`` and
+ ``shmctl()`` read and set a ``struct ipc_perm_r`` (key, mode, uid,
+ gid, cuid, cgid).
+
+Keys
+ ``KEYCTL_CHOWN_R`` takes ``__kernel_uid_r_t`` owner and group.
+
+POSIX ACLs
+ Version 3 of the ACL xattr format (``POSIX_ACL_XATTR_VERSION_R``)
+ has ``struct posix_acl_xattr_entry_r`` entries with an id kind and
+ 16 bytes of id. ``getxattr()`` returns version 3 only if an entry is
+ a UUID or GUID; ``setxattr()`` takes both versions.
+
+procfs
+ ``/proc/<pid>/status`` has ``Uid_r:``, ``Gid_r:`` and ``Groups_r:``
+ lines with the text form of the ids; ``/proc/<pid>/loginuid`` reads
+ and writes UUIDs and GUIDs as text.
+
+Audit
+ Records show UUIDs and GUIDs in their text form; classic ids are
+ printed as before.
+
+RPCSEC_GSS upcalls
+ The text (v1) upcall to gssd names a UUID or GUID user as
+ ``uid_r=<text form>`` instead of ``uid=<number>``; gssd versions that
+ don't know it find no ``uid=`` and refuse the upcall. A downcall for
+ such a user starts with the uid ``-1`` (never valid) followed by the
+ user as ``__kernel_uid_r_t``, then the usual fields. The binary (v0)
+ upcall can't carry such users and fails with ``EACCES``.
+
+Limitations
+===========
+
+- Quotas don't account UUID and GUID owners; quota files hold 32 bit ids.
+- RPCSEC_GSS needs a gssd that knows ``uid_r=`` for UUID and GUID users.
+- UUID and GUID users can't create unprivileged ublk devices.
+- Audit filter rules, SafeSetID policies and IMA policy rules name
+ classic ids only.
[-- Attachment #37: uid_r-0036-selftests-add-tests-for-UUID-and-GUID-ids.patch --]
[-- Type: text/x-patch, Size: 17322 bytes --]
From 370df49a17ca0157a7ddae3161cdfb23c194b7a6 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 09:48:30 +0200
Subject: [PATCH 36/43] selftests: add tests for UUID and GUID ids
Test the *_r system calls (malformed ids, switching to a UUID user and
back through the saved uid, GUID groups, sorted supplementary groups,
chown_r() and statx() on tmpfs, creating files as a UUID fsuid,
-EOVERFLOW on sysfs) and the extended interfaces: SO_PEERCRED_R,
SO_PEERGROUPS_R, SCM_CREDENTIALS_R, ssi_uid_r, IPC_STAT_R/IPC_SET_R,
version 3 POSIX ACL xattrs, the Uid_r: line of /proc/<pid>/status, and
that the root of a user namespace has no privilege over UUID owners.
Assisted-by: Claude:claude-opus-5-5
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/uid_r/.gitignore | 1 +
tools/testing/selftests/uid_r/Makefile | 6 +
tools/testing/selftests/uid_r/config | 4 +
tools/testing/selftests/uid_r/uid_r_test.c | 535 +++++++++++++++++++++
5 files changed, 547 insertions(+)
create mode 100644 tools/testing/selftests/uid_r/.gitignore
create mode 100644 tools/testing/selftests/uid_r/Makefile
create mode 100644 tools/testing/selftests/uid_r/config
create mode 100644 tools/testing/selftests/uid_r/uid_r_test.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index b622052ec..284ee2c6f 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -131,6 +131,7 @@ TARGETS += tpm2
TARGETS += tty
TARGETS += ublk
TARGETS += uevent
+TARGETS += uid_r
TARGETS += user_events
TARGETS += vDSO
TARGETS += mm
diff --git a/tools/testing/selftests/uid_r/.gitignore b/tools/testing/selftests/uid_r/.gitignore
new file mode 100644
index 000000000..5855e85e3
--- /dev/null
+++ b/tools/testing/selftests/uid_r/.gitignore
@@ -0,0 +1 @@
+uid_r_test
diff --git a/tools/testing/selftests/uid_r/Makefile b/tools/testing/selftests/uid_r/Makefile
new file mode 100644
index 000000000..d5dc3e6aa
--- /dev/null
+++ b/tools/testing/selftests/uid_r/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -g -Wall $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
+
+TEST_GEN_PROGS := uid_r_test
+
+include ../lib.mk
diff --git a/tools/testing/selftests/uid_r/config b/tools/testing/selftests/uid_r/config
new file mode 100644
index 000000000..424ac0a2c
--- /dev/null
+++ b/tools/testing/selftests/uid_r/config
@@ -0,0 +1,4 @@
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_SYSVIPC=y
+CONFIG_SIGNALFD=y
diff --git a/tools/testing/selftests/uid_r/uid_r_test.c b/tools/testing/selftests/uid_r/uid_r_test.c
new file mode 100644
index 000000000..d627589a2
--- /dev/null
+++ b/tools/testing/selftests/uid_r/uid_r_test.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests of UUID and GUID user and group ids: the *_r system calls and the
+ * interfaces extended for them. Needs root in the initial user namespace;
+ * every test runs in a child process, file tests on a private tmpfs.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ipc.h>
+#include <sys/mount.h>
+#include <sys/shm.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/uidgid_r.h>
+
+#include "../kselftest_harness.h"
+
+#ifndef __NR_getuid_r
+#define __NR_getuid_r 472
+#define __NR_geteuid_r 473
+#define __NR_getgid_r 474
+#define __NR_getegid_r 475
+#define __NR_setuid_r 476
+#define __NR_setgid_r 477
+#define __NR_setreuid_r 478
+#define __NR_setregid_r 479
+#define __NR_setresuid_r 480
+#define __NR_getresuid_r 481
+#define __NR_setresgid_r 482
+#define __NR_getresgid_r 483
+#define __NR_setfsuid_r 484
+#define __NR_setfsgid_r 485
+#define __NR_getgroups_r 486
+#define __NR_setgroups_r 487
+#define __NR_chown_r 488
+#define __NR_lchown_r 489
+#define __NR_fchown_r 490
+#define __NR_fchownat_r 491
+#endif
+
+#ifndef STATX_UID_R
+#define STATX_UID_R 0x00040000U
+#define STATX_GID_R 0x00080000U
+#endif
+#ifndef SO_PEERCRED_R
+#define SO_PEERCRED_R 85
+#define SO_PEERGROUPS_R 86
+#define SO_PASSCRED_R 87
+#endif
+#ifndef SCM_CREDENTIALS_R
+#define SCM_CREDENTIALS_R 0x05
+#endif
+#ifndef IPC_STAT_R
+#define IPC_SET_R 21
+#define IPC_STAT_R 22
+#endif
+
+typedef __kernel_uid_r_t id_r;
+
+struct ucred_r {
+ __u32 pid;
+ id_r uid, gid;
+};
+
+struct ipc_perm_r_t {
+ int key;
+ __u32 mode;
+ id_r uid, gid, cuid, cgid;
+};
+
+/*
+ * Reserved bytes must be zero, and an initializer of one union member
+ * leaves the others undefined: always clear the whole id first.
+ */
+static id_r legacy(unsigned int v)
+{
+ id_r id;
+
+ memset(&id, 0, sizeof(id));
+ id.type = UID_R_LEGACY;
+ id.legacy = v;
+ return id;
+}
+
+/* a version 4 UUID, made distinct by @tag */
+static id_r uuid(unsigned char tag)
+{
+ static const unsigned char b[16] = {
+ 0x6f, 0x1c, 0x2a, 0x3b, 0x4d, 0x5e, 0x4f, 0x60,
+ 0x8a, 0x7b, 0x0c, 0x1d, 0x2e, 0x3f, 0x4a, 0x00 };
+ id_r id;
+
+ memset(&id, 0, sizeof(id));
+ id.type = UID_R_UUID;
+ memcpy(id.uuid, b, sizeof(b));
+ id.uuid[15] = tag;
+ return id;
+}
+
+static id_r guid(unsigned char tag)
+{
+ id_r id;
+
+ memset(&id, 0, sizeof(id));
+ id.type = UID_R_GUID;
+ for (int i = 0; i < 16; i++)
+ id.guid[i] = 0x10 + i;
+ id.guid[15] = tag;
+ return id;
+}
+
+static int ideq(const id_r *a, const id_r *b)
+{
+ return !memcmp(a, b, sizeof(*a));
+}
+
+static long sys(long nr, long a, long b, long c, long d, long e)
+{
+ long ret = syscall(nr, a, b, c, d, e);
+
+ return ret < 0 ? -errno : ret;
+}
+
+/* a private tmpfs at a fresh directory, for file tests */
+static void private_tmpfs(struct __test_metadata *_metadata, char *dir)
+{
+ strcpy(dir, "/tmp/uid_r.XXXXXX");
+ ASSERT_EQ(0, unshare(CLONE_NEWNS));
+ ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+ ASSERT_NE(NULL, mkdtemp(dir));
+ ASSERT_EQ(0, mount("tmpfs", dir, "tmpfs", 0, "mode=1777"));
+}
+
+static int statx_owner(const char *path, id_r *uid, id_r *gid)
+{
+ struct statx stx;
+
+ memset(&stx, 0, sizeof(stx));
+ if (statx(AT_FDCWD, path, 0, STATX_UID_R | STATX_GID_R, &stx))
+ return -errno;
+ if ((stx.stx_mask & (STATX_UID_R | STATX_GID_R)) !=
+ (STATX_UID_R | STATX_GID_R))
+ return -ENODATA;
+ /* stx_uid_r and stx_gid_r at 0xc0 */
+ memcpy(uid, (char *)&stx + 0xc0, sizeof(*uid));
+ memcpy(gid, (char *)&stx + 0xc0 + sizeof(*uid), sizeof(*gid));
+ return 0;
+}
+
+FIXTURE(uid_r) {
+ char dir[64];
+};
+
+FIXTURE_SETUP(uid_r)
+{
+ id_r id;
+
+ if (geteuid() != 0)
+ SKIP(return, "needs root");
+ if (sys(__NR_getuid_r, (long)&id, 0, 0, 0, 0) == -ENOSYS)
+ SKIP(return, "no *_r system calls");
+ self->dir[0] = 0;
+}
+
+FIXTURE_TEARDOWN(uid_r)
+{
+}
+
+TEST_F(uid_r, malformed_ids)
+{
+ id_r id;
+
+ id = uuid(1);
+ id.uuid[6] = 0x14; /* version 1 */
+ EXPECT_EQ(-EINVAL, sys(__NR_setuid_r, (long)&id, 0, 0, 0, 0));
+ id = uuid(1);
+ id.type = 9;
+ EXPECT_EQ(-EINVAL, sys(__NR_setuid_r, (long)&id, 0, 0, 0, 0));
+ id = legacy(5);
+ id.uuid[10] = 1; /* reserved byte */
+ EXPECT_EQ(-EINVAL, sys(__NR_setuid_r, (long)&id, 0, 0, 0, 0));
+ memset(&id, 0, sizeof(id));
+ id.type = UID_R_GUID; /* nil GUID */
+ EXPECT_EQ(-EINVAL, sys(__NR_setuid_r, (long)&id, 0, 0, 0, 0));
+ EXPECT_EQ(-EINVAL, sys(__NR_setuid_r, 0, 0, 0, 0, 0));
+ EXPECT_EQ(-EFAULT, sys(__NR_getuid_r, 1, 0, 0, 0, 0));
+}
+
+TEST_F(uid_r, setresuid_r)
+{
+ id_r u = uuid(1), s0 = legacy(0), r, e, s;
+
+ ASSERT_EQ(0, sys(__NR_setresuid_r, (long)&u, (long)&u, (long)&s0, 0, 0));
+ EXPECT_EQ(65534, getuid());
+ EXPECT_EQ(65534, geteuid());
+ ASSERT_EQ(0, sys(__NR_getresuid_r, (long)&r, (long)&e, (long)&s, 0, 0));
+ EXPECT_TRUE(ideq(&r, &u));
+ EXPECT_TRUE(ideq(&e, &u));
+ EXPECT_TRUE(ideq(&s, &s0));
+
+ /* without euid 0 no capabilities: no other UUID */
+ r = uuid(2);
+ EXPECT_EQ(-EPERM, sys(__NR_setuid_r, (long)&r, 0, 0, 0, 0));
+
+ /* back through the saved uid */
+ ASSERT_EQ(0, sys(__NR_setresuid_r, 0, (long)&s0, 0, 0, 0));
+ EXPECT_EQ(0, geteuid());
+}
+
+TEST_F(uid_r, guid_group)
+{
+ id_r g = guid(7), e;
+
+ ASSERT_EQ(0, sys(__NR_setresgid_r, 0, (long)&g, 0, 0, 0));
+ EXPECT_EQ(65534, getegid());
+ ASSERT_EQ(0, sys(__NR_getegid_r, (long)&e, 0, 0, 0, 0));
+ EXPECT_TRUE(ideq(&e, &g));
+}
+
+TEST_F(uid_r, groups_r)
+{
+ id_r in[3] = { guid(9), legacy(10), uuid(4) }, out[4];
+ gid_t classic[4];
+
+ ASSERT_EQ(0, sys(__NR_setgroups_r, 3, (long)in, 0, 0, 0));
+ ASSERT_EQ(3, sys(__NR_getgroups_r, 4, (long)out, 0, 0, 0));
+ /* sorted: legacy, UUID, GUID */
+ EXPECT_TRUE(ideq(&out[0], &in[1]));
+ EXPECT_TRUE(ideq(&out[1], &in[2]));
+ EXPECT_TRUE(ideq(&out[2], &in[0]));
+ ASSERT_EQ(3, getgroups(4, classic));
+ EXPECT_EQ(10, classic[0]);
+ EXPECT_EQ(65534, classic[1]);
+ memset(&in[0], 0, sizeof(in[0]));
+ EXPECT_EQ(-EINVAL, sys(__NR_setgroups_r, 1, (long)in, 0, 0, 0));
+}
+
+TEST_F(uid_r, chown_r_statx)
+{
+ id_r u = uuid(5), g = guid(6), r, rg;
+ char path[96];
+ struct stat st;
+ int fd;
+
+ private_tmpfs(_metadata, self->dir);
+ snprintf(path, sizeof(path), "%s/f", self->dir);
+ fd = open(path, O_CREAT | O_WRONLY, 0644);
+ ASSERT_LE(0, fd);
+ ASSERT_EQ(0, sys(__NR_fchown_r, fd, (long)&u, (long)&g, 0, 0));
+ close(fd);
+ ASSERT_EQ(0, stat(path, &st));
+ EXPECT_EQ(65534, st.st_uid);
+ ASSERT_EQ(0, statx_owner(path, &r, &rg));
+ EXPECT_TRUE(ideq(&r, &u));
+ EXPECT_TRUE(ideq(&rg, &g));
+
+ /* NULL leaves the owner, UID_R_NONE too */
+ r = legacy(42);
+ memset(&rg, 0, sizeof(rg));
+ ASSERT_EQ(0, sys(__NR_chown_r, (long)path, 0, (long)&r, 0, 0));
+ ASSERT_EQ(0, sys(__NR_fchownat_r, AT_FDCWD, (long)path, (long)&rg, 0, 0));
+ ASSERT_EQ(0, statx_owner(path, &r, &rg));
+ EXPECT_TRUE(ideq(&r, &u));
+ EXPECT_EQ(UID_R_LEGACY, rg.type);
+ EXPECT_EQ(42, rg.legacy);
+}
+
+TEST_F(uid_r, create_as_uuid_fsuid)
+{
+ id_r u = uuid(3), s0 = legacy(0), old, r, rg;
+ char path[96];
+ int fd;
+
+ private_tmpfs(_metadata, self->dir);
+ snprintf(path, sizeof(path), "%s/byuuid", self->dir);
+ ASSERT_EQ(0, sys(__NR_setfsuid_r, (long)&u, (long)&old, 0, 0, 0));
+ EXPECT_TRUE(ideq(&old, &s0));
+ fd = open(path, O_CREAT | O_WRONLY, 0644);
+ ASSERT_EQ(0, sys(__NR_setfsuid_r, (long)&s0, 0, 0, 0, 0));
+ ASSERT_LE(0, fd);
+ close(fd);
+ ASSERT_EQ(0, statx_owner(path, &r, &rg));
+ EXPECT_TRUE(ideq(&r, &u));
+}
+
+TEST_F(uid_r, classic_only_fs)
+{
+ id_r u = uuid(5);
+
+ /* sysfs (kernfs) can't store UUID owners */
+ EXPECT_EQ(-EOVERFLOW, sys(__NR_chown_r, (long)"/sys/kernel",
+ (long)&u, 0, 0, 0));
+}
+
+TEST_F(uid_r, peercred_r)
+{
+ id_r u = uuid(8), s0 = legacy(0), g = guid(10), groups[2];
+ struct ucred_r pc;
+ struct ucred lc;
+ socklen_t len;
+ int sv[2];
+
+ groups[0] = g;
+ groups[1] = legacy(7);
+ ASSERT_EQ(0, sys(__NR_setgroups_r, 2, (long)groups, 0, 0, 0));
+ ASSERT_EQ(0, sys(__NR_setresuid_r, 0, (long)&u, (long)&s0, 0, 0));
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sv));
+ ASSERT_EQ(0, sys(__NR_setresuid_r, 0, (long)&s0, 0, 0, 0));
+
+ len = sizeof(pc);
+ ASSERT_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_PEERCRED_R, &pc, &len));
+ EXPECT_TRUE(ideq(&pc.uid, &u));
+ len = sizeof(lc);
+ ASSERT_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_PEERCRED, &lc, &len));
+ EXPECT_EQ(65534, lc.uid);
+ len = sizeof(groups);
+ ASSERT_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_PEERGROUPS_R, groups, &len));
+ EXPECT_EQ(2 * sizeof(id_r), len);
+ EXPECT_TRUE(ideq(&groups[1], &g));
+}
+
+TEST_F(uid_r, scm_credentials_r)
+{
+ id_r u = uuid(9), s0 = legacy(0);
+ char data = 'x', cbuf[256];
+ struct iovec iov = { &data, 1 };
+ struct msghdr mh = { 0 };
+ struct cmsghdr *c;
+ int sv[2], one = 1, got_r = 0, got_l = 0;
+
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sv));
+ ASSERT_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)));
+ ASSERT_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_PASSCRED_R, &one, sizeof(one)));
+ /* SCM credentials carry the real ids */
+ ASSERT_EQ(0, sys(__NR_setresuid_r, (long)&u, 0, (long)&s0, 0, 0));
+ ASSERT_EQ(1, write(sv[0], &data, 1));
+ ASSERT_EQ(0, sys(__NR_setresuid_r, (long)&s0, 0, 0, 0, 0));
+
+ mh.msg_iov = &iov;
+ mh.msg_iovlen = 1;
+ mh.msg_control = cbuf;
+ mh.msg_controllen = sizeof(cbuf);
+ ASSERT_EQ(1, recvmsg(sv[1], &mh, 0));
+ EXPECT_FALSE(mh.msg_flags & MSG_CTRUNC);
+ for (c = CMSG_FIRSTHDR(&mh); c; c = CMSG_NXTHDR(&mh, c)) {
+ if (c->cmsg_level != SOL_SOCKET)
+ continue;
+ if (c->cmsg_type == SCM_CREDENTIALS_R) {
+ struct ucred_r pc;
+
+ memcpy(&pc, CMSG_DATA(c), sizeof(pc));
+ got_r = ideq(&pc.uid, &u);
+ } else if (c->cmsg_type == SCM_CREDENTIALS) {
+ struct ucred lc;
+
+ memcpy(&lc, CMSG_DATA(c), sizeof(lc));
+ got_l = lc.uid == 65534;
+ }
+ }
+ EXPECT_TRUE(got_r);
+ EXPECT_TRUE(got_l);
+}
+
+TEST_F(uid_r, signalfd_uid_r)
+{
+ id_r u = uuid(11), s0 = legacy(0), got;
+ struct signalfd_siginfo ssi;
+ sigset_t mask;
+ int sfd;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ ASSERT_EQ(0, sigprocmask(SIG_BLOCK, &mask, NULL));
+ sfd = signalfd(-1, &mask, 0);
+ ASSERT_LE(0, sfd);
+ ASSERT_EQ(0, sys(__NR_setresuid_r, (long)&u, 0, 0, 0, 0));
+ ASSERT_EQ(0, kill(getpid(), SIGUSR1));
+ ASSERT_EQ(0, sys(__NR_setresuid_r, (long)&s0, 0, 0, 0, 0));
+ ASSERT_EQ(sizeof(ssi), read(sfd, &ssi, sizeof(ssi)));
+ EXPECT_EQ(65534, ssi.ssi_uid);
+ /* ssi_uid_r follows ssi_arch */
+ memcpy(&got, (char *)&ssi + 100, sizeof(got));
+ EXPECT_TRUE(ideq(&got, &u));
+}
+
+TEST_F(uid_r, ipc_stat_r)
+{
+ id_r u = uuid(12), g = guid(13);
+ struct ipc_perm_r_t pr;
+ struct shmid_ds ds;
+ int id;
+
+ memset(&pr, 0, sizeof(pr));
+ pr.mode = 0640;
+ pr.uid = u;
+ pr.gid = g;
+
+ id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
+ ASSERT_LE(0, id);
+ ASSERT_EQ(0, shmctl(id, IPC_SET_R, (void *)&pr));
+ memset(&pr, 0, sizeof(pr));
+ ASSERT_EQ(0, shmctl(id, IPC_STAT_R, (void *)&pr));
+ EXPECT_TRUE(ideq(&pr.uid, &u));
+ EXPECT_TRUE(ideq(&pr.gid, &g));
+ EXPECT_EQ(0640, pr.mode & 0777);
+ ASSERT_EQ(0, shmctl(id, IPC_STAT, &ds));
+ EXPECT_EQ(65534, ds.shm_perm.uid);
+ shmctl(id, IPC_RMID, NULL);
+}
+
+/* user::rw- user:<u>:r-- group::--- mask::r-- other::--- in version 3 */
+static size_t acl_v3(unsigned char *buf, const id_r *u)
+{
+ static const unsigned short ent[5][2] = {
+ { 0x01, 6 }, { 0x02, 4 }, { 0x04, 0 }, { 0x10, 4 }, { 0x20, 0 } };
+ unsigned char *e = buf + 4;
+ unsigned int ver = 3;
+
+ memcpy(buf, &ver, 4);
+ for (int i = 0; i < 5; i++, e += 24) {
+ unsigned int type = 0;
+
+ memset(e, 0, 24);
+ memcpy(e, &ent[i][0], 2);
+ memcpy(e + 2, &ent[i][1], 2);
+ if (ent[i][0] == 0x02) {
+ type = u->type;
+ memcpy(e + 8, u->uuid, 16);
+ }
+ memcpy(e + 4, &type, 4);
+ }
+ return 4 + 5 * 24;
+}
+
+TEST_F(uid_r, posix_acl_v3)
+{
+ unsigned char buf[256], got[256];
+ id_r u = uuid(20), s0 = legacy(0);
+ char path[96];
+ size_t len;
+ int fd;
+
+ private_tmpfs(_metadata, self->dir);
+ snprintf(path, sizeof(path), "%s/acl", self->dir);
+ fd = open(path, O_CREAT | O_WRONLY, 0600);
+ ASSERT_LE(0, fd);
+ close(fd);
+ len = acl_v3(buf, &u);
+ if (setxattr(path, "system.posix_acl_access", buf, len, 0) &&
+ errno == EOPNOTSUPP)
+ SKIP(return, "tmpfs without POSIX ACLs");
+ ASSERT_EQ(len, getxattr(path, "system.posix_acl_access", got, sizeof(got)));
+ EXPECT_EQ(3, got[0]);
+ EXPECT_EQ(0, memcmp(got + 4 + 24 + 8, u.uuid, 16));
+
+ /* the ACL lets the UUID user read the 0600 root file */
+ ASSERT_EQ(0, sys(__NR_setfsuid_r, (long)&u, 0, 0, 0, 0));
+ fd = open(path, O_RDONLY);
+ ASSERT_EQ(0, sys(__NR_setfsuid_r, (long)&s0, 0, 0, 0, 0));
+ EXPECT_LE(0, fd);
+}
+
+TEST_F(uid_r, proc_status)
+{
+ id_r u = uuid(6), s0 = legacy(0);
+ char buf[4096], line[160];
+ int fd, n;
+
+ ASSERT_EQ(0, sys(__NR_setresuid_r, 0, (long)&u, (long)&s0, 0, 0));
+ fd = open("/proc/self/status", O_RDONLY);
+ n = fd < 0 ? -1 : read(fd, buf, sizeof(buf) - 1);
+ ASSERT_EQ(0, sys(__NR_setresuid_r, 0, (long)&s0, 0, 0, 0));
+ ASSERT_LT(0, n);
+ buf[n] = 0;
+ close(fd);
+ EXPECT_NE(NULL, strstr(buf, "Uid:\t0\t65534\t0\t65534\n"));
+ snprintf(line, sizeof(line),
+ "Uid_r:\t0\t6f1c2a3b-4d5e-4f60-8a7b-0c1d2e3f4a06\t0\t"
+ "6f1c2a3b-4d5e-4f60-8a7b-0c1d2e3f4a06\n");
+ EXPECT_NE(NULL, strstr(buf, line));
+}
+
+TEST_F(uid_r, user_namespace)
+{
+ id_r u = uuid(21), r;
+ char path[96];
+ int fd, status;
+ pid_t pid;
+
+ private_tmpfs(_metadata, self->dir);
+ snprintf(path, sizeof(path), "%s/nsfile", self->dir);
+ fd = open(path, O_CREAT | O_WRONLY, 0644);
+ ASSERT_LE(0, fd);
+ close(fd);
+ ASSERT_EQ(0, sys(__NR_chown_r, (long)path, (long)&u, 0, 0, 0));
+
+ pid = fork();
+ ASSERT_LE(0, pid);
+ if (pid == 0) {
+ int bad = 0;
+
+ if (unshare(CLONE_NEWUSER))
+ _exit(100);
+ /* from inside, only the own id can be mapped */
+ fd = open("/proc/self/uid_map", O_WRONLY);
+ if (fd < 0 || write(fd, "0 0 1", 5) != 5)
+ bad |= 1;
+ close(fd);
+ if (sys(__NR_setuid_r, (long)&u, 0, 0, 0, 0) != -EINVAL)
+ bad |= 2;
+ /* CAP_FOWNER in the namespace gives nothing over a UUID */
+ if (chmod(path, 0600) == 0 || errno != EPERM)
+ bad |= 4;
+ (void)r;
+ _exit(bad);
+ }
+ ASSERT_EQ(pid, waitpid(pid, &status, 0));
+ if (WEXITSTATUS(status) == 100)
+ SKIP(return, "no user namespaces");
+ EXPECT_EQ(0, WEXITSTATUS(status));
+}
+
+TEST_HARNESS_MAIN
[-- Attachment #38: uid_r-0037-ovl-allow-UUID-and-GUID-owners-when-the-upper-layer-.patch --]
[-- Type: text/x-patch, Size: 1268 bytes --]
From b10dd013157fbbe54658f021bbc524299b781d43 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 13:58:30 +0200
Subject: [PATCH 37/43] ovl: allow UUID and GUID owners when the upper layer
does
Changes to an overlay land in its upper layer, so whether the overlay
can store UUID and GUID owners is the upper layer's business: take
SB_I_UID_R over from it. Copy-up of such an owner from a lower layer
into an upper layer without the flag keeps failing with -EOVERFLOW.
This is what a live system with a tmpfs over a read-only image needs
to give files to UUID and GUID users.
Assisted-by: Claude:claude-opus-5-5
---
fs/overlayfs/super.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 43eb0c053..5b48a9499 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1455,6 +1455,9 @@ static int ovl_fill_super_creds(struct fs_context *fc, struct super_block *sb)
sb->s_stack_depth = upper_sb->s_stack_depth;
sb->s_time_gran = upper_sb->s_time_gran;
+ /* changes land in the upper layer: its kinds of owner */
+ if (upper_sb->s_iflags & SB_I_UID_R)
+ sb->s_iflags |= SB_I_UID_R;
}
oe = ovl_get_lowerstack(sb, ctx, ofs, layers);
err = PTR_ERR(oe);
[-- Attachment #39: uid_r-0038-devpts-allow-UUID-and-GUID-owners.patch --]
[-- Type: text/x-patch, Size: 1153 bytes --]
From 764cb4600427c956b7addf402c6e7ceae4751d1c Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 14:31:16 +0200
Subject: [PATCH 38/43] devpts: allow UUID and GUID owners
Terminals are given to the user of a login session: sshd and login
chown the slave to the user, and a pty opened by a UUID or GUID user
gets that user as its owner. devpts keeps owners in its inodes only,
so like tmpfs and ramfs it can hold any kind of owner: set SB_I_UID_R.
Without it, sshd's chown() of the pty of a UUID user fails with
-EOVERFLOW and the session is closed.
Assisted-by: Claude:claude-opus-5-5
---
fs/devpts/inode.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c
index 9844dcf35..1bdda468d 100644
--- a/fs/devpts/inode.c
+++ b/fs/devpts/inode.c
@@ -363,6 +363,8 @@ static int devpts_fill_super(struct super_block *s, struct fs_context *fc)
struct inode *inode;
s->s_iflags &= ~SB_I_NODEV;
+ /* owners live in memory only, a UUID or GUID one as well */
+ s->s_iflags |= SB_I_UID_R;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
s->s_magic = DEVPTS_SUPER_MAGIC;
[-- Attachment #40: uid_r-0039-ksmbd-log-in-UUID-and-GUID-users-and-map-them-to-SID.patch --]
[-- Type: text/x-patch, Size: 22119 bytes --]
From e9708d8dd4a8e6241f380983b27bb151da5b11ba Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 14:43:50 +0200
Subject: [PATCH 39/43] ksmbd: log in UUID and GUID users and map them to SIDs
A user whose ids are UUIDs or GUIDs could not use ksmbd: the login
response carries 32 bit ids, sessions ran as make_kuid(uid), and SIDs
were built from from_kuid_munged(), so such users and their files all
became the overflow id.
Take the ids from the login response extension: flags, formerly
reserved and zero, has KSMBD_LOGIN_EXT_ID_R when uid_r and gid_r (also
formerly reserved) hold the user's ids as __kernel_uid_r_t, and
KSMBD_LOGIN_EXT_GROUPS_R when the payload holds __kernel_gid_r_t
supplementary groups instead of gid_t. struct ksmbd_user keeps kuid,
kgid and kgid_t groups, which the session's credentials and durable
handle owners use.
UUID and GUID users and groups have no RID; they are represented as
S-1-22-<kind>-w0-w1-w2-w3 (kind 3/4: UUID user/group, 5/6: GUID
user/group; w0..w3 the 16 bytes as big endian words), next to the
existing S-1-22-1-<uid> and S-1-22-2-<gid>, in security descriptors,
inherited ACEs, POSIX ACL entries and owner changes from clients.
The SMB3 POSIX extension's fixed 16 and 28 byte SID buffers can't hold
such a SID; they keep showing the overflow id.
Assisted-by: Claude:claude-opus-5-5
---
fs/smb/server/ksmbd_netlink.h | 15 ++-
fs/smb/server/mgmt/user_config.c | 35 +++++-
fs/smb/server/mgmt/user_config.h | 4 +-
fs/smb/server/smb2pdu.c | 8 +-
fs/smb/server/smb_common.c | 18 ++--
fs/smb/server/smbacl.c | 176 ++++++++++++++++++++++++-------
fs/smb/server/smbacl.h | 19 +++-
fs/smb/server/transport_ipc.c | 4 +-
fs/smb/server/vfs_cache.c | 9 +-
fs/smb/server/vfs_cache.h | 4 +-
10 files changed, 227 insertions(+), 65 deletions(-)
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index af1e76045..40c43507b 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -9,6 +9,7 @@
#define _LINUX_KSMBD_SERVER_H
#include <linux/types.h>
+#include <linux/uidgid_r.h>
/*
* This is a userspace ABI to communicate data between ksmbd and user IPC
@@ -153,14 +154,26 @@ struct ksmbd_login_response {
/*
* IPC user login response extension.
+ *
+ * With KSMBD_LOGIN_EXT_ID_R, uid_r and gid_r are the user's ids and may be
+ * UUIDs or GUIDs (see Documentation/userspace-api/uid_r.rst); the response's
+ * uid and gid are then ignored. With KSMBD_LOGIN_EXT_GROUPS_R the payload
+ * holds ngroups __kernel_gid_r_t instead of ngroups gid_t. A user space
+ * that knows neither leaves flags (formerly reserved) zero.
*/
struct ksmbd_login_response_ext {
__u32 handle;
__s32 ngroups; /* supplementary group count */
- __s8 reserved[128]; /* Reserved room */
+ __u32 flags; /* KSMBD_LOGIN_EXT_* */
+ __kernel_uid_r_t uid_r; /* with KSMBD_LOGIN_EXT_ID_R */
+ __kernel_gid_r_t gid_r;
+ __s8 reserved[84]; /* Reserved room */
__s8 ____payload[];
};
+#define KSMBD_LOGIN_EXT_ID_R 0x1
+#define KSMBD_LOGIN_EXT_GROUPS_R 0x2
+
/*
* IPC request to fetch net share config.
*/
diff --git a/fs/smb/server/mgmt/user_config.c b/fs/smb/server/mgmt/user_config.c
index 0be08cf18..6050bc748 100644
--- a/fs/smb/server/mgmt/user_config.c
+++ b/fs/smb/server/mgmt/user_config.c
@@ -56,6 +56,8 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
user->flags = resp->status;
user->gid = resp->gid;
user->uid = resp->uid;
+ user->kuid = make_kuid(&init_user_ns, resp->uid);
+ user->kgid = make_kgid(&init_user_ns, resp->gid);
user->passkey_sz = resp->hash_sz;
user->passkey = kmalloc(resp->hash_sz, KSMBD_DEFAULT_GFP);
if (user->passkey)
@@ -68,12 +70,38 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
goto err_free;
if (resp_ext) {
- user->sgid = kmemdup(resp_ext->____payload,
- resp_ext->ngroups * sizeof(gid_t),
+ int i;
+
+ if (resp_ext->flags & KSMBD_LOGIN_EXT_ID_R) {
+ /* the user's ids may be UUIDs or GUIDs */
+ user->kuid = make_kuid_r(&init_user_ns, &resp_ext->uid_r);
+ user->kgid = make_kgid_r(&init_user_ns, &resp_ext->gid_r);
+ if (!uid_valid(user->kuid) || !gid_valid(user->kgid))
+ goto err_free;
+ user->uid = from_kuid_munged(&init_user_ns, user->kuid);
+ user->gid = from_kgid_munged(&init_user_ns, user->kgid);
+ }
+
+ user->sgid = kcalloc(resp_ext->ngroups, sizeof(kgid_t),
KSMBD_DEFAULT_GFP);
- if (!user->sgid)
+ if (!user->sgid && resp_ext->ngroups)
goto err_free;
+ for (i = 0; i < resp_ext->ngroups; i++) {
+ if (resp_ext->flags & KSMBD_LOGIN_EXT_GROUPS_R) {
+ const __kernel_gid_r_t *g = (const __kernel_gid_r_t *)
+ resp_ext->____payload + i;
+
+ user->sgid[i] = make_kgid_r(&init_user_ns, g);
+ } else {
+ const gid_t *g = (const gid_t *)resp_ext->____payload + i;
+
+ user->sgid[i] = make_kgid(&init_user_ns, *g);
+ }
+ if (!gid_valid(user->sgid[i]))
+ goto err_free;
+ }
+
user->ngroups = resp_ext->ngroups;
ksmbd_debug(SMB, "supplementary groups : %d\n", user->ngroups);
}
@@ -81,6 +109,7 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp,
return user;
err_free:
+ kfree(user->sgid);
kfree(user->name);
kfree(user->passkey);
kfree(user);
diff --git a/fs/smb/server/mgmt/user_config.h b/fs/smb/server/mgmt/user_config.h
index cc460b4ff..e55470e00 100644
--- a/fs/smb/server/mgmt/user_config.h
+++ b/fs/smb/server/mgmt/user_config.h
@@ -13,13 +13,15 @@ struct ksmbd_user {
unsigned int uid;
unsigned int gid;
+ kuid_t kuid; /* the user: uid, or a UUID or GUID */
+ kgid_t kgid;
char *name;
size_t passkey_sz;
char *passkey;
int ngroups;
- gid_t *sgid;
+ kgid_t *sgid;
};
static inline bool user_guest(struct ksmbd_user *user)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 3e6d30242..f99f2fd92 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4083,7 +4083,7 @@ int smb2_open(struct ksmbd_work *work)
if (file_present && !(req->CreateOptions & FILE_DELETE_ON_CLOSE_LE)) {
rc = smb_check_perm_dacl(conn, &path, &daccess,
req->DesiredAccess,
- sess->user->uid, false);
+ sess->user->kuid, false);
if (rc)
goto err_out;
}
@@ -4252,8 +4252,8 @@ int smb2_open(struct ksmbd_work *work)
if (rc == -ENOENT) {
if (test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_ACL_XATTR)) {
- rc = smb_inherit_dacl(conn, &path, sess->user->uid,
- sess->user->gid);
+ rc = smb_inherit_dacl(conn, &path, sess->user->kuid,
+ sess->user->kgid);
}
if (rc) {
if (posix_acl_rc)
@@ -5186,7 +5186,7 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
path.dentry = dent;
rc = smb_check_perm_dacl(priv->work->conn, &path,
&daccess, daccess,
- priv->work->sess->user->uid,
+ priv->work->sess->user->kuid,
true);
if (rc) {
dput(dent);
diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c
index 080fbc9eb..8278c99a7 100644
--- a/fs/smb/server/smb_common.c
+++ b/fs/smb/server/smb_common.c
@@ -782,23 +782,23 @@ int __ksmbd_override_fsids(struct ksmbd_work *work,
struct ksmbd_user *user = sess->user;
struct cred *cred;
struct group_info *gi;
- unsigned int uid;
- unsigned int gid;
+ kuid_t uid;
+ kgid_t gid;
int i;
- uid = user_uid(user);
- gid = user_gid(user);
+ uid = user->kuid;
+ gid = user->kgid;
if (share->force_uid != KSMBD_SHARE_INVALID_UID)
- uid = share->force_uid;
+ uid = make_kuid(&init_user_ns, share->force_uid);
if (share->force_gid != KSMBD_SHARE_INVALID_GID)
- gid = share->force_gid;
+ gid = make_kgid(&init_user_ns, share->force_gid);
cred = prepare_kernel_cred(&init_task);
if (!cred)
return -ENOMEM;
- cred->fsuid = make_kuid(&init_user_ns, uid);
- cred->fsgid = make_kgid(&init_user_ns, gid);
+ cred->fsuid = uid;
+ cred->fsgid = gid;
gi = groups_alloc(user->ngroups);
if (!gi) {
@@ -807,7 +807,7 @@ int __ksmbd_override_fsids(struct ksmbd_work *work,
}
for (i = 0; i < user->ngroups; i++)
- gi->gid[i] = make_kgid(&init_user_ns, user->sgid[i]);
+ gi->gid[i] = user->sgid[i];
if (user->ngroups)
groups_sort(gi);
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index ae1527aa6..10c9b111f 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -7,6 +7,7 @@
*/
#include <linux/fs.h>
+#include <linux/unaligned.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/mnt_idmapping.h>
@@ -254,6 +255,67 @@ void id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid)
ssid->num_subauth++;
}
+/*
+ * UUID and GUID users and groups (Documentation/userspace-api/uid_r.rst)
+ * have no RID: they are S-1-22-<kind>-w0-w1-w2-w3, the 16 bytes of the id
+ * as four big endian words, next to S-1-22-1-<uid> and S-1-22-2-<gid>.
+ */
+#define SID_UNIX_UUID_USER 3
+#define SID_UNIX_UUID_GROUP 4
+#define SID_UNIX_GUID_USER 5
+#define SID_UNIX_GUID_GROUP 6
+
+static void id_r_to_sid(u32 kind, const u8 *id, struct smb_sid *ssid)
+{
+ int i;
+
+ smb_copy_sid(ssid, &sid_unix_users);
+ ssid->sub_auth[0] = cpu_to_le32(kind);
+ for (i = 0; i < 4; i++)
+ ssid->sub_auth[1 + i] = cpu_to_le32(get_unaligned_be32(id + 4 * i));
+ ssid->num_subauth = 5;
+}
+
+/* the kind (SID_UNIX_*) and 16 bytes of a UUID or GUID SID, or 0 */
+static u32 sid_to_id_r(const struct smb_sid *psid, u8 *id)
+{
+ u32 kind;
+ int i;
+
+ if (psid->num_subauth != 5 || psid->revision != sid_unix_users.revision ||
+ memcmp(psid->authority, sid_unix_users.authority, NUM_AUTHS))
+ return 0;
+ kind = le32_to_cpu(psid->sub_auth[0]);
+ if (kind < SID_UNIX_UUID_USER || kind > SID_UNIX_GUID_GROUP)
+ return 0;
+ for (i = 0; i < 4; i++)
+ put_unaligned_be32(le32_to_cpu(psid->sub_auth[1 + i]), id + 4 * i);
+ return kind;
+}
+
+/* id_to_sid() for any kuid: a classic one gets the usual SIDs */
+void ksmbd_uid_to_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid)
+{
+ if (uid_is_legacy(uid) || sidtype == SIDCREATOR_OWNER ||
+ sidtype == SIDCREATOR_GROUP)
+ id_to_sid(from_kuid_munged(&init_user_ns, uid), sidtype, ssid);
+ else if (uid.type == KID_UUID)
+ id_r_to_sid(SID_UNIX_UUID_USER, uid.uuid.b, ssid);
+ else
+ id_r_to_sid(SID_UNIX_GUID_USER, uid.guid.b, ssid);
+}
+
+void ksmbd_gid_to_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid)
+{
+ if (gid_is_legacy(gid) || sidtype == SIDCREATOR_OWNER ||
+ sidtype == SIDCREATOR_GROUP)
+ id_to_sid(from_kgid_munged(&init_user_ns, gid), sidtype, ssid);
+ else if (gid.type == KID_UUID)
+ id_r_to_sid(SID_UNIX_UUID_GROUP, gid.uuid.b, ssid);
+ else
+ id_r_to_sid(SID_UNIX_GUID_GROUP, gid.guid.b, ssid);
+}
+
static int sid_to_id(struct mnt_idmap *idmap,
struct smb_sid *psid, uint sidtype,
struct smb_fattr *fattr)
@@ -276,6 +338,42 @@ static int sid_to_id(struct mnt_idmap *idmap,
return -EIO;
}
+ {
+ u8 id[16];
+ u32 kind = sid_to_id_r(psid, id);
+
+ if (kind && sidtype == SIDOWNER &&
+ (kind == SID_UNIX_UUID_USER || kind == SID_UNIX_GUID_USER)) {
+ kuid_t uid;
+
+ if (kind == SID_UNIX_UUID_USER)
+ uid = KUIDT_INIT_UUID(*(uuid_t *)id);
+ else
+ uid = KUIDT_INIT_GUID(*(guid_t *)id);
+ uid = from_vfsuid(idmap, &init_user_ns, VFSUIDT_INIT(uid));
+ if (!uid_valid(uid))
+ return -EINVAL;
+ fattr->cf_uid = uid;
+ return 0;
+ }
+ if (kind && sidtype != SIDOWNER &&
+ (kind == SID_UNIX_UUID_GROUP || kind == SID_UNIX_GUID_GROUP)) {
+ kgid_t gid;
+
+ if (kind == SID_UNIX_UUID_GROUP)
+ gid = KGIDT_INIT_UUID(*(uuid_t *)id);
+ else
+ gid = KGIDT_INIT_GUID(*(guid_t *)id);
+ gid = from_vfsgid(idmap, &init_user_ns, VFSGIDT_INIT(gid));
+ if (!gid_valid(gid))
+ return -EINVAL;
+ fattr->cf_gid = gid;
+ return 0;
+ }
+ if (kind)
+ return -EINVAL;
+ }
+
if (sidtype == SIDOWNER) {
kuid_t uid;
uid_t id;
@@ -640,18 +738,18 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap,
break;
if (pace->e_tag == ACL_USER) {
- uid_t uid;
+ kuid_t uid;
unsigned int sid_type = SIDOWNER;
- uid = posix_acl_uid_translate(idmap, pace);
- if (!uid)
+ uid = posix_acl_kuid_translate(idmap, pace);
+ if (uid_eq(uid, GLOBAL_ROOT_UID))
sid_type = SIDUNIX_USER;
- id_to_sid(uid, sid_type, sid);
+ ksmbd_uid_to_sid(uid, sid_type, sid);
} else if (pace->e_tag == ACL_GROUP) {
- gid_t gid;
+ kgid_t gid;
- gid = posix_acl_gid_translate(idmap, pace);
- id_to_sid(gid, SIDUNIX_GROUP, sid);
+ gid = posix_acl_kgid_translate(idmap, pace);
+ ksmbd_gid_to_sid(gid, SIDUNIX_GROUP, sid);
} else if (pace->e_tag == ACL_OTHER && !had_nt_aces) {
smb_copy_sid(sid, &sid_everyone);
} else {
@@ -813,7 +911,7 @@ static void set_mode_dacl(struct mnt_idmap *idmap,
u16 num_aces = 0;
u16 size = 0, ace_size = 0;
uid_t uid;
- const struct smb_sid *sid;
+ struct smb_sid owner_sid, group_sid;
pace = pndace = (struct smb_ace *)((char *)pndacl + sizeof(struct smb_acl));
@@ -823,25 +921,20 @@ static void set_mode_dacl(struct mnt_idmap *idmap,
goto out;
}
- /* owner RID */
+ /* owner: RID, or a UUID or GUID SID */
uid = from_kuid(&init_user_ns, fattr->cf_uid);
- if (uid)
- sid = &server_conf.domain_sid;
- else
- sid = &sid_unix_users;
- ace_size = fill_ace_for_sid(pace, sid, ACCESS_ALLOWED, 0,
+ ksmbd_uid_to_sid(fattr->cf_uid, uid ? SIDOWNER : SIDUNIX_USER, &owner_sid);
+ ace_size = fill_ace_for_sid(pace, &owner_sid, ACCESS_ALLOWED, 0,
fattr->cf_mode, 0700);
- pace->sid.sub_auth[pace->sid.num_subauth++] = cpu_to_le32(uid);
- pace->size = cpu_to_le16(ace_size + 4);
+ pace->size = cpu_to_le16(ace_size);
size += le16_to_cpu(pace->size);
pace = (struct smb_ace *)((char *)pndace + size);
- /* Group RID */
- ace_size = fill_ace_for_sid(pace, &sid_unix_groups,
+ /* Group: RID, or a UUID or GUID SID */
+ ksmbd_gid_to_sid(fattr->cf_gid, SIDUNIX_GROUP, &group_sid);
+ ace_size = fill_ace_for_sid(pace, &group_sid,
ACCESS_ALLOWED, 0, fattr->cf_mode, 0070);
- pace->sid.sub_auth[pace->sid.num_subauth++] =
- cpu_to_le32(from_kgid(&init_user_ns, fattr->cf_gid));
- pace->size = cpu_to_le16(ace_size + 4);
+ pace->size = cpu_to_le16(ace_size);
size += le16_to_cpu(pace->size);
pace = (struct smb_ace *)((char *)pndace + size);
num_aces = 3;
@@ -1038,7 +1131,6 @@ int build_sec_desc(struct mnt_idmap *idmap,
struct smb_sid *nowner_sid_ptr, *ngroup_sid_ptr;
struct smb_acl *dacl_ptr = NULL; /* no need for SACL ptr */
uid_t uid;
- gid_t gid;
unsigned int sid_type = SIDOWNER;
nowner_sid_ptr = kmalloc_obj(struct smb_sid, KSMBD_DEFAULT_GFP);
@@ -1048,7 +1140,7 @@ int build_sec_desc(struct mnt_idmap *idmap,
uid = from_kuid(&init_user_ns, fattr->cf_uid);
if (!uid)
sid_type = SIDUNIX_USER;
- id_to_sid(uid, sid_type, nowner_sid_ptr);
+ ksmbd_uid_to_sid(fattr->cf_uid, sid_type, nowner_sid_ptr);
ngroup_sid_ptr = kmalloc_obj(struct smb_sid, KSMBD_DEFAULT_GFP);
if (!ngroup_sid_ptr) {
@@ -1056,8 +1148,7 @@ int build_sec_desc(struct mnt_idmap *idmap,
return -ENOMEM;
}
- gid = from_kgid(&init_user_ns, fattr->cf_gid);
- id_to_sid(gid, SIDUNIX_GROUP, ngroup_sid_ptr);
+ ksmbd_gid_to_sid(fattr->cf_gid, SIDUNIX_GROUP, ngroup_sid_ptr);
offset = sizeof(struct smb_ntsd);
pntsd->sacloffset = 0;
@@ -1185,7 +1276,7 @@ static int smb_validate_ntsd_sid(struct smb_ntsd *pntsd, size_t pntsd_size,
int smb_inherit_dacl(struct ksmbd_conn *conn,
const struct path *path,
- unsigned int uid, unsigned int gid)
+ kuid_t uid, kgid_t gid)
{
const struct smb_sid *psid, *creator = NULL;
struct smb_ace *parent_aces, *aces;
@@ -1292,11 +1383,11 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
if (!compare_sids(&creator_owner, &parent_aces->sid)) {
creator = &creator_owner;
- id_to_sid(uid, SIDOWNER, &owner_sid);
+ ksmbd_uid_to_sid(uid, SIDOWNER, &owner_sid);
psid = &owner_sid;
} else if (!compare_sids(&creator_group, &parent_aces->sid)) {
creator = &creator_group;
- id_to_sid(gid, SIDUNIX_GROUP, &group_sid);
+ ksmbd_gid_to_sid(gid, SIDUNIX_GROUP, &group_sid);
psid = &group_sid;
} else {
creator = NULL;
@@ -1445,7 +1536,7 @@ bool smb_inherit_flags(int flags, bool is_dir)
}
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
- __le32 *pdaccess, __le32 raw_daccess, int uid,
+ __le32 *pdaccess, __le32 raw_daccess, kuid_t uid,
bool strict)
{
struct mnt_idmap *idmap = mnt_idmap(path->mnt);
@@ -1494,9 +1585,9 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
goto err_out;
}
- if (!uid)
+ if (uid_eq(uid, GLOBAL_ROOT_UID))
sid_type = SIDUNIX_USER;
- id_to_sid(uid, sid_type, &sid);
+ ksmbd_uid_to_sid(uid, sid_type, &sid);
if (*pdaccess & FILE_MAXIMAL_ACCESS_LE) {
access_bits = READ_CONTROL | WRITE_DAC | FILE_READ_ATTRIBUTES |
@@ -1581,18 +1672,27 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
if (IS_ENABLED(CONFIG_FS_POSIX_ACL)) {
posix_acls = get_inode_acl(d_inode(path->dentry), ACL_TYPE_ACCESS);
if (!IS_ERR_OR_NULL(posix_acls) && !found && !others_ace) {
- unsigned int id = -1;
+ kuid_t pa_uid;
+ bool match;
pa_entry = posix_acls->a_entries;
for (i = 0; i < posix_acls->a_count; i++, pa_entry++) {
- if (pa_entry->e_tag == ACL_USER)
- id = posix_acl_uid_translate(idmap, pa_entry);
- else if (pa_entry->e_tag == ACL_GROUP)
- id = posix_acl_gid_translate(idmap, pa_entry);
- else
+ /*
+ * A group entry matches a gid equal to the uid,
+ * as it always has.
+ */
+ if (pa_entry->e_tag == ACL_USER) {
+ pa_uid = posix_acl_kuid_translate(idmap, pa_entry);
+ match = uid_eq(pa_uid, uid);
+ } else if (pa_entry->e_tag == ACL_GROUP) {
+ match = uid_is_legacy(uid) &&
+ posix_acl_gid_translate(idmap, pa_entry) ==
+ from_kuid(&init_user_ns, uid);
+ } else {
continue;
+ }
- if (id == uid) {
+ if (match) {
mode_to_access_flags(pa_entry->e_perm,
0777,
&access_bits);
diff --git a/fs/smb/server/smbacl.h b/fs/smb/server/smbacl.h
index 01810c16c..b6a01f0d4 100644
--- a/fs/smb/server/smbacl.h
+++ b/fs/smb/server/smbacl.h
@@ -93,18 +93,33 @@ void posix_state_to_acl(struct posix_acl_state *state,
int compare_sids(const struct smb_sid *ctsid, const struct smb_sid *cwsid);
bool smb_inherit_flags(int flags, bool is_dir);
int smb_inherit_dacl(struct ksmbd_conn *conn, const struct path *path,
- unsigned int uid, unsigned int gid);
+ kuid_t uid, kgid_t gid);
int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
- __le32 *pdaccess, __le32 raw_daccess, int uid,
+ __le32 *pdaccess, __le32 raw_daccess, kuid_t uid,
bool strict);
int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
const struct path *path, struct smb_ntsd *pntsd, int ntsd_len,
bool type_check, bool get_write);
void id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid);
+void ksmbd_uid_to_sid(kuid_t uid, uint sidtype, struct smb_sid *ssid);
+void ksmbd_gid_to_sid(kgid_t gid, uint sidtype, struct smb_sid *ssid);
void ksmbd_init_domain(u32 *sub_auth);
size_t smb_acl_sec_desc_scratch_len(struct smb_fattr *fattr,
struct smb_ntsd *ppntsd, int ppntsd_size, int addition_info);
+/* the owner of an ACL entry as a kuid (also a UUID or GUID) */
+static inline kuid_t posix_acl_kuid_translate(struct mnt_idmap *idmap,
+ struct posix_acl_entry *pace)
+{
+ return vfsuid_into_kuid(make_vfsuid(idmap, &init_user_ns, pace->e_uid));
+}
+
+static inline kgid_t posix_acl_kgid_translate(struct mnt_idmap *idmap,
+ struct posix_acl_entry *pace)
+{
+ return vfsgid_into_kgid(make_vfsgid(idmap, &init_user_ns, pace->e_gid));
+}
+
static inline uid_t posix_acl_uid_translate(struct mnt_idmap *idmap,
struct posix_acl_entry *pace)
{
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 5ad33fbfc..934e62de0 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -564,7 +564,9 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
return -EINVAL;
}
msg_sz = sizeof(struct ksmbd_login_response_ext) +
- resp->ngroups * sizeof(gid_t);
+ resp->ngroups *
+ (resp->flags & KSMBD_LOGIN_EXT_GROUPS_R ?
+ sizeof(__kernel_gid_r_t) : sizeof(gid_t));
}
}
}
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 5eae49ed1..c0b01d8a3 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -1580,8 +1580,8 @@ int ksmbd_vfs_set_durable_owner(struct ksmbd_file *fp,
return -ENOMEM;
spin_lock(&fp->f_lock);
- fp->owner.uid = user->uid;
- fp->owner.gid = user->gid;
+ fp->owner.uid = user->kuid;
+ fp->owner.gid = user->kgid;
fp->owner.name = name;
spin_unlock(&fp->f_lock);
@@ -1611,7 +1611,7 @@ bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp,
goto out;
/* Check if the UID and GID match first (fast path) */
- if (fp->owner.uid != user->uid || fp->owner.gid != user->gid)
+ if (!uid_eq(fp->owner.uid, user->kuid) || !gid_eq(fp->owner.gid, user->kgid))
goto out;
/* Validate the account name to ensure the same SecurityContext */
@@ -1817,7 +1817,8 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
up_write(&ci->m_lock);
spin_lock(&fp->f_lock);
- fp->owner.uid = fp->owner.gid = 0;
+ fp->owner.uid = GLOBAL_ROOT_UID;
+ fp->owner.gid = GLOBAL_ROOT_GID;
kfree(fp->owner.name);
fp->owner.name = NULL;
spin_unlock(&fp->f_lock);
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 1c6fc4349..d028a3944 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -77,8 +77,8 @@ enum {
/* Owner information for durable handle reconnect */
struct durable_owner {
- unsigned int uid;
- unsigned int gid;
+ kuid_t uid;
+ kgid_t gid;
char *name;
};
[-- Attachment #41: uid_r-0040-nfsd-nfs-carry-UUID-and-GUID-owners-as-their-text-fo.patch --]
[-- Type: text/x-patch, Size: 7714 bytes --]
From ab4f638ba87da32eeb855689b52a9df2c2badec1 Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 14:45:48 +0200
Subject: [PATCH 40/43] nfsd, nfs: carry UUID and GUID owners as their text
form
NFSv4 owners and ACL principals are strings. Classic ids go through
idmapd or, with id mapping disabled, as decimal numbers; a UUID or GUID
user or group (Documentation/userspace-api/uid_r.rst) has neither a
number nor an idmapd entry and showed as the overflow id.
Encode such an id as its text form -- "xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx" for a UUID, the same in braces for a GUID -- in both
directions on the server and the client, and take such a string (it
has no '@', so it can't be a user@domain name) as that id again. NFSv4
superblocks set SB_I_UID_R, so the client can chown to such owners.
RPC credentials stay classic: AUTH_SYS has a 32 bit uid, so a UUID or
GUID user on a client still reaches the server as the overflow id.
Assisted-by: Claude:claude-opus-5-5
---
fs/nfs/nfs4idmap.c | 53 +++++++++++++++++++++++++++++++++++
fs/nfs/super.c | 2 ++
fs/nfsd/nfs4idmap.c | 68 +++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 121 insertions(+), 2 deletions(-)
diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
index bc397110d..0ca50bf8f 100644
--- a/fs/nfs/nfs4idmap.c
+++ b/fs/nfs/nfs4idmap.c
@@ -729,11 +729,44 @@ idmap_release_pipe(struct inode *inode)
nfs_idmap_complete_pipe_upcall(data, -EPIPE);
}
+/*
+ * UUID and GUID owners (Documentation/userspace-api/uid_r.rst) travel as
+ * the id's text form, which has no '@' and no number for idmapd.
+ */
+static bool nfs_id_r_name(const char *name, size_t namelen, char *buf)
+{
+ if (namelen != 36 && !(namelen == 38 && name[0] == '{'))
+ return false;
+ memcpy(buf, name, namelen);
+ buf[namelen] = '\0';
+ return true;
+}
+
+static int nfs_id_r_to_string(const char *str, char *buf, size_t buflen)
+{
+ size_t len = strlen(str);
+
+ if (len > buflen)
+ return -ERANGE;
+ memcpy(buf, str, len);
+ return len;
+}
+
int nfs_map_name_to_uid(const struct nfs_server *server, const char *name, size_t namelen, kuid_t *uid)
{
struct idmap *idmap = server->nfs_client->cl_idmap;
__u32 id = -1;
int ret = 0;
+ char idbuf[KID_STR_LEN];
+
+ if (nfs_id_r_name(name, namelen, idbuf)) {
+ kuid_t k = make_kuid_str(idmap_userns(idmap), idbuf);
+
+ if (uid_valid(k) && !uid_is_legacy(k)) {
+ *uid = k;
+ return 0;
+ }
+ }
if (!nfs_map_string_to_numeric(name, namelen, &id))
ret = nfs_idmap_lookup_id(name, namelen, "uid", &id, idmap);
@@ -751,6 +784,16 @@ int nfs_map_group_to_gid(const struct nfs_server *server, const char *name, size
struct idmap *idmap = server->nfs_client->cl_idmap;
__u32 id = -1;
int ret = 0;
+ char idbuf[KID_STR_LEN];
+
+ if (nfs_id_r_name(name, namelen, idbuf)) {
+ kgid_t k = make_kgid_str(idmap_userns(idmap), idbuf);
+
+ if (gid_valid(k) && !gid_is_legacy(k)) {
+ *gid = k;
+ return 0;
+ }
+ }
if (!nfs_map_string_to_numeric(name, namelen, &id))
ret = nfs_idmap_lookup_id(name, namelen, "gid", &id, idmap);
@@ -769,6 +812,11 @@ int nfs_map_uid_to_name(const struct nfs_server *server, kuid_t uid, char *buf,
int ret = -EINVAL;
__u32 id;
+ if (!uid_is_legacy(uid) && kuid_has_mapping(idmap_userns(idmap), uid)) {
+ char str[KID_STR_LEN];
+
+ return nfs_id_r_to_string(kuid_str(uid, str), buf, buflen);
+ }
id = from_kuid_munged(idmap_userns(idmap), uid);
if (!(server->caps & NFS_CAP_UIDGID_NOMAP))
ret = nfs_idmap_lookup_name(id, "user", buf, buflen, idmap);
@@ -783,6 +831,11 @@ int nfs_map_gid_to_group(const struct nfs_server *server, kgid_t gid, char *buf,
int ret = -EINVAL;
__u32 id;
+ if (!gid_is_legacy(gid) && kgid_has_mapping(idmap_userns(idmap), gid)) {
+ char str[KID_STR_LEN];
+
+ return nfs_id_r_to_string(kgid_str(gid, str), buf, buflen);
+ }
id = from_kgid_munged(idmap_userns(idmap), gid);
if (!(server->caps & NFS_CAP_UIDGID_NOMAP))
ret = nfs_idmap_lookup_name(id, "group", buf, buflen, idmap);
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index cb19f1540..2d292e441 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -1100,6 +1100,8 @@ static void nfs_fill_super(struct super_block *sb, struct nfs_fs_context *ctx)
break;
case 4:
sb->s_iflags |= SB_I_NOUMASK;
+ /* owners are strings: UUIDs and GUIDs in their text form */
+ sb->s_iflags |= SB_I_UID_R;
sb->s_time_gran = 1;
sb->s_time_min = S64_MIN;
sb->s_time_max = S64_MAX;
diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c
index 71ba61b5d..669484eb3 100644
--- a/fs/nfsd/nfs4idmap.c
+++ b/fs/nfsd/nfs4idmap.c
@@ -643,6 +643,33 @@ static __be32 encode_name_from_id(struct xdr_stream *xdr,
return idmap_id_to_name(xdr, rqstp, type, id);
}
+/*
+ * UUID and GUID users and groups (Documentation/userspace-api/uid_r.rst)
+ * have no number for idmapd or for numeric owner strings: their owner
+ * string is the id's text form, "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" or
+ * "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", in either mode. Such a string
+ * (it has no '@') is taken as that id again; classic ids are unchanged.
+ */
+static bool id_r_name(const char *name, size_t namelen, char *buf)
+{
+ if (namelen != 36 && !(namelen == 38 && name[0] == '{'))
+ return false;
+ memcpy(buf, name, namelen);
+ buf[namelen] = '\0';
+ return true;
+}
+
+static __be32 encode_id_r(struct xdr_stream *xdr, const char *str)
+{
+ size_t len = strlen(str);
+ __be32 *p = xdr_reserve_space(xdr, len + 4);
+
+ if (!p)
+ return nfserr_resource;
+ xdr_encode_opaque(p, str, len);
+ return 0;
+}
+
/**
* nfsd_map_name_to_uid - Map user@domain to local UID
* @rqstp: RPC execution context
@@ -671,6 +698,16 @@ __be32 nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name,
if (name == NULL || namelen == 0)
return nfserr_inval;
+ {
+ char buf[KID_STR_LEN];
+
+ if (id_r_name(name, namelen, buf)) {
+ *uid = make_kuid_str(nfsd_user_namespace(rqstp), buf);
+ if (uid_valid(*uid) && !uid_is_legacy(*uid))
+ return nfs_ok;
+ }
+ }
+
status = do_name_to_id(rqstp, IDMAP_TYPE_USER, name, namelen, &id);
if (status)
return status;
@@ -708,6 +745,16 @@ __be32 nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name,
if (name == NULL || namelen == 0)
return nfserr_inval;
+ {
+ char buf[KID_STR_LEN];
+
+ if (id_r_name(name, namelen, buf)) {
+ *gid = make_kgid_str(nfsd_user_namespace(rqstp), buf);
+ if (gid_valid(*gid) && !gid_is_legacy(*gid))
+ return nfs_ok;
+ }
+ }
+
status = do_name_to_id(rqstp, IDMAP_TYPE_GROUP, name, namelen, &id);
if (status)
return status;
@@ -720,13 +767,30 @@ __be32 nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name,
__be32 nfsd4_encode_user(struct xdr_stream *xdr, struct svc_rqst *rqstp,
kuid_t uid)
{
- u32 id = from_kuid_munged(nfsd_user_namespace(rqstp), uid);
+ u32 id;
+
+ /* (outside the initial user namespace: the overflow id below) */
+ if (!uid_is_legacy(uid) &&
+ kuid_has_mapping(nfsd_user_namespace(rqstp), uid)) {
+ char buf[KID_STR_LEN];
+
+ return encode_id_r(xdr, kuid_str(uid, buf));
+ }
+ id = from_kuid_munged(nfsd_user_namespace(rqstp), uid);
return encode_name_from_id(xdr, rqstp, IDMAP_TYPE_USER, id);
}
__be32 nfsd4_encode_group(struct xdr_stream *xdr, struct svc_rqst *rqstp,
kgid_t gid)
{
- u32 id = from_kgid_munged(nfsd_user_namespace(rqstp), gid);
+ u32 id;
+
+ if (!gid_is_legacy(gid) &&
+ kgid_has_mapping(nfsd_user_namespace(rqstp), gid)) {
+ char buf[KID_STR_LEN];
+
+ return encode_id_r(xdr, kgid_str(gid, buf));
+ }
+ id = from_kgid_munged(nfsd_user_namespace(rqstp), gid);
return encode_name_from_id(xdr, rqstp, IDMAP_TYPE_GROUP, id);
}
[-- Attachment #42: uid_r-0041-Documentation-uid_r-describe-SMB-and-NFSv4.patch --]
[-- Type: text/x-patch, Size: 2024 bytes --]
From 591abc7a124985b02c2a0ff87314b783e133542e Mon Sep 17 00:00:00 2001
From: Jan Koester <tuxist@googlemail.com>
Date: Sat, 10 Oct 2026 14:46:03 +0200
Subject: [PATCH 41/43] Documentation: uid_r: describe SMB and NFSv4
ksmbd's login extension and SIDs for UUID and GUID users, NFSv4 owner
strings, and what AUTH_SYS and the SMB3 POSIX extensions can't carry.
Assisted-by: Claude:claude-opus-5-5
---
Documentation/userspace-api/uid_r.rst | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/Documentation/userspace-api/uid_r.rst b/Documentation/userspace-api/uid_r.rst
index c08048545..d16d4317f 100644
--- a/Documentation/userspace-api/uid_r.rst
+++ b/Documentation/userspace-api/uid_r.rst
@@ -159,11 +159,28 @@ RPCSEC_GSS upcalls
user as ``__kernel_uid_r_t``, then the usual fields. The binary (v0)
upcall can't carry such users and fails with ``EACCES``.
+SMB (ksmbd)
+ The login response extension (``struct ksmbd_login_response_ext``)
+ carries the user's ids as ``__kernel_uid_r_t`` with
+ ``KSMBD_LOGIN_EXT_ID_R`` and the supplementary groups as
+ ``__kernel_gid_r_t`` with ``KSMBD_LOGIN_EXT_GROUPS_R``. Such users and
+ groups appear in security descriptors as
+ ``S-1-22-<kind>-w0-w1-w2-w3``: kind 3 and 4 for a UUID user and group,
+ 5 and 6 for a GUID user and group, w0 to w3 the 16 bytes of the id as
+ big endian words.
+
+NFSv4
+ Owners and ACL principals that are UUIDs or GUIDs travel as their text
+ form, on the server and the client, with or without idmapd.
+
Limitations
===========
- Quotas don't account UUID and GUID owners; quota files hold 32 bit ids.
- RPCSEC_GSS needs a gssd that knows ``uid_r=`` for UUID and GUID users.
+- NFS with AUTH_SYS sends a 32 bit uid: a UUID or GUID user on a client
+ reaches the server as the overflow id.
+- The SMB3 POSIX extensions' fixed size SID fields show the overflow id.
- UUID and GUID users can't create unprivileged ublk devices.
- Audit filter rules, SafeSetID policies and IMA policy rules name
classic ids only.
[-- Attachment #43: uid_r-0042-ksmbd-pass-through-NTLM-authentication-to-a-domain-c.patch --]
[-- Type: text/x-patch, Size: 11593 bytes --]
From c3bd00e9eac032f25d2468cfd286788ee3539fc8 Mon Sep 17 00:00:00 2001
From: Jan Koester <jan.koester@tuxist.de>
Date: Sun, 11 Oct 2026 17:28:28 +0200
Subject: [PATCH 42/43] ksmbd: pass through NTLM authentication to a domain
controller
A ksmbd server that is a member of an Active Directory domain cannot
check the NTLM response of a domain user on its own: it has no password
hash for that user. Add a way to defer the check to user space, which
can ask a domain controller over NETLOGON (NetrLogonSamLogonEx).
When the login response for a user carries the new
KSMBD_USER_FLAG_PASSTHROUGH flag, ksmbd no longer computes the NTLMv2
response from a local hash. Instead ksmbd_decode_ntlmssp_auth_blob()
hands the server challenge and the client's user name, domain name,
workstation and NT/LM responses to user space through the new
KSMBD_EVENT_NTLM_AUTHEN_REQUEST event and takes the session base key
from the KSMBD_EVENT_NTLM_AUTHEN_RESPONSE, then continues with the
existing key-exchange path.
The two new events get a strict netlink policy and are validated in
ipc_validate_msg(); ksmbd_nl_init_fixup() keeps strict validation for
commands at or after resv_start_op so the new fixed-size response is
checked.
Assisted-by: Claude:claude-opus-4-8
---
fs/smb/server/auth.c | 68 ++++++++++++++++++++++++++++++++
fs/smb/server/ksmbd_netlink.h | 41 ++++++++++++++++++++
fs/smb/server/transport_ipc.c | 73 ++++++++++++++++++++++++++++++++++-
fs/smb/server/transport_ipc.h | 3 ++
4 files changed, 183 insertions(+), 2 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 8d22d28a1..3536032a1 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -79,6 +79,16 @@ static int calc_ntlmv2_hash(struct ksmbd_conn *conn, struct ksmbd_session *sess,
__le16 *uniname = NULL;
struct hmac_md5_ctx ctx;
+ /*
+ * A pass-through user (KSMBD_USER_FLAG_PASSTHROUGH) has no local
+ * password hash and must never reach the NTLMv2 code -- it is handled
+ * in ksmbd_decode_ntlmssp_auth_blob() instead. Guard against a NULL
+ * passkey anyway so a user space that sets KSMBD_USER_FLAG_OK without a
+ * hash can only fail the login, not crash the kernel.
+ */
+ if (!user_passkey(sess->user))
+ return -EINVAL;
+
hmac_md5_init_usingrawkey(&ctx, user_passkey(sess->user),
CIFS_ENCPWD_SIZE);
@@ -182,6 +192,55 @@ int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
return rc;
}
+/**
+ * ksmbd_auth_ntlm_passthrough() - NTLM authentication by user space
+ * @conn: connection
+ * @authblob: authenticate blob
+ * @blob_len: length of @authblob
+ * @sess_key: session base key output buffer
+ *
+ * For a user that user space has no password hash for (a domain user): it
+ * checks the response, e.g. with a domain controller, and returns the
+ * session base key.
+ *
+ * Return: 0 on success, error number on error
+ */
+static int ksmbd_auth_ntlm_passthrough(struct ksmbd_conn *conn,
+ struct authenticate_message *authblob,
+ int blob_len, char *sess_key)
+{
+ const struct security_buffer *bufs[5] = {
+ &authblob->UserName, &authblob->DomainName,
+ &authblob->WorkstationName, &authblob->NtChallengeResponse,
+ &authblob->LmChallengeResponse,
+ };
+ struct ksmbd_ntlm_authen_response *resp;
+ const char *parts[5];
+ unsigned short lens[5];
+ int i, rc;
+
+ for (i = 0; i < 5; i++) {
+ unsigned int off = le32_to_cpu(bufs[i]->BufferOffset);
+
+ lens[i] = le16_to_cpu(bufs[i]->Length);
+ if (blob_len < (u64)off + lens[i])
+ return -EINVAL;
+ parts[i] = (const char *)authblob + off;
+ }
+
+ resp = ksmbd_ipc_ntlm_authen_request(conn->ntlmssp.cryptkey, parts,
+ lens);
+ if (!resp)
+ return -EINVAL;
+
+ rc = resp->status & KSMBD_USER_FLAG_OK ? 0 : -EINVAL;
+ if (!rc)
+ memcpy(sess_key, resp->session_key, SMB2_NTLMV2_SESSKEY_SIZE);
+ memzero_explicit(resp->session_key, sizeof(resp->session_key));
+ kvfree(resp);
+ return rc;
+}
+
/**
* ksmbd_decode_ntlmssp_auth_blob() - helper function to construct
* authenticate blob
@@ -223,6 +282,14 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
nt_len < CIFS_ENCPWD_SIZE)
return -EINVAL;
+ if (test_user_flag(sess->user, KSMBD_USER_FLAG_PASSTHROUGH)) {
+ ret = ksmbd_auth_ntlm_passthrough(conn, authblob, blob_len,
+ sess_key);
+ if (ret)
+ return ret;
+ goto key_exchange;
+ }
+
/* TODO : use domain name that imported from configuration file */
domain_name = smb_strndup_from_utf16((const char *)authblob + dn_off,
dn_len, true, conn->local_nls);
@@ -240,6 +307,7 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
if (ret)
return ret;
+key_exchange:
/* The recovered secondary session key */
if (conn->ntlmssp.client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) {
struct arc4_ctx *ctx_arc4;
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index 40c43507b..c93eb956c 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -55,6 +55,11 @@
*
* - KSMBD_EVENT_LOGIN_REQUEST_EXT/RESPONSE_EXT(ksmbd_login_request_ext/response_ext)
* This event is to get user account extension info to user IPC daemon.
+ *
+ * - KSMBD_EVENT_NTLM_AUTHEN_REQUEST/RESPONSE(ksmbd_ntlm_authen_request/response)
+ * This event is to make NTLM authentication of a user the login response
+ * marked KSMBD_USER_FLAG_PASSTHROUGH to be processed in userspace, e.g.
+ * by a domain controller.
*/
#define KSMBD_GENL_NAME "SMBD_GENL"
@@ -174,6 +179,38 @@ struct ksmbd_login_response_ext {
#define KSMBD_LOGIN_EXT_ID_R 0x1
#define KSMBD_LOGIN_EXT_GROUPS_R 0x2
+/*
+ * IPC request to check the NTLM response of a user whose login response
+ * had KSMBD_USER_FLAG_PASSTHROUGH: user space has no password hash for it
+ * and asks someone who has, e.g. a domain controller (NETLOGON).
+ */
+struct ksmbd_ntlm_authen_request {
+ __u32 handle;
+ __u8 challenge[8]; /* the server challenge */
+ __u16 user_name_len; /* lengths of the payload parts */
+ __u16 domain_name_len;
+ __u16 workstation_len;
+ __u16 nt_response_len;
+ __u16 lm_response_len;
+ __u16 reserved16;
+ __u32 reserved[16]; /* Reserved room */
+ __u8 payload[]; /*
+ * user name, domain name, workstation
+ * (UTF-16LE, as the client sent them),
+ * NT response, LM response
+ */
+};
+
+/*
+ * IPC response to an NTLM authentication request
+ */
+struct ksmbd_ntlm_authen_response {
+ __u32 handle;
+ __u32 status; /* KSMBD_USER_FLAG_OK or _BAD_PASSWORD */
+ __u8 session_key[16]; /* the session base key */
+ __u32 reserved[16]; /* Reserved room */
+};
+
/*
* IPC request to fetch net share config.
*/
@@ -339,6 +376,9 @@ enum ksmbd_event {
KSMBD_EVENT_LOGIN_REQUEST_EXT,
KSMBD_EVENT_LOGIN_RESPONSE_EXT,
+ KSMBD_EVENT_NTLM_AUTHEN_REQUEST,
+ KSMBD_EVENT_NTLM_AUTHEN_RESPONSE,
+
__KSMBD_EVENT_MAX,
KSMBD_EVENT_MAX = __KSMBD_EVENT_MAX - 1
};
@@ -370,6 +410,7 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_USER_FLAG_GUEST_ACCOUNT BIT(4)
#define KSMBD_USER_FLAG_DELAY_SESSION BIT(5)
#define KSMBD_USER_FLAG_EXTENSION BIT(6)
+#define KSMBD_USER_FLAG_PASSTHROUGH BIT(7)
/*
* Share config flags.
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 934e62de0..a2c46bb68 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -127,6 +127,10 @@ static const struct nla_policy ksmbd_nl_policy[KSMBD_EVENT_MAX + 1] = {
[KSMBD_EVENT_LOGIN_RESPONSE_EXT] = {
.len = sizeof(struct ksmbd_login_response_ext),
},
+ [KSMBD_EVENT_NTLM_AUTHEN_REQUEST] =
+ NLA_POLICY_MIN_LEN(sizeof(struct ksmbd_ntlm_authen_request)),
+ [KSMBD_EVENT_NTLM_AUTHEN_RESPONSE] =
+ NLA_POLICY_EXACT_LEN(sizeof(struct ksmbd_ntlm_authen_response)),
};
static struct genl_ops ksmbd_genl_ops[] = {
@@ -202,6 +206,14 @@ static struct genl_ops ksmbd_genl_ops[] = {
.cmd = KSMBD_EVENT_LOGIN_RESPONSE_EXT,
.doit = handle_generic_event,
},
+ {
+ .cmd = KSMBD_EVENT_NTLM_AUTHEN_REQUEST,
+ .doit = handle_unsupported_event,
+ },
+ {
+ .cmd = KSMBD_EVENT_NTLM_AUTHEN_RESPONSE,
+ .doit = handle_generic_event,
+ },
};
static struct genl_family ksmbd_genl_family = {
@@ -220,9 +232,11 @@ static void ksmbd_nl_init_fixup(void)
{
int i;
+ /* the commands from resv_start_op on are validated strictly */
for (i = 0; i < ARRAY_SIZE(ksmbd_genl_ops); i++)
- ksmbd_genl_ops[i].validate = GENL_DONT_VALIDATE_STRICT |
- GENL_DONT_VALIDATE_DUMP;
+ if (ksmbd_genl_ops[i].cmd < ksmbd_genl_family.resv_start_op)
+ ksmbd_genl_ops[i].validate = GENL_DONT_VALIDATE_STRICT |
+ GENL_DONT_VALIDATE_DUMP;
ksmbd_genl_family.policy = ksmbd_nl_policy;
}
@@ -549,6 +563,10 @@ static int ipc_validate_msg(struct ipc_msg_table_entry *entry)
return -EINVAL;
break;
}
+ case KSMBD_EVENT_NTLM_AUTHEN_REQUEST:
+ if (entry->msg_sz != sizeof(struct ksmbd_ntlm_authen_response))
+ return -EINVAL;
+ break;
case KSMBD_EVENT_LOGIN_REQUEST_EXT:
{
struct ksmbd_login_response_ext *resp = entry->response;
@@ -704,6 +722,57 @@ ksmbd_ipc_spnego_authen_request(const char *spnego_blob, int blob_len)
return resp;
}
+/**
+ * ksmbd_ipc_ntlm_authen_request() - have user space check an NTLM response
+ * @challenge: the server challenge (CIFS_CRYPTO_KEY_SIZE bytes)
+ * @parts: user name, domain name, workstation (UTF-16LE),
+ * NT response, LM response
+ * @lens: their lengths
+ *
+ * For a user whose login response had KSMBD_USER_FLAG_PASSTHROUGH.
+ *
+ * Return: the response (kvfree() it), NULL on error
+ */
+struct ksmbd_ntlm_authen_response *
+ksmbd_ipc_ntlm_authen_request(const char *challenge, const char *parts[5],
+ const unsigned short lens[5])
+{
+ struct ksmbd_ipc_msg *msg;
+ struct ksmbd_ntlm_authen_request *req;
+ struct ksmbd_ntlm_authen_response *resp;
+ size_t payload_sz = 0, off = 0;
+ int i;
+
+ for (i = 0; i < 5; i++)
+ payload_sz += lens[i];
+ if (payload_sz > KSMBD_IPC_MAX_PAYLOAD)
+ return NULL;
+
+ msg = ipc_msg_alloc(sizeof(struct ksmbd_ntlm_authen_request) +
+ payload_sz);
+ if (!msg)
+ return NULL;
+
+ msg->type = KSMBD_EVENT_NTLM_AUTHEN_REQUEST;
+ req = (struct ksmbd_ntlm_authen_request *)msg->payload;
+ req->handle = ksmbd_acquire_id(&ipc_ida);
+ memcpy(req->challenge, challenge, sizeof(req->challenge));
+ req->user_name_len = lens[0];
+ req->domain_name_len = lens[1];
+ req->workstation_len = lens[2];
+ req->nt_response_len = lens[3];
+ req->lm_response_len = lens[4];
+ for (i = 0; i < 5; i++) {
+ memcpy(req->payload + off, parts[i], lens[i]);
+ off += lens[i];
+ }
+
+ resp = ipc_msg_send_request(msg, req->handle);
+ ipc_msg_handle_free(req->handle);
+ ipc_msg_free(msg);
+ return resp;
+}
+
struct ksmbd_tree_connect_response *
ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
struct ksmbd_share_config *share,
diff --git a/fs/smb/server/transport_ipc.h b/fs/smb/server/transport_ipc.h
index e51850f14..f957a33ae 100644
--- a/fs/smb/server/transport_ipc.h
+++ b/fs/smb/server/transport_ipc.h
@@ -32,6 +32,9 @@ struct ksmbd_share_config_response *
ksmbd_ipc_share_config_request(const char *name);
struct ksmbd_spnego_authen_response *
ksmbd_ipc_spnego_authen_request(const char *spnego_blob, int blob_len);
+struct ksmbd_ntlm_authen_response *
+ksmbd_ipc_ntlm_authen_request(const char *challenge, const char *parts[5],
+ const unsigned short lens[5]);
int ksmbd_ipc_id_alloc(void);
void ksmbd_rpc_id_free(int handle);
struct ksmbd_rpc_command *ksmbd_rpc_open(struct ksmbd_session *sess, int handle);
[-- Attachment #44: uid_r-0043-ksmbd-put-the-workgroup-into-the-NTLMSSP-target-info.patch --]
[-- Type: text/x-patch, Size: 5644 bytes --]
From 6d2b53020107922610f03a572c52f4b924054411 Mon Sep 17 00:00:00 2001
From: Jan Koester <jan.koester@tuxist.de>
Date: Sat, 17 Oct 2026 09:29:07 +0200
Subject: [PATCH 43/43] ksmbd: put the workgroup into the NTLMSSP target info's
domain names
ksmbd_build_ntlmssp_challenge_blob() writes the netbios name into all
four AV pairs of the challenge's target info, the domain names
(MsvAvNbDomainName, MsvAvDnsDomainName) included. The client copies
them into its NTLMv2 response.
With NTLM pass-through, a domain controller verifies that response over
NETLOGON, and Samba (NTLMv2_RESPONSE_verify_workstation()) refuses it
with NT_STATUS_LOGON_FAILURE unless MsvAvNbComputerName is the secure
channel's computer account and MsvAvNbDomainName its workgroup. Every
pass-through login of a domain user then fails.
Put the workgroup into the two domain names. Size the challenge blob
from both names in one helper, ntlmssp_challenge_size(), and check that
the blob fits into the response buffer when it is built in place
without SPNEGO: the workgroup may be longer than the netbios name.
Assisted-by: Claude:claude-opus-5-5
---
fs/smb/server/auth.c | 41 ++++++++++++++++++++++++++++++++++-------
fs/smb/server/smb2pdu.c | 21 +++++++++++++++++++--
2 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/fs/smb/server/auth.c b/fs/smb/server/auth.c
index 3536032a1..a874692c5 100644
--- a/fs/smb/server/auth.c
+++ b/fs/smb/server/auth.c
@@ -374,10 +374,10 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
struct ksmbd_conn *conn)
{
struct target_info *tinfo;
- wchar_t *name;
+ wchar_t *name, *domain;
__u8 *target_name;
unsigned int flags, blob_off, blob_len, type, target_info_len = 0;
- int len, uni_len, conv_len;
+ int len, uni_len, dom_len, conv_len;
int cflags = conn->ntlmssp.client_flags;
memcpy(chgblob->Signature, NTLMSSP_SIGNATURE, 8);
@@ -424,6 +424,23 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
uni_len = UNICODE_LEN(conv_len);
+ len = strlen(ksmbd_work_group());
+ domain = kmalloc(2 + UNICODE_LEN(len), KSMBD_DEFAULT_GFP);
+ if (!domain) {
+ kfree(name);
+ return -ENOMEM;
+ }
+
+ conv_len = smb_strtoUTF16((__le16 *)domain, ksmbd_work_group(), len,
+ conn->local_nls);
+ if (conv_len < 0 || conv_len > len) {
+ kfree(domain);
+ kfree(name);
+ return -EINVAL;
+ }
+
+ dom_len = UNICODE_LEN(conv_len);
+
blob_off = sizeof(struct challenge_message);
blob_len = blob_off + uni_len;
@@ -444,14 +461,23 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
tinfo = (struct target_info *)(target_name + uni_len);
chgblob->TargetInfoArray.Length = 0;
- /* Add target info list for NetBIOS/DNS settings */
+ /*
+ * Add target info list for NetBIOS/DNS settings: the computer names
+ * are the netbios name, the domain names the workgroup. A domain
+ * controller checks the latter when it is asked to verify the
+ * NTLMv2 response of a domain user (NETLOGON pass-through).
+ */
for (type = NTLMSSP_AV_NB_COMPUTER_NAME;
type <= NTLMSSP_AV_DNS_DOMAIN_NAME; type++) {
+ bool is_domain = type == NTLMSSP_AV_NB_DOMAIN_NAME ||
+ type == NTLMSSP_AV_DNS_DOMAIN_NAME;
+ int av_len = is_domain ? dom_len : uni_len;
+
tinfo->Type = cpu_to_le16(type);
- tinfo->Length = cpu_to_le16(uni_len);
- memcpy(tinfo->Content, name, uni_len);
- tinfo = (struct target_info *)((char *)tinfo + 4 + uni_len);
- target_info_len += 4 + uni_len;
+ tinfo->Length = cpu_to_le16(av_len);
+ memcpy(tinfo->Content, is_domain ? domain : name, av_len);
+ tinfo = (struct target_info *)((char *)tinfo + 4 + av_len);
+ target_info_len += 4 + av_len;
}
/* Add terminator subblock */
@@ -462,6 +488,7 @@ ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
chgblob->TargetInfoArray.Length = cpu_to_le16(target_info_len);
chgblob->TargetInfoArray.MaximumLength = cpu_to_le16(target_info_len);
blob_len += target_info_len;
+ kfree(domain);
kfree(name);
ksmbd_debug(AUTH, "NTLMSSP SecurityBufferLength %d\n", blob_len);
return blob_len;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index f99f2fd92..e6b2b6a5d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1586,6 +1586,20 @@ static __le32 deassemble_neg_contexts(struct ksmbd_conn *conn,
return status;
}
+/*
+ * ntlmssp_challenge_size() - room for the challenge blob: the netbios name as
+ * target name and the two computer names, the workgroup as the two domain
+ * names (UTF-16, each with its 4-byte AV pair header), the terminator
+ */
+static size_t ntlmssp_challenge_size(void)
+{
+ size_t nb = strlen(ksmbd_netbios_name()) * 2;
+ size_t wg = strlen(ksmbd_work_group()) * 2;
+
+ return sizeof(struct challenge_message) + nb + 2 * (4 + nb) +
+ 2 * (4 + wg) + 4;
+}
+
/**
* smb2_handle_negotiate() - handler for smb2 negotiate command
* @work: smb work containing smb request buffer
@@ -1852,6 +1866,10 @@ static int ntlm_negotiate(struct ksmbd_work *work,
memset(chgblob, 0, sizeof(struct challenge_message));
if (!work->conn->use_spnego) {
+ if ((char *)rsp->Buffer + ntlmssp_challenge_size() >
+ (char *)work->response_buf + work->response_sz)
+ return -EINVAL;
+
sz = ksmbd_build_ntlmssp_challenge_blob(chgblob, work->conn);
if (sz < 0)
return -ENOMEM;
@@ -1860,8 +1878,7 @@ static int ntlm_negotiate(struct ksmbd_work *work,
return 0;
}
- sz = sizeof(struct challenge_message);
- sz += (strlen(ksmbd_netbios_name()) * 2 + 1 + 4) * 6;
+ sz = ntlmssp_challenge_size();
neg_blob = kzalloc(sz, KSMBD_DEFAULT_GFP);
if (!neg_blob)
^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-10-19 2:10 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-18 18:01 [RFC PATCH 00/41] UUID and GUID user and group ids (*_r system calls) Jan Koester
2026-10-18 18:01 ` [RFC PATCH 01/41] uuid: move uuid_t and guid_t into <linux/uuid_types.h> Jan Koester
2026-10-18 18:01 ` [RFC PATCH 02/41] uapi: add <linux/uidgid_r.h> for 128 bit capable user and group ids Jan Koester
2026-10-18 18:01 ` [RFC PATCH 03/41] uidgid: add uid_is_legacy() and gid_is_legacy() Jan Koester
2026-10-18 18:01 ` [RFC PATCH 04/41] net: sock: read sk_uid through a seqcount Jan Koester
2026-10-18 18:01 ` [RFC PATCH 05/41] af_unix: keep sender ids in skb->cb only when they are classic ids Jan Koester
2026-10-18 18:01 ` [RFC PATCH 06/41] netlink: keep classic sender ids in skb->cb Jan Koester
2026-10-18 18:01 ` [RFC PATCH 07/41] Bluetooth: " Jan Koester
2026-10-18 18:01 ` [RFC PATCH 08/41] smb: client: use __kuid_val() for the unix SIDs of the caller Jan Koester
2026-10-18 18:01 ` [RFC PATCH 09/41] uidgid: let kuid_t and kgid_t hold a UUID or a GUID Jan Koester
2026-10-18 19:47 ` Andy Lutomirski
2026-10-18 21:02 ` Eric Biggers
2026-10-19 2:10 ` Jan Koester
2026-10-18 18:01 ` [RFC PATCH 10/41] uidgid: convert between kernel ids and the ids of the *_r system calls Jan Koester
2026-10-18 18:01 ` [RFC PATCH 11/41] fscrypt: show the whole uid in the description of user key claims Jan Koester
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.