[RFC PATCH v2 40/41] nfsd, nfs: carry UUID and GUID owners as their text form

sashiko-bot at kernel.org sashiko-bot at kernel.org
Thu Oct 22 03:18:09 UTC 2026


Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] nfsd: unmapped -EOVERFLOW in nfserrno() on UUID/GUID setattr and ACLs
- [High] nfs: silent overflowuid munging on UUID chown when idmap user_ns is non-init
- [Medium] nfs: rejected UUID/GUID chown clears NFS_CAP_UIDGID_NOMAP for entire mount
--

commit 651e121e99cc1fc974fd455542b8ff4a2ecaa7ac
Author: Jan Koester <jan.koester at tuxist.de>

nfsd, nfs: carry UUID and GUID owners as their text form

This commit encodes and decodes non-legacy UUID and GUID user and group
IDs in their text form for NFSv4 owners and ACL principals on both the
client and server. It also sets SB_I_UID_R on NFSv4 client superblocks
so userspace can set UUID and GUID owners via chown_r().

> diff --git a/fs/nfs/nfs4idmap.c b/fs/nfs/nfs4idmap.c
> index bc397110d977f..0ca50bf8f3fc6 100644
> --- a/fs/nfs/nfs4idmap.c
> +++ b/fs/nfs/nfs4idmap.c

[ ... ]

> @@ -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);

[Severity: High]
What happens here when an NFSv4 superblock has SB_I_UID_R set by
nfs_fill_super(), but idmap_userns(idmap) is not init_user_ns?

Because nfs4_fs_type sets FS_USERNS_DELEGATABLE in fs/nfs/fs_context.c,
an nfs4 context created via fsopen() in a non-initial user namespace
sets idmap->user_ns to that namespace in nfs_idmap_new() (and
nfs_match_client() in fs/nfs/client.c can also share that cl_idmap with
a subsequent mount in init_user_ns).

When a process in init_user_ns calls chown_r() with a UUID or GUID on
that mount, notify_change() in fs/attr.c checks vfsuid_has_fsmapping()
(where from_vfsuid() in fs/mnt_idmapping.c returns AS_KUIDT(vfsuid)
immediately for nop_mnt_idmap without checking fs_userns) and
sb_can_own_kuid(), then calls nfs_setattr() -> nfs4_proc_setattr() ->
encode_attrs().

In nfs_map_uid_to_name() and nfs_map_gid_to_group(), kuid_has_mapping()
and kgid_has_mapping() return false for non-initial user namespaces, so
execution falls through to from_kuid_munged() and from_kgid_munged(),
silently sending overflowuid or overflowgid in the SETATTR RPC instead
of the requested UUID/GUID.

If overflowuid or overflowgid is not mapped in idmap->user_ns,
decode_attr_owner() and decode_attr_group() in fs/nfs/nfs4xdr.c also
fail to map the post-op GETATTR owner, so nfs_setattr_update_inode() in
fs/nfs/inode.c caches the requested UUID/GUID in inode->i_uid or
inode->i_gid while the file on the server is owned by overflowuid or
overflowgid:

fs/nfs/inode.c:nfs_setattr_update_inode() {
    ...
		if ((attr->ia_valid & ATTR_UID) != 0)
			inode->i_uid = attr->ia_uid;
		if ((attr->ia_valid & ATTR_GID) != 0)
			inode->i_gid = attr->ia_gid;
    ...
}

Should nfs_fill_super() only set SB_I_UID_R when idmap_userns(idmap) is
init_user_ns, or should nfs_setattr() and nfs_map_uid_to_name() reject
unmapped non-legacy IDs rather than munging them to overflowuid?

> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index cb19f1540d984..2d292e44186e7 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;

[Severity: Medium]
Can setting SB_I_UID_R unconditionally on all NFSv4 mounts cause a
rejected UUID or GUID chown_r() to disable numeric UID/GID support for
the entire mount?

