git: 2c905456312b - main - linuxulator: Fix O_PATH file descriptors errno for f*xattr(2)

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

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

commit 2c905456312b2e5986afe3402a9c87d49eb9cf86
Author:     YAO, Xin <mr.yaoxin@outlook.com>
AuthorDate: 2026-05-07 06:39:16 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-07-01 08:38:05 +0000

    linuxulator: Fix O_PATH file descriptors errno for f*xattr(2)
    
    LTP open13 expects these operations to fail with EBADF, matching
    Linux behavior, but FreeBSD currently returns EOPNOTSUPP
    for fgetxattr() on an O_PATH fd
    
    Look up Linux fd-based xattr descriptors with getvnode()
    and route the operations through shared kern_extattr_*_fp()
    helpers so the O_PATH check and the extattr operation use the
    same referenced file.
    
    Apply the same EBADF handling to fsetxattr(), fremovexattr(), and
    flistxattr() so the xattr paths stay consistent.
    
    Signed-off-by:  YAO, Xin <mr.yaoxin@outlook.com>
    PR:             295537
    Reviewed by:    kib
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2263
---
 sys/compat/linux/linux_xattr.c | 81 ++++++++++++++++++++++++++++++++++++------
 sys/kern/vfs_extattr.c         | 47 ++++++++++++++++++++----
 sys/sys/syscallsubr.h          | 10 ++++++
 3 files changed, 121 insertions(+), 17 deletions(-)

diff --git a/sys/compat/linux/linux_xattr.c b/sys/compat/linux/linux_xattr.c
index 2eec33f8ef93..70e4c3a1d3db 100644
--- a/sys/compat/linux/linux_xattr.c
+++ b/sys/compat/linux/linux_xattr.c
@@ -26,8 +26,10 @@
  */
 
 #include <sys/param.h>
+#include <sys/capsicum.h>
 #include <sys/extattr.h>
 #include <sys/fcntl.h>
+#include <sys/file.h>
 #include <sys/namei.h>
 #include <sys/proc.h>
 #include <sys/syscallsubr.h>
