git: 2fc95fe26e72 - main - linuxulator: Add linux_extattr_get_vp() for atomic getxattr

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Wed, 01 Jul 2026 08:44:08 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=2fc95fe26e725439209217f53dd76437a52be76b

commit 2fc95fe26e725439209217f53dd76437a52be76b
Author:     YAO, Xin <mr.yaoxin@outlook.com>
AuthorDate: 2026-06-29 01:55:25 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-07-01 08:38:32 +0000

    linuxulator: Add linux_extattr_get_vp() for atomic getxattr
    
    Move the atomic size-probe-and-read logic into a new
    linux_extattr_get_vp() function in linux_xattr.c instead of
    modifying the generic extattr_get_vp() in vfs_extattr.c.
    This keeps Linux-specific getxattr semantics (ERANGE on
    too-small buffer, EOPNOTSUPP to ENOATTR mapping)
    self-contained within the linuxulator.
    
    The function probes the attribute size and reads the data
    under a single vnode lock, preventing a TOCTOU race between
    the size probe and data read.
    
    Signed-off-by:  YAO, Xin <mr.yaoxin@outlook.com>
    Reviewed by:    kib
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2263
---
 sys/compat/linux/linux_xattr.c | 105 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 94 insertions(+), 11 deletions(-)

diff --git a/sys/compat/linux/linux_xattr.c b/sys/compat/linux/linux_xattr.c
index 70e4c3a1d3db..296b70ff409a 100644
--- a/sys/compat/linux/linux_xattr.c
+++ b/sys/compat/linux/linux_xattr.c
@@ -33,6 +33,9 @@
 #include <sys/namei.h>
 #include <sys/proc.h>
 #include <sys/syscallsubr.h>
+#include <sys/vnode.h>
+
+#include <security/mac/mac_framework.h>
 
 #ifdef COMPAT_LINUX32
 #include <machine/../linux32/linux.h>
@@ -334,11 +337,83 @@ linux_fremovexattr(struct thread *td, struct linux_fremovexattr_args *args)
 	return (removexattr(td, &eargs));
 }
 
+/*-
+ * Linux-specific atomic extended attribute get on a vnode.
+ *
+ * Probes the attribute size and reads the data under a single vnode lock,
+ * preventing a TOCTOU race and returning ERANGE when the buffer is too
+ * small (matching Linux getxattr(2) semantics).
+ */
+static int
+linux_extattr_get_vp(struct vnode *vp, int attrnamespace, const char *attrname,
+    void *data, size_t nbytes, struct thread *td)
+{
+	struct uio auio;
+	struct iovec aiov;
+	size_t size;
+	int error;
+
+	if (nbytes > IOSIZE_MAX)
+		return (EINVAL);
+
+	vn_lock(vp, LK_SHARED | LK_RETRY);
+
+#ifdef MAC
+	error = mac_vnode_check_getextattr(td->td_ucred, vp, attrnamespace,
+	    attrname);
+	if (error != 0)
+		goto done;
+#endif
+
+	/*
+	 * Probe the attribute size first under the vnode lock;
+	 */
+	error = VOP_GETEXTATTR(vp, attrnamespace, attrname, NULL,
+	    &size, td->td_ucred, td);
+	if (error != 0)
+		goto done;
+
+	/*
+	 * The caller only wants the size, so we are done after this.
+	 */
+	if (data == NULL || nbytes == 0) {
+		td->td_retval[0] = size;
+		goto done;
+	}
+	/*
+	 * If the buffer is too small, return ERANGE
+	 * so the caller can retry (Linux getxattr semantics).
+	 */
+	if (size > nbytes) {
+		error = ERANGE;
+		goto done;
+	}
+	/* Buffer is large enough; read the value. */
+	aiov.iov_base = data;
+	aiov.iov_len = nbytes;
+	auio.uio_iov = &aiov;
+	auio.uio_iovcnt = 1;
+	auio.uio_offset = 0;
+	auio.uio_resid = nbytes;
+	auio.uio_rw = UIO_READ;
+	auio.uio_segflg = UIO_USERSPACE;
+	auio.uio_td = td;
+	error = VOP_GETEXTATTR(vp, attrnamespace, attrname, &auio, NULL,
+		td->td_ucred, td);
+	if (error == 0)
+		td->td_retval[0] = nbytes - auio.uio_resid;
+done:
+	VOP_UNLOCK(vp);
+	return (error);
+}
+
 static int
 getxattr(struct thread *td, struct getxattr_args *args)
 {
 	char attrname[LINUX_XATTR_NAME_MAX + 1];
 	struct file *fp = NULL;
+	struct nameidata nd;
+	struct vnode *vp;
 	cap_rights_t rights;
 	int attrnamespace, error;
 
@@ -347,22 +422,30 @@ getxattr(struct thread *td, struct getxattr_args *args)
 		    cap_rights_init_one(&rights, CAP_EXTATTR_GET), &fp);
 		if (error != 0)
 			return (error);
+		vp = fp->f_vnode;
+	} else {
+		NDINIT_ATRIGHTS(&nd, LOOKUP, args->follow, UIO_USERSPACE,
+		    args->path, AT_FDCWD,
+		    cap_rights_init_one(&rights, CAP_EXTATTR_GET));
+		error = namei(&nd);
+		if (error != 0)
+			return (error);
+		NDFREE_PNBUF(&nd);
+		vp = nd.ni_vp;
 	}
 
 	error = xattr_to_extattr(args->name, &attrnamespace, attrname);
-	if (error != 0)
-		goto out_err;
-	if (args->path != NULL)
-		error = kern_extattr_get_path(td, args->path, attrnamespace,
-		    attrname, args->value, args->size, args->follow, UIO_USERSPACE);
-	else
-		error = kern_extattr_get_fp(td, fp, attrnamespace,
-		    attrname, args->value, args->size);
+	if (error == 0) {
+		error = linux_extattr_get_vp(vp, attrnamespace, attrname,
+			args->value, args->size, td);
+	}
 
-out_err:
-	if (fp != NULL)
+	if (fp != NULL) {
 		fdrop(fp, td);
-	return (error == EPERM ? ENOATTR : error);
+	} else {
+		vrele(nd.ni_vp);
+	}
+	return (error == EPERM || error == EOPNOTSUPP ? ENOATTR : error);
 }
 
 int