When a user calls chown_r() with a UUID or GUID on a sec=sys mount where
NFS_CAP_UIDGID_NOMAP is set, notify_change() delegates permission checks
to the server and calls nfs_setattr() -> nfs4_proc_setattr() ->
nfs4_do_setattr().

During XDR encoding, nfs_map_uid_to_name() and nfs_map_gid_to_group()
encode the UUID or GUID as its text string even when
NFS_CAP_UIDGID_NOMAP is set. If the server does not support UUID/GUID
owners and returns NFS4ERR_BADOWNER (which nfsd4_decode_fattr4() in
fs/nfsd/nfs4xdr.c returns during XDR decode before nfsd4_setattr()
checks file ownership or CAP_CHOWN), nfs4_do_setattr() passes
-NFS4ERR_BADOWNER to nfs4_do_handle_exception() in fs/nfs/nfs4proc.c:

		case -NFS4ERR_BADOWNER:
			/* The following works around a Linux server bug! */
		case -NFS4ERR_BADNAME:
			if (server->caps & NFS_CAP_UIDGID_NOMAP) {
				server->caps &= ~NFS_CAP_UIDGID_NOMAP;
				exception->retry = 1;

This clears NFS_CAP_UIDGID_NOMAP permanently on server->caps for the
mount and retries with the identical UUID/GUID string, forcing all
subsequent classic UID/GID setattr calls through idmapd (similar to the
regression fixed for ACLs in commit f8849e206ef52b).

Should nfs4_do_setattr() avoid clearing NFS_CAP_UIDGID_NOMAP when the
setattr request carried a non-legacy kuid_t or kgid_t?

> diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c
> index e9faf8b78f74a..8c3e908b323ed 100644
> --- a/fs/nfsd/nfs4idmap.c
> +++ b/fs/nfsd/nfs4idmap.c

[ ... ]

> @@ -672,6 +699,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;
> @@ -709,6 +746,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;

[Severity: High]
Could accepting UUID and GUID strings here trigger a kernel warning in
nfserrno() when the exported filesystem does not support SB_I_UID_R?

nfsd_map_name_to_uid() and nfsd_map_name_to_gid() decode 36-byte UUID
and 38-byte GUID strings into non-legacy kuid_t and kgid_t values in
nfsd4_decode_fattr4(), nfsd4_decode_nfsace4(), and
nfsd4_decode_posixace4() before the target export's superblock is
checked.

When nfsd4_setattr(), nfsd4_create(), or do_open_lookup() (via
nfsd_create_setattr()) then calls nfsd_setattr() in fs/nfsd/vfs.c on an
exported filesystem without SB_I_UID_R (or on ext4 when
ext4_make_room_for_id_r() cannot expand an existing inode),
notify_change() in fs/attr.c:

	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;

and set_posix_acl() in fs/posix_acl.c:

		if (posix_acl_has_id_r(acl) &&
		    !(inode->i_sb->s_iflags & SB_I_UID_R))
			return -EOVERFLOW;

return -EOVERFLOW.

nfsd_setattr() and nfsd4_setattr() pass that error to nfserrno() in
fs/nfsd/vfs.c:

	for (i = 0; i < ARRAY_SIZE(nfs_errtbl); i++) {
		if (nfs_errtbl[i].syserr == errno)
			return nfs_errtbl[i].nfserr;
	}
	WARN_ONCE(1, "nfsd: non-standard errno: %d\n", errno);
	return nfserr_io;

Because nfs_errtbl[] has no entry for EOVERFLOW, this triggers
WARN_ONCE() and returns nfserr_io instead of nfserr_badowner (and for
OPEN(CREATE) or CREATE with initial UUID/GUID attributes, leaves the
newly created file or directory on disk after nfsd_create_setattr()
fails).

Should nfsd check whether the export's superblock supports SB_I_UID_R
(and map EOVERFLOW to nfserr_badowner in nfsd_setattr() / nfserrno())?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1792552161.git.jan.koester@tuxist.de?part=40



More information about the Linux-security-module-archive mailing list