@@ -132,12 +134,21 @@ listxattr(struct thread *td, struct listxattr_args *args)
 {
 	char attrname[LINUX_XATTR_NAME_MAX + 1];
 	char *data, *prefix, *key;
+	cap_rights_t rights;
+	struct file *fp = NULL;
 	struct uio auio;
 	struct iovec aiov;
 	unsigned char keylen;
 	size_t sz, cnt, rs, prefixlen, pairlen;
 	int attrnamespace, error;
 
+	if (args->path == NULL) {
+		error = getvnode(td, args->fd,
+		    cap_rights_init_one(&rights, CAP_EXTATTR_LIST), &fp);
+		if (error != 0)
+			return (error);
+	}
+
 	if (args->size != 0)
 		sz = min(LINUX_XATTR_LIST_MAX, args->size);
 	else
@@ -162,7 +173,7 @@ listxattr(struct thread *td, struct listxattr_args *args)
 			error = kern_extattr_list_path(td, args->path,
 			    attrnamespace, &auio, args->follow, UIO_USERSPACE);
 		else
-			error = kern_extattr_list_fd(td, args->fd,
+			error = kern_extattr_list_fp(td, fp,
 			    attrnamespace, &auio);
 		rs = sz - auio.uio_resid;
 		if (error == EPERM)
@@ -204,6 +215,8 @@ listxattr(struct thread *td, struct listxattr_args *args)
 	if (error == 0)
 		td->td_retval[0] = cnt;
 	free(data, M_LINUX);
+	if (fp != NULL)
+		fdrop(fp, td);
 	return (error_to_xattrerror(attrnamespace, error));
 }
 
@@ -253,18 +266,33 @@ static int
 removexattr(struct thread *td, struct removexattr_args *args)
 {
 	char attrname[LINUX_XATTR_NAME_MAX + 1];
+	struct file *fp = NULL;
+	cap_rights_t rights;
 	int attrnamespace, error;
 
+	if (args->path == NULL) {
+		error = getvnode(td, args->fd,
+		    cap_rights_init_one(&rights, CAP_EXTATTR_DELETE), &fp);
+		if (error != 0)
+			return (error);
+	}
+
 	error = xattr_to_extattr(args->name, &attrnamespace, attrname);
 	if (error != 0)
-		return (error);
+		goto out_err;
 	if (args->path != NULL)
 		error = kern_extattr_delete_path(td, args->path, attrnamespace,
 		    attrname, args->follow, UIO_USERSPACE);
 	else
-		error = kern_extattr_delete_fd(td, args->fd, attrnamespace,
+		error = kern_extattr_delete_fp(td, fp, attrnamespace,
 		    attrname);
+	if (fp != NULL)
+		fdrop(fp, td);
 	return (error_to_xattrerror(attrnamespace, error));
+out_err:
+	if (fp != NULL)
+		fdrop(fp, td);
+	return (error);
 }
 
 int
@@ -310,17 +338,30 @@ static int
 getxattr(struct thread *td, struct getxattr_args *args)
 {
 	char attrname[LINUX_XATTR_NAME_MAX + 1];
+	struct file *fp = NULL;
+	cap_rights_t rights;
 	int attrnamespace, error;
 
+	if (args->path == NULL) {
+		error = getvnode(td, args->fd,
+		    cap_rights_init_one(&rights, CAP_EXTATTR_GET), &fp);
+		if (error != 0)
+			return (error);
+	}
+
 	error = xattr_to_extattr(args->name, &attrnamespace, attrname);
 	if (error != 0)
-		return (error);
+		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_fd(td, args->fd, attrnamespace,
+		error = kern_extattr_get_fp(td, fp, attrnamespace,
 		    attrname, args->value, args->size);
+
+out_err:
+	if (fp != NULL)
+		fdrop(fp, td);
 	return (error == EPERM ? ENOATTR : error);
 }
 
@@ -373,14 +414,28 @@ static int
 setxattr(struct thread *td, struct setxattr_args *args)
 {
 	char attrname[LINUX_XATTR_NAME_MAX + 1];
+	struct file *fp = NULL;
+	cap_rights_t rights;
 	int attrnamespace, error;
 
+	if (args->path == NULL) {
+		if ((args->flags & LINUX_XATTR_FLAGS) != 0)
+			cap_rights_init(&rights, CAP_EXTATTR_GET, CAP_EXTATTR_SET);
+		else
+			cap_rights_init_one(&rights, CAP_EXTATTR_SET);
+		error = getvnode(td, args->fd, &rights, &fp);
+		if (error != 0)
+			return (error);
+	}
+
 	if ((args->flags & ~(LINUX_XATTR_FLAGS)) != 0 ||
-	    args->flags == (LINUX_XATTR_FLAGS))
-		return (EINVAL);
+	    args->flags == (LINUX_XATTR_FLAGS)) {
+		error = EINVAL;
+		goto out_err;
+	}
 	error = xattr_to_extattr(args->name, &attrnamespace, attrname);
 	if (error != 0)
-		return (error);
+		goto out_err;
 
 	if ((args->flags & (LINUX_XATTR_FLAGS)) != 0 ) {
 		if (args->path != NULL)
@@ -388,7 +443,7 @@ setxattr(struct thread *td, struct setxattr_args *args)
 			    attrnamespace, attrname, NULL, args->size,
 			    args->follow, UIO_USERSPACE);
 		else
-			error = kern_extattr_get_fd(td, args->fd,
+			error = kern_extattr_get_fp(td, fp,
 			    attrnamespace, attrname, NULL, args->size);
 		if ((args->flags & LINUX_XATTR_CREATE) != 0) {
 			if (error == 0)
@@ -404,11 +459,17 @@ setxattr(struct thread *td, struct setxattr_args *args)
 		    attrname, args->value, args->size, args->follow,
 		    UIO_USERSPACE);
 	else
-		error = kern_extattr_set_fd(td, args->fd, attrnamespace,
+		error = kern_extattr_set_fp(td, fp, attrnamespace,
 		    attrname, args->value, args->size);
 out:
+	if (fp != NULL)
+		fdrop(fp, td);
 	td->td_retval[0] = 0;
 	return (error_to_xattrerror(attrnamespace, error));
+out_err:
+	if (fp != NULL)
+		fdrop(fp, td);
+	return (error);
 }
 
 int
diff --git a/sys/kern/vfs_extattr.c b/sys/kern/vfs_extattr.c
index 1fe7494f3998..01bfe343a1e4 100644
--- a/sys/kern/vfs_extattr.c
+++ b/sys/kern/vfs_extattr.c
@@ -240,6 +240,15 @@ sys_extattr_set_fd(struct thread *td, struct extattr_set_fd_args *uap)
 	    attrname, uap->data, uap->nbytes));
 }
 
+int
+kern_extattr_set_fp(struct thread *td, struct file *fp, int attrnamespace,
+    const char *attrname, void *data, size_t nbytes)
+{
+
+	return (extattr_set_vp(fp->f_vnode, attrnamespace, attrname, data,
+	    nbytes, td));
+}
+
 int
 kern_extattr_set_fd(struct thread *td, int fd, int attrnamespace,
     const char *attrname, void *data, size_t nbytes)
@@ -257,8 +266,8 @@ kern_extattr_set_fd(struct thread *td, int fd, int attrnamespace,
 	if (error)
 		return (error);
 
-	error = extattr_set_vp(fp->f_vnode, attrnamespace,
-	    attrname, data, nbytes, td);
+	error = kern_extattr_set_fp(td, fp, attrnamespace, attrname, data,
+	    nbytes);
 	fdrop(fp, td);
 
 	return (error);
@@ -428,6 +437,15 @@ sys_extattr_get_fd(struct thread *td, struct extattr_get_fd_args *uap)
 	    attrname, uap->data, uap->nbytes));
 }
 
