[PATCH 2/3] xattrat: accept empty O_PATH file descriptors

From: Andreas Gruenbacher

Date: Sun Jun 07 2026 - 12:42:19 EST


Right now, the setxattrat(), getxattrat(), listxattrat(), and removexattrat()
system calls fail with -EBADF when dfd is an O_PATH file descriptor, pathname
is an empty string or NULL, and the AT_EMPTY_PATH flag in at_flags is set.
(Regular non-O_PATH file descriptors are accepted.) This is inconsistent with
the behavior of system calls like fstatat() and fchmodat() which do accept
those file descriptors.

The same operations can also be carried out indirectly by using
"/proc/self/fd/<dfd>" as the pathname, but that is only more cumbersome, and
for no good reason. Fix that by changing the xattrat() system calls to accept
these file descriptors directly.

We stick with the existing practice of leaving the behavior of the fsetxattr(),
fgetxattr(), flistxattr(), and fremovexattr() system calls unchanged: those
will still reject O_PATH file descriptors.

Signed-off-by: Andreas Gruenbacher <agruenba@xxxxxxxxxx>
---
fs/xattr.c | 56 +++++++++++++++++++++++++++++++-----------------------
1 file changed, 32 insertions(+), 24 deletions(-)

diff --git a/fs/xattr.c b/fs/xattr.c
index 09ecbaaa1660..34fa5a6139c4 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -692,7 +692,8 @@ int filename_setxattr(int dfd, struct filename *filename,

static int path_setxattrat(int dfd, const char __user *pathname,
unsigned int at_flags, const char __user *name,
- const void __user *value, size_t size, int flags)
+ const void __user *value, size_t size, int flags,
+ fmode_t mask)
{
struct xattr_name kname;
struct kernel_xattr_ctx ctx = {
@@ -717,7 +718,7 @@ static int path_setxattrat(int dfd, const char __user *pathname,

CLASS(filename_maybe_null, filename)(pathname, at_flags);
if (!filename && dfd >= 0) {
- CLASS(fd, f)(dfd);
+ CLASS(fd_except, f)(dfd, mask);
if (fd_empty(f))
error = -EBADF;
else
@@ -750,14 +751,15 @@ SYSCALL_DEFINE6(setxattrat, int, dfd, const char __user *, pathname, unsigned in

return path_setxattrat(dfd, pathname, at_flags, name,
u64_to_user_ptr(args.value), args.size,
- args.flags);
+ args.flags, 0);
}

SYSCALL_DEFINE5(setxattr, const char __user *, pathname,
const char __user *, name, const void __user *, value,
size_t, size, int, flags)
{
- return path_setxattrat(AT_FDCWD, pathname, 0, name, value, size, flags);
+ return path_setxattrat(AT_FDCWD, pathname, 0, name, value, size, flags,
+ FMODE_PATH);
}

SYSCALL_DEFINE5(lsetxattr, const char __user *, pathname,
@@ -765,14 +767,14 @@ SYSCALL_DEFINE5(lsetxattr, const char __user *, pathname,
size_t, size, int, flags)
{
return path_setxattrat(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, name,
- value, size, flags);
+ value, size, flags, FMODE_PATH);
}

SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
const void __user *,value, size_t, size, int, flags)
{
return path_setxattrat(fd, NULL, AT_EMPTY_PATH, name,
- value, size, flags);
+ value, size, flags, FMODE_PATH);
}

/*
@@ -837,7 +839,7 @@ ssize_t filename_getxattr(int dfd, struct filename *filename,

static ssize_t path_getxattrat(int dfd, const char __user *pathname,
unsigned int at_flags, const char __user *name,
- void __user *value, size_t size)
+ void __user *value, size_t size, fmode_t mask)
{
struct xattr_name kname;
struct kernel_xattr_ctx ctx = {
@@ -857,7 +859,7 @@ static ssize_t path_getxattrat(int dfd, const char __user *pathname,

CLASS(filename_maybe_null, filename)(pathname, at_flags);
if (!filename && dfd >= 0) {
- CLASS(fd, f)(dfd);
+ CLASS(fd_except, f)(dfd, mask);
if (fd_empty(f))
return -EBADF;
return file_getxattr(fd_file(f), &ctx);
@@ -891,26 +893,28 @@ SYSCALL_DEFINE6(getxattrat, int, dfd, const char __user *, pathname, unsigned in
return -EINVAL;

return path_getxattrat(dfd, pathname, at_flags, name,
- u64_to_user_ptr(args.value), args.size);
+ u64_to_user_ptr(args.value), args.size, 0);
}

SYSCALL_DEFINE4(getxattr, const char __user *, pathname,
const char __user *, name, void __user *, value, size_t, size)
{
- return path_getxattrat(AT_FDCWD, pathname, 0, name, value, size);
+ return path_getxattrat(AT_FDCWD, pathname, 0, name, value, size,
+ FMODE_PATH);
}

SYSCALL_DEFINE4(lgetxattr, const char __user *, pathname,
const char __user *, name, void __user *, value, size_t, size)
{
return path_getxattrat(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, name,
- value, size);
+ value, size, FMODE_PATH);
}

SYSCALL_DEFINE4(fgetxattr, int, fd, const char __user *, name,
void __user *, value, size_t, size)
{
- return path_getxattrat(fd, NULL, AT_EMPTY_PATH, name, value, size);
+ return path_getxattrat(fd, NULL, AT_EMPTY_PATH, name, value, size,
+ FMODE_PATH);
}

/*
@@ -974,7 +978,7 @@ ssize_t filename_listxattr(int dfd, struct filename *filename,

static ssize_t path_listxattrat(int dfd, const char __user *pathname,
unsigned int at_flags, char __user *list,
- size_t size)
+ size_t size, fmode_t mask)
{
int lookup_flags;

@@ -983,7 +987,7 @@ static ssize_t path_listxattrat(int dfd, const char __user *pathname,

CLASS(filename_maybe_null, filename)(pathname, at_flags);
if (!filename) {
- CLASS(fd, f)(dfd);
+ CLASS(fd_except, f)(dfd, mask);
if (fd_empty(f))
return -EBADF;
return file_listxattr(fd_file(f), list, size);
@@ -997,24 +1001,26 @@ SYSCALL_DEFINE5(listxattrat, int, dfd, const char __user *, pathname,
unsigned int, at_flags,
char __user *, list, size_t, size)
{
- return path_listxattrat(dfd, pathname, at_flags, list, size);
+ return path_listxattrat(dfd, pathname, at_flags, list, size, 0);
}

SYSCALL_DEFINE3(listxattr, const char __user *, pathname, char __user *, list,
size_t, size)
{
- return path_listxattrat(AT_FDCWD, pathname, 0, list, size);
+ return path_listxattrat(AT_FDCWD, pathname, 0, list, size, FMODE_PATH);
}

SYSCALL_DEFINE3(llistxattr, const char __user *, pathname, char __user *, list,
size_t, size)
{
- return path_listxattrat(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, list, size);
+ return path_listxattrat(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, list,
+ size, FMODE_PATH);
}

SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size)
{
- return path_listxattrat(fd, NULL, AT_EMPTY_PATH, list, size);
+ return path_listxattrat(fd, NULL, AT_EMPTY_PATH, list, size,
+ FMODE_PATH);
}

/*
@@ -1065,7 +1071,8 @@ static int filename_removexattr(int dfd, struct filename *filename,
}

static int path_removexattrat(int dfd, const char __user *pathname,
- unsigned int at_flags, const char __user *name)
+ unsigned int at_flags, const char __user *name,
+ fmode_t mask)
{
struct xattr_name kname;
unsigned int lookup_flags;
@@ -1080,7 +1087,7 @@ static int path_removexattrat(int dfd, const char __user *pathname,

CLASS(filename_maybe_null, filename)(pathname, at_flags);
if (!filename) {
- CLASS(fd, f)(dfd);
+ CLASS(fd_except, f)(dfd, mask);
if (fd_empty(f))
return -EBADF;
return file_removexattr(fd_file(f), &kname);
@@ -1092,24 +1099,25 @@ static int path_removexattrat(int dfd, const char __user *pathname,
SYSCALL_DEFINE4(removexattrat, int, dfd, const char __user *, pathname,
unsigned int, at_flags, const char __user *, name)
{
- return path_removexattrat(dfd, pathname, at_flags, name);
+ return path_removexattrat(dfd, pathname, at_flags, name, 0);
}

SYSCALL_DEFINE2(removexattr, const char __user *, pathname,
const char __user *, name)
{
- return path_removexattrat(AT_FDCWD, pathname, 0, name);
+ return path_removexattrat(AT_FDCWD, pathname, 0, name, FMODE_PATH);
}

SYSCALL_DEFINE2(lremovexattr, const char __user *, pathname,
const char __user *, name)
{
- return path_removexattrat(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, name);
+ return path_removexattrat(AT_FDCWD, pathname, AT_SYMLINK_NOFOLLOW, name,
+ FMODE_PATH);
}

SYSCALL_DEFINE2(fremovexattr, int, fd, const char __user *, name)
{
- return path_removexattrat(fd, NULL, AT_EMPTY_PATH, name);
+ return path_removexattrat(fd, NULL, AT_EMPTY_PATH, name, FMODE_PATH);
}

int xattr_list_one(char **buffer, ssize_t *remaining_size, const char *name)
--
2.54.0