Re: [PATCH 2/3] xattrat: accept empty O_PATH file descriptors
From: Jori Koolstra
Date: Sun Jun 07 2026 - 14:46:33 EST
On Sun, Jun 07, 2026 at 06:41:35PM +0200, Andreas Gruenbacher wrote:
> 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.
>
It does change the security model somewhat, because containers cannot
rely anymore on unmounting procfs to prevent SCM_RIGHT fds from being
used this way. However, we have already been letting go of this a bit
with O_EMPTYPATH. But Christian told me we might have to drop it
depending on what user space is actually doing. I am not aware of any
additional security issues that would be introduced by this. Do you have
any thoughts on this?
> 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
>