+int
+kern_extattr_get_fp(struct thread *td, struct file *fp, int attrnamespace,
+    const char *attrname, void *data, size_t nbytes)
+{
+
+	return (extattr_get_vp(fp->f_vnode, attrnamespace, attrname, data,
+	    nbytes, td));
+}
+
 int
 kern_extattr_get_fd(struct thread *td, int fd, int attrnamespace,
     const char *attrname, void *data, size_t nbytes)
@@ -445,8 +463,8 @@ kern_extattr_get_fd(struct thread *td, int fd, int attrnamespace,
 	if (error)
 		return (error);
 
-	error = extattr_get_vp(fp->f_vnode, attrnamespace,
-	    attrname, data, nbytes, td);
+	error = kern_extattr_get_fp(td, fp, attrnamespace, attrname, data,
+	    nbytes);
 
 	fdrop(fp, td);
 	return (error);
@@ -584,6 +602,14 @@ sys_extattr_delete_fd(struct thread *td, struct extattr_delete_fd_args *uap)
 	    attrname));
 }
 
+int
+kern_extattr_delete_fp(struct thread *td, struct file *fp, int attrnamespace,
+    const char *attrname)
+{
+
+	return (extattr_delete_vp(fp->f_vnode, attrnamespace, attrname, td));
+}
+
 int
 kern_extattr_delete_fd(struct thread *td, int fd, int attrnamespace,
     const char *attrname)
@@ -601,8 +627,7 @@ kern_extattr_delete_fd(struct thread *td, int fd, int attrnamespace,
 	if (error)
 		return (error);
 
-	error = extattr_delete_vp(fp->f_vnode, attrnamespace,
-	    attrname, td);
+	error = kern_extattr_delete_fp(td, fp, attrnamespace, attrname);
 	fdrop(fp, td);
 	return (error);
 }
@@ -753,6 +778,14 @@ sys_extattr_list_fd(struct thread *td, struct extattr_list_fd_args *uap)
 	    auiop));
 }
 
+int
+kern_extattr_list_fp(struct thread *td, struct file *fp, int attrnamespace,
+    struct uio *auiop)
+{
+
+	return (extattr_list_vp(fp->f_vnode, attrnamespace, auiop, td));
+}
+
 int
 kern_extattr_list_fd(struct thread *td, int fd, int attrnamespace,
     struct uio *auiop)
@@ -768,7 +801,7 @@ kern_extattr_list_fd(struct thread *td, int fd, int attrnamespace,
 	if (error)
 		return (error);
 
-	error = extattr_list_vp(fp->f_vnode, attrnamespace, auiop, td);
+	error = kern_extattr_list_fp(td, fp, attrnamespace, auiop);
 
 	fdrop(fp, td);
 	return (error);
diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h
index 0eb471cc9dde..c6bfe89dcf35 100644
--- a/sys/sys/syscallsubr.h
+++ b/sys/sys/syscallsubr.h
@@ -139,21 +139,31 @@ int	kern_execve(struct thread *td, struct image_args *args,
 void	kern_exit(struct thread *, int, int);
 int	kern_extattr_delete_fd(struct thread *td, int fd, int attrnamespace,
 	    const char *attrname);
+int	kern_extattr_delete_fp(struct thread *td, struct file *fp,
+	    int attrnamespace, const char *attrname);
 int	kern_extattr_delete_path(struct thread *td, const char *path,
 	    int attrnamespace, const char *attrname, int follow,
 	    enum uio_seg pathseg);
 int	kern_extattr_get_fd(struct thread *td, int fd, int attrnamespace,
 	    const char *attrname, void *data, size_t nbytes);
+int	kern_extattr_get_fp(struct thread *td, struct file *fp,
+	    int attrnamespace, const char *attrname, void *data,
+	    size_t nbytes);
 int	kern_extattr_get_path(struct thread *td, const char *path,
 	    int attrnamespace, const char *attrname, void *data,
 	    size_t nbytes, int follow, enum uio_seg pathseg);
 int	kern_extattr_list_fd(struct thread *td, int fd, int attrnamespace,
 	    struct uio *auiop);
+int	kern_extattr_list_fp(struct thread *td, struct file *fp,
+	    int attrnamespace, struct uio *auiop);
 int	kern_extattr_list_path(struct thread *td, const char *path,
 	    int attrnamespace, struct uio *auiop, int follow,
 	    enum uio_seg pathseg);
 int	kern_extattr_set_fd(struct thread *td, int fd, int attrnamespace,
 	    const char *attrname, void *data, size_t nbytes);
+int	kern_extattr_set_fp(struct thread *td, struct file *fp,
+	    int attrnamespace, const char *attrname, void *data,
+	    size_t nbytes);
 int	kern_extattr_set_path(struct thread *td, const char *path,
 	    int attrnamespace, const char *attrname, void *data,
 	    size_t nbytes, int follow, enum uio_seg pathseg);