git: 2fc95fe26e72 - main - linuxulator: Add linux_extattr_get_vp() for atomic getxattr
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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