BPF List
 help / color / mirror / Atom feed
* Re: [PATCH v3 -next 01/18] fs: pass struct path to notify_change()
  2026-10-27 12:11 ` [PATCH v3 -next 01/18] fs: pass struct path to notify_change() Cai Xinchen
@ 2026-10-27 12:07   ` sashiko-bot
  0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 12:07 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] fs: __notify_change() in ntfs_stream_setattr_common() bypasses base inode LSM checks
- [Low] nfsd: stale @dentry kernel-doc parameter in nfsd4_deleg_getattr_conflict()
--

commit 205fb5135661a50181cb638aff919af8443d55ff
Author: Cai Xinchen <caixinchen1@huawei.com>

fs: pass struct path to notify_change()

This commit updates notify_change() and do_truncate() to accept a struct path
pointer instead of a struct dentry pointer alongside the mount idmap. It also
introduces __notify_change() without the security_inode_setattr() check and
uses it in ntfs_stream_setattr_common() when forwarding attribute updates to
the base inode.

> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> index 1de6c6d757c3e..3a08c56a27c60 100644
> --- a/fs/nfsd/nfs4state.c
> +++ b/fs/nfsd/nfs4state.c

[ ... ]

> @@ -10531,7 +10533,7 @@ static int cb_getattr_update_times(struct dentry *dentry, struct nfs4_delegation
>   * caller must put the reference.
>   */
>  __be32
> -nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
> +nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, const struct path *path,
>  			     struct nfs4_delegation **pdp)

[Severity: Low]
Since the second parameter of nfsd4_deleg_getattr_conflict() in
fs/nfsd/nfs4state.c was changed from dentry to path, should the kernel-doc
comment right above it also be updated from @dentry to @path to avoid
scripts/kernel-doc warnings when building with W=1 or htmldocs?

[ ... ]

> diff --git a/fs/ntfs/named_stream.c b/fs/ntfs/named_stream.c
> index 95a3c05f98211..f0b577a6f7c3e 100644
> --- a/fs/ntfs/named_stream.c
> +++ b/fs/ntfs/named_stream.c
> @@ -897,7 +897,7 @@ static int ntfs_stream_setattr_common(const struct mnt_idmap *idmap,
>  			goto out;
>  		}
>  		if (base_attr.ia_valid)
> -			err = notify_change(idmap, base_dentry, &base_attr,
> +			err = __notify_change(idmap, base_dentry, &base_attr,
>  					NULL);
>  		else
>  			err = 0;

[Severity: High]
Does switching to __notify_change() in ntfs_stream_setattr_common() skip
required LSM and EVM checks on the base inode when a user calls chmod, chown,
or utimensat on an NTFS named stream?

The stream inode (vi) and the base inode (base_vi) are separate struct inode
objects with distinct security state and potentially different file types.

First, NTFS permits named data streams on directories, and
ntfs_stream_setattr_common() preserves base_vi's S_IFMT when forwarding mode
changes:

    else if (base_attr.ia_valid & ATTR_MODE)
        base_attr.ia_mode = (base_attr.ia_mode & ~S_IFMT) |
                (base_vi->i_mode & S_IFMT);

However, ntfs_read_locked_attr_inode() and ntfs_stream_inode_refresh() always
force the stream inode's mode to S_IFREG:

    base_vi = VFS_I(ni->ext.base_ntfs_ino);
    vi->i_uid = base_vi->i_uid;
    vi->i_gid = base_vi->i_gid;
    vi->i_mode = (base_vi->i_mode & ~S_IFMT) | S_IFREG;

In SELinux, the initial notify_change() on the stream dentry checks the
stream inode with sclass SECCLASS_FILE, whereas base_vi has sclass
SECCLASS_DIR. Skipping security_inode_setattr() on base_dentry via
__notify_change() allows a caller with file setattr permission (but not dir
setattr permission) to modify the base directory's mode, ownership, or
timestamps through a directory stream.

Second, ntfs_setxattr() rejects stream inodes with -EOPNOTSUPP while allowing
security.* xattr changes on base_vi. If base_vi is relabeled via setxattr()
while a stream inode is cached in memory (such as via an open stream file
descriptor), security_inode_post_setxattr() (including
selinux_inode_post_setxattr(), smack_inode_post_setxattr(), and
evm_reset_status()) only updates base_vi's security state, leaving the stream
inode's cached label and EVM status stale.

Furthermore, in fs/attr.c, do_notify_change() skips security_inode_setattr()
when path is NULL, but still runs security_inode_post_setattr() at the end:

    if (path) {
        error = security_inode_setattr(idmap, dentry, attr);
        if (error)
            return error;
    }
    ...
    if (!error) {
        fsnotify_change(dentry, ia_valid);
        security_inode_post_setattr(idmap, dentry, ia_valid);
    }

Wouldn't calling __notify_change() on base_dentry bypass
selinux_inode_setattr(), smack_inode_setattr(), and evm_inode_setattr() on
base_dentry while still invoking evm_inode_post_setattr(), causing EVM to
write a new valid security.evm HMAC onto base_dentry without prior integrity
verification on base_dentry?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=1

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights
@ 2026-10-27 12:11 Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 01/18] fs: pass struct path to notify_change() Cai Xinchen
                   ` (17 more replies)
  0 siblings, 18 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="y", Size: 17013 bytes --]

This series adds two new Landlock filesystem access rights,
LANDLOCK_ACCESS_FS_READ_METADATA and LANDLOCK_ACCESS_FS_WRITE_METADATA,
which control access to file and directory metadata such as inode
attributes (mode, ownership, timestamps), extended attributes, POSIX
ACLs and chattr(1)-style flags.  It picks up the work from the
"landlock: add chmod and chown support" series [1] and follows the
coarse-grained grouping discussed in that thread [2]: instead of
separate chmod/chown rights, metadata operations are grouped into one
read and one write right.

Landlock evaluates access rights on a per-path basis, but the
metadata related LSM hooks (inode_setattr, inode_setsecctx, the inode
xattr, POSIX ACL and fileattr hooks) only receive the dentry of the
accessed object.  Patches 1-8 therefore first pass struct path
instead of dentry through the metadata-related VFS helpers and LSM
hooks.  The hooks also receive a struct file argument, set by the
VFS to the file through which the operation is requested when there
is one (i.e. for the file descriptor-based system calls such as
fchmod(2), fchown(2), fgetxattr(2), utimensat(2) on an empty path,
and for the FS_IOC_* ioctls and file_[gs]etattr(2) with
AT_EMPTY_PATH), and NULL otherwise (i.e. for the path-based system
calls and for kernel-internal accesses).  This is a pure refactoring
with no behavior change for the other LSMs, which only have their
hook implementations' signatures updated; the file argument is only
consumed by Landlock (patch 15).  The series is split so that every
patch builds on its own:

  1: notify_change() and its callers
  2: inode_setsecctx hook (must come before 3: the SELinux and Smack
     implementations call __vfs_setxattr_locked internally)
  3: xattr helpers, which also drops a redundant EVM xattr size sanity
     check whose vfs_getxattr() call only has a dentry and therefore
     cannot be migrated to the new path-based signature
  4: POSIX ACL helpers
  5: inode_setattr hook, which now receives the struct file held by
     the fd-based callers (fchmod(2), fchown(2), fchmodat2(2) and
     fchownat(2) with AT_EMPTY_PATH, utimensat(2) on an empty path);
     do_truncate() passes NULL because size changes are governed by
     the truncate hooks
  6: inode xattr hooks, threaded through the xattr helpers so that
     the f[gs]etxattr(2) and io_uring flows pass their file
  7: inode POSIX ACL hooks, threaded through do_set_acl()/do_get_acl()
  8: inode fileattr hooks (FS_IOC_[GS]ETFLAGS, FS_IOC_FS[SG]ETXATTR,
     file_[gs]etattr(2)), which also adds __vfs_fileattr_[gs]et()
     counterparts without security checks for the fileattr forwards
     of ecryptfs and overlayfs, following the __vfs_setxattr_noperm()
     precedent, and removes the ovl_security_fileattr() hack

Three deliberate scoping decisions for this refactor:

- The hooks consistently take struct path rather than struct file as
  their primary object.  The VFS call sites involved (chmod(2),
  chown(2), utimensat(2), xattr(2) and ACL syscalls) operate on
  paths, and several of them (lstat(2), lchown(2), llistxattr(2),
  ...) have no struct file to begin with.  The optional struct file
  argument carries the open-time semantic described below.

- struct inode_operations->setattr still receives (idmap, dentry, attr).
  Only the VFS boundary (notify_change()) and the LSM hook layer see the
  path, which keeps the refactor contained to fs/attr.c and the LSM
  infrastructure instead of touching every filesystem.

- The path argument of the hooks is only meant to be handed to VFS
  helpers taking a struct path (e.g., notify_change(), the xattr
  helpers): the VFS makes no guarantees with regard to path->mnt,
  whose layout and implementation details are subject to change, and
  implementations must not rely on its properties.  The idmap, in
  contrast, is passed separately so that the VFS and the filesystem
  are guaranteed to see the same idmap.
  
Unlike what was done for LANDLOCK_ACCESS_FS_TRUNCATE, no new file_*
hooks are introduced for the file descriptor flows: the metadata
hooks are already consumed by other LSMs (SELinux, Smack, EVM, IMA,
AppArmor, TOMOYO) on these flows, and routing them to new hooks
would either silently remove the other LSMs' checks, or keep the
path_* hooks as well and check twice with Landlock.  Passing the
struct file to the existing path_* hooks instead keeps the pipeline
and the other LSMs' behavior unchanged, while giving Landlock the
information it needs to apply the open-time semantic below.

Since these hooks now operate on paths rather than inodes, patches
9-14 rename them to the path_* naming convention used by the other
hooks that take a struct path (path_chmod, path_chown, path_link,
...).  The inode_getattr hook, which already received a struct path,
is renamed to path_getattr as well; patch 9 also threads the struct
file held by the fd-based vfs_fstat() and vfs_statx_fd() callers
through it, while the many kernel-internal vfs_getattr() callers
keep a wrapper which passes NULL.  These renames are purely
mechanical and can be reviewed independently.

Patch 15 then adds the access rights and implements their
enforcement, and patches 16-18 add the selftests, the sandboxer
sample update and the documentation.

Patch 15 applies the LANDLOCK_ACCESS_FS_TRUNCATE mechanic to the
new rights, following Mickaël's confirmation [4]: for operations on
already opened files, it is the access rights at the time of open(2)
which decide whether a metadata operation is permitted, not the
rights of the current thread.  READ_METADATA and WRITE_METADATA are
looked up at open time, recorded in the opened file, and consumed by
the metadata hooks when they are called with a file.  As a
consequence:

- A file descriptor opened before sandboxing keeps allowing metadata
  operations whatever the current domain is, which resolves the
  "catch-22" raised by Justin [5]: an already opened file descriptor
  keeps working, it is not an escape hatch because its rights were
  fixed when it was legitimate to open the file.
- Conversely, a restricted file descriptor passed to another
  (possibly non-landlocked) process keeps denying metadata
  operations: the file descriptor carries the rights of the process
  which opened it.
- ioctl(FS_IOC_GETFLAGS) and friends on a file descriptor opened
  without READ_METADATA are now denied, which closes the metadata
  leak Justin was concerned about.
- O_PATH file descriptors are an exception: they are not opened
  through the open permission checks (cf. do_dentry_open()) and
  carry no recorded rights; metadata accesses through them are
  checked against the current domain, like path-based accesses.
- File descriptors obtained without going through the file_open
  hook (e.g. memfd_create(2), pipes, sockets) keep the full access
  rights granted at file allocation time, consistent with the
  truncate behavior.
- deny_masks_t is widened from u8 to u16 to keep tracking one
  nibble per optional access right for the audit logs.
  
The path-based semantics are unchanged from v2:

- READ_METADATA covers stat(2) and friends, getxattr(2) and friends,
  listxattr(2) and friends, POSIX ACL reads, and the chattr(1)-style
  reads ioctl(FS_IOC_GETFLAGS), ioctl(FS_IOC_FSGETXATTR) and
  file_getattr(2).
- WRITE_METADATA covers chmod(2) and friends, chown(2) and friends,
  utimensat(2) and friends, setxattr(2) and friends, removexattr(2)
  and friends, POSIX ACL set and remove, and the chattr(1)-style
  writes ioctl(FS_IOC_SETFLAGS), ioctl(FS_IOC_FSSETXATTR) and
  file_setattr(2).
- Only explicit metadata changes requested by user space are
  restricted.  Implicit changes performed by the kernel (e.g.
  timestamp updates and size changes on write(2) or truncate(2)) are
  not, and neither are chmod(2)/chown(2) calls that change nothing
  (e.g. chown(2) with (-1, -1), which never reaches the hook),
  matching the SELinux inode_setattr behavior.
- Clearing the set-user-ID and set-group-ID bits after write(2) or
  truncate(2) does not require WRITE_METADATA either: the mode
  change that the kernel synthesizes from ATTR_KILL_S[UG]ID is
  ignored, like SELinux and Smack do.
- Kernel-internal accesses performed with override_creds() (e.g.
  overlayfs, cachefiles) and kernel threads without a Landlock
  domain (e.g. nfsd, ksmbd) are not restricted.

Two review suggestions are deliberately not taken, following the
TRUNCATE precedent and Mickaël's arbitration [4]:

- Requiring READ_METADATA for open(2) and readdir(2) (Günther's
  lockstep proposal) would change the semantics of existing rights;
  the documentation instead recommends granting READ_METADATA
  together with READ_FILE and READ_DIR, as it already recommends
  grouping TRUNCATE with WRITE_FILE.
- Making READ_FILE imply READ_METADATA (Justin's proposal) would
  break the orthogonality of the access rights; the same
  documentation warning covers the grouping.

The Landlock ABI version is incremented from 12 to 13; ABI 12 was
taken by the capability and namespace use restrictions that are
already in linux-next.

The series is based on linux-next commit f1ac44c32cba ("Add linux-next
specific files for 20261026").

Changes since v2 [3]:

- The idmap is passed separately from the struct path again, instead
  of being derived from path->mnt inside the helpers and the hooks.
  As Christian explains in his review of patch 1, the idmap must be
  taken of the mount at the time it was checked and passed down to
  the filesystem so that the VFS and the filesystem are guaranteed
  to see the same idmap.  The mainline signatures are kept wherever
  they already pass an idmap: notify_change(), the xattr, POSIX ACL
  and fileattr helpers, and the setattr, setxattr, removexattr and
  *_acl hooks, along with their SELinux, Smack, EVM, IMA and
  capability implementations, keep receiving the idmap as a separate
  argument (patches 1, 3-8, 10-12).  The Landlock implementations
  receive it as well (patch 15).  The getxattr and listxattr hooks
  keep taking no idmap, like their mainline counterparts, and the
  new fileattr hooks take none either, as they have no mainline
  counterpart to inherit the argument from.
- The metadata-related LSM hooks now also receive the struct file
  through which the operation is requested, when there is one
  (patches 5-9); see above for why no new file_* hooks are
  introduced.
- Metadata accesses on already opened files are now checked at open
  time, following the LANDLOCK_ACCESS_FS_TRUNCATE mechanic confirmed
  by Mickaël [4] (patch 15).
- The Landlock patch is moved after the hook renames (patch 15 now,
  patch 9 in v2), so that it lands on the hooks' final path_* names.
- The kernel-doc comments of the converted security_*() wrappers and
  of the EVM and Smack hook implementations are completed and fixed:
  the new file and idmap arguments are documented, and the wrappers
  state that the VFS makes no guarantees with regard to path->mnt.
- The selftests are adapted to the open-time semantic (the
  fileattr_write_only and write_metadata denial cases now use file
  descriptors opened after the ruleset is enforced) and extended
  with the metadata_open_time and metadata_transfer tests; the
  documentation describes the open-time semantic and the O_PATH
  exception.

[1]
https://lore.kernel.org/all/20220827111215.131442-1-xiujianfeng@huawei.com/
[2]
https://lore.kernel.org/all/abc960a1-e66e-792e-6869-cfd201c29dbe@digikod.net/
[3]
https://lore.kernel.org/all/20261015125425.3246251-1-caixinchen1@huawei.com/
[4] https://lore.kernel.org/all/20260929.Ahgoo4yeetai@digikod.net/
[5] https://lore.kernel.org/all/arvjb0HJYh1KyEBB@suesslenovo/

Cai Xinchen (18):
  fs: pass struct path to notify_change()
  LSM: pass struct path to the inode_setsecctx hook
  fs: pass struct path to xattr helpers
  fs: pass struct path to POSIX ACL helpers
  LSM: pass struct path and struct file to the inode_setattr hook
  LSM: pass struct path and struct file to the inode xattr hooks
  LSM: pass struct path and struct file to the inode posix acl hooks
  LSM: pass struct path and struct file to the inode fileattr hooks
  LSM: rename inode_getattr to path_getattr and pass struct file
  LSM: rename inode_setattr to path_setattr
  LSM: rename the inode xattr hooks to path_xattr hooks
  LSM: rename the inode posix acl hooks to path_*_acl hooks
  LSM: rename the inode fileattr hooks to path_file_* hooks
  LSM: rename inode_setsecctx to path_setsecctx
  landlock: Add READ_METADATA and WRITE_METADATA access rights
  selftests/landlock: Add tests for metadata access rights
  samples/landlock: Add metadata rights to sandboxer
  Documentation: Update landlock doc for metadata rights

 Documentation/bpf/fs_kfuncs.rst               |   2 +-
 Documentation/userspace-api/landlock.rst      |  92 +-
 drivers/base/devtmpfs.c                       |   6 +-
 drivers/block/zloop.c                         |   9 +-
 fs/attr.c                                     | 113 ++-
 fs/bpf_fs_kfuncs.c                            |   6 +-
 fs/btrfs/volumes.c                            |   2 +-
 fs/cachefiles/xattr.c                         |  45 +-
 fs/coredump.c                                 |   2 +-
 fs/ecryptfs/inode.c                           |  42 +-
 fs/exfat/file.c                               |   4 +-
 fs/fat/file.c                                 |   4 +-
 fs/file_attr.c                                | 159 ++-
 fs/init.c                                     |   6 +-
 fs/inode.c                                    |   7 +-
 fs/internal.h                                 |  26 +-
 fs/namei.c                                    |   5 +-
 fs/nfsd/nfs4ctl.h                             |   4 +-
 fs/nfsd/nfs4state.c                           |  14 +-
 fs/nfsd/nfs4xdr.c                             |   2 +-
 fs/nfsd/state.h                               |   2 +-
 fs/nfsd/vfs.c                                 |  79 +-
 fs/ntfs/named_stream.c                        |   2 +-
 fs/open.c                                     |  63 +-
 fs/overlayfs/copy_up.c                        |   4 +-
 fs/overlayfs/inode.c                          |  54 +-
 fs/overlayfs/overlayfs.h                      |  44 +-
 fs/overlayfs/xattrs.c                         |  12 +-
 fs/posix_acl.c                                |  52 +-
 fs/smb/server/smb2pdu.c                       |  56 +-
 fs/smb/server/smb_common.c                    |  12 +-
 fs/smb/server/smbacl.c                        |  12 +-
 fs/smb/server/vfs.c                           | 100 +-
 fs/smb/server/vfs.h                           |  18 +-
 fs/stat.c                                     |  48 +-
 fs/utimes.c                                   |  23 +-
 fs/xattr.c                                    | 114 ++-
 include/linux/fileattr.h                      |  10 +-
 include/linux/fs.h                            |  14 +-
 include/linux/landlock.h                      |   4 +-
 include/linux/lsm_hook_defs.h                 |  59 +-
 include/linux/posix_acl.h                     |  24 +-
 include/linux/security.h                      | 129 +--
 include/linux/xattr.h                         |  31 +-
 include/uapi/linux/landlock.h                 |  25 +-
 init/initramfs.c                              |   2 +-
 kernel/bpf/bpf_lsm.c                          |  10 +-
 kernel/trace/bpf_trace.c                      |   2 +-
 samples/landlock/sandboxer.c                  |  16 +-
 security/apparmor/lsm.c                       |   5 +-
 security/commoncap.c                          |  24 +-
 security/integrity/evm/evm_crypto.c           |   8 +-
 security/integrity/evm/evm_main.c             |  77 +-
 security/integrity/ima/ima_appraise.c         |  41 +-
 security/landlock/access.h                    |  13 +-
 security/landlock/fs.c                        | 232 ++++-
 security/landlock/limits.h                    |   2 +-
 security/landlock/syscalls.c                  |   2 +-
 security/security.c                           | 169 ++--
 security/selinux/hooks.c                      | 104 +-
 security/smack/smack_lsm.c                    | 124 ++-
 security/tomoyo/tomoyo.c                      |   7 +-
 .../testing/selftests/bpf/prog_tests/d_path.c |   4 +-
 .../selftests/bpf/prog_tests/fs_kfuncs.c      |  10 +-
 .../testing/selftests/bpf/progs/test_d_path.c |   2 +-
 .../bpf/progs/test_d_path_check_rdonly_mem.c  |   2 +-
 .../bpf/progs/test_d_path_check_types.c       |   2 +-
 .../selftests/bpf/progs/test_get_xattr.c      |   7 +-
 .../bpf/progs/test_set_remove_xattr.c         |  34 +-
 .../bpf/progs/verifier_lsm_init_xattr.c       |   8 +-
 .../selftests/bpf/progs/verifier_vfs_accept.c |   2 +-
 .../selftests/bpf/progs/verifier_vfs_reject.c |   2 +-
 tools/testing/selftests/landlock/base_test.c  |   2 +-
 tools/testing/selftests/landlock/fs_test.c    | 921 +++++++++++++++++-
 .../selftests/landlock/trace_fs_test.c        |   3 +-
 75 files changed, 2472 insertions(+), 906 deletions(-)

-- 
2.18.0.huawei.25


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 01/18] fs: pass struct path to notify_change()
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:07   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 02/18] LSM: pass struct path to the inode_setsecctx hook Cai Xinchen
                   ` (16 subsequent siblings)
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

notify_change() takes a struct mnt_idmap and a struct dentry even though
all of its callers either already hold a struct path (or a struct file)
or have to pass &nop_mnt_idmap because no idmapped mount is involved.
Switch the helper and its declaration to take a struct path instead of
the dentry, next to the idmap: the dentry is taken from the path, while
the idmap remains a separate argument, as it must be taken from the
mount at the time the path is checked and passed down unchanged, so
that the VFS and the filesystem are guaranteed to see the same idmap.

The ntfs named stream support applies metadata changes shared with a
stream to the base inode's anonymous dentry, for which no struct path
exists.  Add a __notify_change() variant without the LSM hook call,
following the __vfs_setxattr_noperm() and __vfs_fileattr_set()
precedents, and use it for that internal forward: the user-facing
operation on the stream was already checked by the surrounding
notify_change() call, so the base inode does not need a second
security_inode_setattr() check.

The security_inode_setattr() call within notify_change() keeps taking
the idmap and dentry for now; it will be converted together with
the LSM hook itself in a separate patch.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 drivers/base/devtmpfs.c  |   6 ++-
 fs/attr.c                | 109 +++++++++++++++++++++++++++------------
 fs/coredump.c            |   2 +-
 fs/ecryptfs/inode.c      |  15 +++---
 fs/inode.c               |   7 +--
 fs/namei.c               |   5 +-
 fs/nfsd/nfs4state.c      |  14 ++---
 fs/nfsd/nfs4xdr.c        |   2 +-
 fs/nfsd/state.h          |   2 +-
 fs/nfsd/vfs.c            |  15 ++++--
 fs/ntfs/named_stream.c   |   2 +-
 fs/open.c                |  17 +++---
 fs/overlayfs/overlayfs.h |   7 ++-
 fs/smb/server/smb2pdu.c  |   2 +-
 fs/smb/server/smbacl.c   |   2 +-
 fs/utimes.c              |   2 +-
 include/linux/fs.h       |   8 +--
 17 files changed, 141 insertions(+), 76 deletions(-)

diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c
index 11c70888f38b..410906a66ee9 100644
--- a/drivers/base/devtmpfs.c
+++ b/drivers/base/devtmpfs.c
@@ -284,13 +284,14 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
 			dev->devt, NULL);
 	if (!err) {
 		struct iattr newattrs;
+		struct path attr_path = { .mnt = path.mnt, .dentry = dentry };
 
 		newattrs.ia_mode = mode;
 		newattrs.ia_uid = uid;
 		newattrs.ia_gid = gid;
 		newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
 		inode_lock(d_inode(dentry));
-		notify_change(&nop_mnt_idmap, dentry, &newattrs, NULL);
+		notify_change(&nop_mnt_idmap, &attr_path, &newattrs, NULL);
 		inode_unlock(d_inode(dentry));
 
 		/* mark as kernel-created inode */
@@ -380,6 +381,7 @@ static int handle_remove(const char *nodename, struct device *dev)
 	inode = d_inode(dentry);
 	if (dev_mynode(dev, inode)) {
 		struct iattr newattrs;
+		struct path attr_path = { .mnt = parent.mnt, .dentry = dentry };
 		/*
 		 * before unlinking this node, reset permissions
 		 * of possible references like hardlinks
@@ -390,7 +392,7 @@ static int handle_remove(const char *nodename, struct device *dev)
 		newattrs.ia_valid =
 			ATTR_UID|ATTR_GID|ATTR_MODE;
 		inode_lock(d_inode(dentry));
-		notify_change(&nop_mnt_idmap, dentry, &newattrs, NULL);
+		notify_change(&nop_mnt_idmap, &attr_path, &newattrs, NULL);
 		inode_unlock(d_inode(dentry));
 		err = vfs_unlink(&nop_mnt_idmap, d_inode(parent.dentry),
 				 dentry, NULL);
diff --git a/fs/attr.c b/fs/attr.c
index 9706d370f34a..cc06fd676e6b 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -397,35 +397,10 @@ int may_setattr(const struct mnt_idmap *idmap, struct inode *inode,
 }
 EXPORT_SYMBOL(may_setattr);
 
-/**
- * notify_change - modify attributes of a filesystem object
- * @idmap:	idmap of the mount the inode was found from
- * @dentry:	object affected
- * @attr:	new attributes
- * @delegated_inode: returns inode, if the inode is delegated
- *
- * The caller must hold the i_rwsem exclusively on the affected object.
- *
- * If notify_change discovers a delegation in need of breaking,
- * it will return -EWOULDBLOCK and return a reference to the inode in
- * delegated_inode.  The caller should then break the delegation and
- * retry.  Because breaking a delegation may take a long time, the
- * caller should drop the i_rwsem before doing so.
- *
- * Alternatively, a caller may pass NULL for delegated_inode.  This may
- * be appropriate for callers that expect the underlying filesystem not
- * to be NFS exported.  Also, passing NULL is fine for callers holding
- * the file open for write, as there can be no conflicting delegation in
- * that case.
- *
- * If the inode has been found through an idmapped mount the idmap of
- * the vfsmount must be passed through @idmap. This function will then
- * take care to map the inode according to @idmap before checking
- * permissions. On non-idmapped mounts or if permission checking is to be
- * performed on the raw inode simply pass @nop_mnt_idmap.
- */
-int notify_change(const struct mnt_idmap *idmap, struct dentry *dentry,
-		  struct iattr *attr, struct delegated_inode *delegated_inode)
+static int do_notify_change(const struct path *const path,
+			    const struct mnt_idmap *const idmap,
+			    struct dentry *const dentry, struct iattr *attr,
+			    struct delegated_inode *delegated_inode)
 {
 	struct inode *inode = dentry->d_inode;
 	umode_t mode = inode->i_mode;
@@ -537,9 +512,16 @@ int notify_change(const struct mnt_idmap *idmap, struct dentry *dentry,
 	    !vfsgid_valid(i_gid_into_vfsgid(idmap, inode)))
 		return -EOVERFLOW;
 
-	error = security_inode_setattr(idmap, dentry, attr);
-	if (error)
-		return error;
+	/*
+	 * The LSM checks are only performed for user-facing calls (i.e.
+	 * when @path is set): internal forwards, e.g. from a stacking
+	 * filesystem, were already checked on the user-facing path.
+	 */
+	if (path) {
+		error = security_inode_setattr(idmap, dentry, attr);
+		if (error)
+			return error;
+	}
 
 	/*
 	 * If ATTR_DELEG is set, then these attributes are being set on
@@ -564,4 +546,67 @@ int notify_change(const struct mnt_idmap *idmap, struct dentry *dentry,
 
 	return error;
 }
+
+/**
+ * __notify_change - modify attributes of a filesystem object, without
+ *                   the LSM check
+ * @idmap:	idmap of the mount the inode was found from
+ * @dentry:	object affected
+ * @attr:	new attributes
+ * @delegated_inode: returns inode, if the inode is delegated
+ *
+ * Like notify_change(), but no security_inode_setattr() hook is called:
+ * this is the variant to use for internal forwards, where the LSM
+ * checks were already performed on the user-facing path.
+ *
+ * The caller must hold the i_rwsem exclusively on the affected object.
+ *
+ * If the inode has been found through an idmapped mount, permission
+ * checking is performed based on @idmap.  This function will take care
+ * to map the inode according to the idmap before checking permissions.
+ *
+ * Return: 0 on success, or a negative error on failure.
+ */
+int __notify_change(const struct mnt_idmap *idmap, struct dentry *dentry,
+		    struct iattr *attr, struct delegated_inode *delegated_inode)
+{
+	return do_notify_change(NULL, idmap, dentry, attr, delegated_inode);
+}
+EXPORT_SYMBOL(__notify_change);
+
+/**
+ * notify_change - modify attributes of a filesystem object
+ * @idmap:	idmap of the mount the inode was found from
+ * @path:	object affected
+ * @attr:	new attributes
+ * @delegated_inode: returns inode, if the inode is delegated
+ *
+ * The caller must hold the i_rwsem exclusively on the affected object.
+ *
+ * If notify_change discovers a delegation in need of breaking,
+ * it will return -EWOULDBLOCK and return a reference to the inode in
+ * delegated_inode.  The caller should then break the delegation and
+ * retry.  Because breaking a delegation may take a long time, the
+ * caller should drop the i_rwsem before doing so.
+ *
+ * Alternatively, a caller may pass NULL for delegated_inode.  This may
+ * be appropriate for callers that expect the underlying filesystem not
+ * to be NFS exported.  Also, passing NULL is fine for callers holding
+ * the file open for write, as there can be no conflicting delegation in
+ * that case.
+ *
+ * If the inode has been found through an idmapped mount the idmap of
+ * the vfsmount must be passed through @idmap. This function will then
+ * take care to map the inode according to @idmap before checking
+ * permissions. On non-idmapped mounts or if permission checking is to
+ * be performed on the raw inode simply pass @nop_mnt_idmap.
+ *
+ * Return: 0 on success, or a negative error on failure.
+ */
+int notify_change(const struct mnt_idmap *idmap, const struct path *path,
+		  struct iattr *attr, struct delegated_inode *delegated_inode)
+{
+	return do_notify_change(path, idmap, path->dentry, attr,
+				delegated_inode);
+}
 EXPORT_SYMBOL(notify_change);
diff --git a/fs/coredump.c b/fs/coredump.c
index b25ef2ffbdee..6ade29cf7d06 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1060,7 +1060,7 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,
 	}
 	if (!(file->f_mode & FMODE_CAN_WRITE))
 		return false;
-	if (do_truncate(idmap, file->f_path.dentry, 0, 0, file))
+	if (do_truncate(idmap, &file->f_path, 0, 0, file))
 		return false;
 
 	cprm->file = no_free_ptr(file);
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 48e520960d66..26967e4e4660 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -732,7 +732,8 @@ upper_size_to_lower_size(struct ecryptfs_crypt_stat *crypt_stat,
  */
 static int __ecryptfs_truncate(struct dentry *dentry, const struct iattr *ia)
 {
-	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
+	struct path lower_path = ecryptfs_lower_path(dentry);
+	struct dentry *lower_dentry = lower_path.dentry;
 	struct inode *inode = d_inode(dentry);
 	struct ecryptfs_crypt_stat *crypt_stat;
 	loff_t i_size = i_size_read(inode);
@@ -821,7 +822,7 @@ static int __ecryptfs_truncate(struct dentry *dentry, const struct iattr *ia)
 set_size:
 	lower_ia.ia_valid |= ATTR_SIZE;
 	inode_lock(d_inode(lower_dentry));
-	rc = notify_change(&nop_mnt_idmap, lower_dentry, &lower_ia, NULL);
+	rc = notify_change(&nop_mnt_idmap, &lower_path, &lower_ia, NULL);
 	inode_unlock(d_inode(lower_dentry));
 out:
 	ecryptfs_put_lower_file(inode);
@@ -873,7 +874,6 @@ static int ecryptfs_setattr(const struct mnt_idmap *idmap,
 			    struct dentry *dentry, struct iattr *ia)
 {
 	struct inode *inode = d_inode(dentry);
-	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
 	struct inode *lower_inode = ecryptfs_inode_to_lower(inode);
 	struct ecryptfs_crypt_stat *crypt_stat;
 	int rc;
@@ -925,14 +925,15 @@ static int ecryptfs_setattr(const struct mnt_idmap *idmap,
 	if (ia->ia_valid & ATTR_SIZE) {
 		rc = __ecryptfs_truncate(dentry, ia);
 	} else {
+		struct path lower_path = ecryptfs_lower_path(dentry);
 		struct iattr lower_ia;
 
 		ecryptfs_iattr_to_lower(&lower_ia, ia);
 
-		inode_lock(d_inode(lower_dentry));
-		rc = notify_change(&nop_mnt_idmap, lower_dentry, &lower_ia,
-				NULL);
-		inode_unlock(d_inode(lower_dentry));
+		inode_lock(d_inode(lower_path.dentry));
+		rc = notify_change(&nop_mnt_idmap, &lower_path, &lower_ia,
+				   NULL);
+		inode_unlock(d_inode(lower_path.dentry));
 	}
 out:
 	fsstack_copy_attr_all(inode, lower_inode);
diff --git a/fs/inode.c b/fs/inode.c
index 702f2d5ef459..fe176293fd36 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2357,7 +2357,7 @@ int dentry_needs_remove_privs(const struct mnt_idmap *idmap,
 }
 
 static int __remove_privs(const struct mnt_idmap *idmap,
-			  struct dentry *dentry, int kill)
+			  const struct path *path, int kill)
 {
 	struct iattr newattrs;
 
@@ -2366,7 +2366,7 @@ static int __remove_privs(const struct mnt_idmap *idmap,
 	 * Note we call this on write, so notify_change will not
 	 * encounter any conflicting delegations:
 	 */
-	return notify_change(idmap, dentry, &newattrs, NULL);
+	return notify_change(idmap, path, &newattrs, NULL);
 }
 
 static int file_remove_privs_flags(struct file *file, unsigned int flags)
@@ -2387,7 +2387,8 @@ static int file_remove_privs_flags(struct file *file, unsigned int flags)
 		if (flags & IOCB_NOWAIT)
 			return -EAGAIN;
 
-		error = __remove_privs(file_mnt_idmap(file), dentry, kill);
+		error = __remove_privs(file_mnt_idmap(file), &file->f_path,
+				       kill);
 	}
 
 	if (!error)
diff --git a/fs/namei.c b/fs/namei.c
index 96c65940391a..e3961bc336c9 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4297,9 +4297,8 @@ static int handle_truncate(const struct mnt_idmap *idmap, struct file *filp)
 
 	error = security_file_truncate(filp);
 	if (!error) {
-		error = do_truncate(idmap, path->dentry, 0,
-				    ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
-				    filp);
+		error = do_truncate(idmap, path, 0,
+				    ATTR_MTIME|ATTR_CTIME|ATTR_OPEN, filp);
 	}
 	put_write_access(inode);
 	return error;
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 1de6c6d757c3..3a08c56a27c6 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -10477,8 +10477,10 @@ bool nfsd4_vet_deleg_time(struct timespec64 *req, const struct timespec64 *orig,
 	return true;
 }
 
-static int cb_getattr_update_times(struct dentry *dentry, struct nfs4_delegation *dp)
+static int cb_getattr_update_times(const struct path *path,
+				   struct nfs4_delegation *dp)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
 	struct nfs4_cb_fattr *ncf = &dp->dl_cb_fattr;
 	struct iattr attrs = { };
@@ -10508,7 +10510,7 @@ static int cb_getattr_update_times(struct dentry *dentry, struct nfs4_delegation
 
 	attrs.ia_valid |= ATTR_DELEG;
 	inode_lock(inode);
-	ret = notify_change(&nop_mnt_idmap, dentry, &attrs, NULL);
+	ret = notify_change(&nop_mnt_idmap, path, &attrs, NULL);
 	inode_unlock(inode);
 	return ret;
 }
@@ -10531,7 +10533,7 @@ static int cb_getattr_update_times(struct dentry *dentry, struct nfs4_delegation
  * caller must put the reference.
  */
 __be32
-nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
+nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, const struct path *path,
 			     struct nfs4_delegation **pdp)
 {
 	struct nfsd_thread_local_info *ntli = rqstp->rq_private;
@@ -10539,7 +10541,7 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
 	struct nfs4_delegation *dp = NULL;
 	struct file_lease *fl;
 	struct nfs4_cb_fattr *ncf;
-	struct inode *inode = d_inode(dentry);
+	struct inode *inode = d_inode(path->dentry);
 	__be32 status;
 
 	ctx = locks_inode_context(inode);
@@ -10603,7 +10605,7 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
 		 * not update the file's metadata with the client's
 		 * modified size
 		 */
-		err = cb_getattr_update_times(dentry, dp);
+		err = cb_getattr_update_times(path, dp);
 		if (err) {
 			status = nfserrno(err);
 			goto out_status;
@@ -10789,7 +10791,7 @@ void nfsd_update_cmtime_attr(struct file *f, unsigned int flags)
 	};
 
 	inode_lock(inode);
-	ret = notify_change(&nop_mnt_idmap, f->f_path.dentry, &attr, NULL);
+	ret = notify_change(&nop_mnt_idmap, &f->f_path, &attr, NULL);
 	inode_unlock(inode);
 	if (ret)
 		pr_notice_ratelimited("nfsd: Unable to update timestamps on "
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 84161dbecd1c..2a86b69b2ae9 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -4083,7 +4083,7 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 	    (attrmask[1] & (FATTR4_WORD1_TIME_ACCESS |
 			    FATTR4_WORD1_TIME_MODIFY |
 			    FATTR4_WORD1_TIME_METADATA))) {
-		status = nfsd4_deleg_getattr_conflict(rqstp, dentry, &dp);
+		status = nfsd4_deleg_getattr_conflict(rqstp, &path, &dp);
 		if (status)
 			goto out;
 	}
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index cd9294f024bb..183123f0b91f 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -1018,7 +1018,7 @@ static inline bool try_to_expire_client(struct nfs4_client *clp)
 }
 
 extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
-		struct dentry *dentry, struct nfs4_delegation **pdp);
+		const struct path *path, struct nfs4_delegation **pdp);
 
 struct nfsd4_get_dir_delegation;
 struct nfs4_delegation *nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 56c49b5bc5bb..54440029c235 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -455,7 +455,7 @@ nfsd_get_write_access(struct svc_fh *fhp)
 	return nfserrno(get_write_access(d_inode(fhp->fh_dentry)));
 }
 
-static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap)
+static int __nfsd_setattr(const struct path *path, struct iattr *iap)
 {
 	int host_err;
 
@@ -475,7 +475,8 @@ static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap)
 		if (iap->ia_size < 0)
 			return -EFBIG;
 
-		host_err = notify_change(&nop_mnt_idmap, dentry, &size_attr, NULL);
+		host_err = notify_change(&nop_mnt_idmap, path, &size_attr,
+					  NULL);
 		if (host_err)
 			return host_err;
 		iap->ia_valid &= ~ATTR_SIZE;
@@ -501,7 +502,7 @@ static int __nfsd_setattr(struct dentry *dentry, struct iattr *iap)
 	if (!(iap->ia_valid & ATTR_DELEG))
 		iap->ia_valid |= ATTR_CTIME;
 
-	return notify_change(&nop_mnt_idmap, dentry, iap, NULL);
+	return notify_change(&nop_mnt_idmap, path, iap, NULL);
 }
 
 /**
@@ -524,6 +525,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
 {
 	struct dentry	*dentry;
 	struct inode	*inode;
+	struct path	path;
 	struct iattr	*iap = attr->na_iattr;
 	int		accmode = NFSD_MAY_SATTR;
 	umode_t		ftype = 0;
@@ -569,6 +571,11 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	dentry = fhp->fh_dentry;
 	inode = d_inode(dentry);
 
+	path = (struct path){
+		.mnt	= fhp->fh_export->ex_path.mnt,
+		.dentry	= dentry,
+	};
+
 	nfsd_sanitize_attrs(inode, iap);
 
 	/*
@@ -612,7 +619,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		 * copy for every loop iteration.
 		 */
 		attrs = *iap;
-		host_err = __nfsd_setattr(dentry, &attrs);
+		host_err = __nfsd_setattr(&path, &attrs);
 		if (host_err != -EAGAIN || !retries--)
 			break;
 		if (!nfsd_wait_for_delegreturn(rqstp, inode))
diff --git a/fs/ntfs/named_stream.c b/fs/ntfs/named_stream.c
index 95a3c05f9821..f0b577a6f7c3 100644
--- a/fs/ntfs/named_stream.c
+++ b/fs/ntfs/named_stream.c
@@ -897,7 +897,7 @@ static int ntfs_stream_setattr_common(const struct mnt_idmap *idmap,
 			goto out;
 		}
 		if (base_attr.ia_valid)
-			err = notify_change(idmap, base_dentry, &base_attr,
+			err = __notify_change(idmap, base_dentry, &base_attr,
 					NULL);
 		else
 			err = 0;
diff --git a/fs/open.c b/fs/open.c
index bf398105f03a..294d0ea7488e 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -36,10 +36,11 @@
 
 #include "internal.h"
 
-int do_truncate(const struct mnt_idmap *idmap, struct dentry *dentry,
+int do_truncate(const struct mnt_idmap *idmap, const struct path *path,
 		loff_t length, unsigned int time_attrs, struct file *filp)
 {
 	int ret;
+	struct dentry *dentry = path->dentry;
 	struct iattr newattrs;
 
 	/* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */
@@ -65,7 +66,7 @@ int do_truncate(const struct mnt_idmap *idmap, struct dentry *dentry,
 		return ret;
 
 	/* Note any delegations or leases have already been broken: */
-	ret = notify_change(idmap, dentry, &newattrs, NULL);
+	ret = notify_change(idmap, path, &newattrs, NULL);
 	inode_unlock(dentry->d_inode);
 	return ret;
 }
@@ -115,7 +116,7 @@ int vfs_truncate(const struct path *path, loff_t length)
 
 	error = security_path_truncate(path);
 	if (!error)
-		error = do_truncate(idmap, path->dentry, length, 0, NULL);
+		error = do_truncate(idmap, path, length, 0, NULL);
 
 put_write_and_out:
 	put_write_access(inode);
@@ -191,8 +192,8 @@ int do_ftruncate(struct file *file, loff_t length, unsigned int flags)
 		return error;
 
 	scoped_guard(super_write, inode->i_sb)
-		return do_truncate(file_mnt_idmap(file), dentry, length,
-				   ATTR_MTIME | ATTR_CTIME, file);
+		return do_truncate(file_mnt_idmap(file), &file->f_path,
+				   length, ATTR_MTIME | ATTR_CTIME, file);
 }
 
 int ksys_ftruncate(unsigned int fd, loff_t length, unsigned int flags)
@@ -683,8 +684,8 @@ int chmod_common(const struct path *path, umode_t mode)
 		goto out_unlock;
 	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
 	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
-	error = notify_change(mnt_idmap(path->mnt), path->dentry,
-			      &newattrs, &delegated_inode);
+	error = notify_change(mnt_idmap(path->mnt), path, &newattrs,
+			      &delegated_inode);
 out_unlock:
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
@@ -822,7 +823,7 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
 		from_vfsuid(idmap, fs_userns, newattrs.ia_vfsuid),
 		from_vfsgid(idmap, fs_userns, newattrs.ia_vfsgid));
 	if (!error)
-		error = notify_change(idmap, path->dentry, &newattrs,
+		error = notify_change(idmap, path, &newattrs,
 				      &delegated_inode);
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 92da556598da..0d5699ffadea 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -208,7 +208,12 @@ static inline int ovl_do_notify_change(struct ovl_fs *ofs,
 				       struct dentry *upperdentry,
 				       struct iattr *attr)
 {
-	return notify_change(ovl_upper_mnt_idmap(ofs), upperdentry, attr, NULL);
+	struct path path = {
+		.mnt = ovl_upper_mnt(ofs),
+		.dentry = upperdentry,
+	};
+
+	return notify_change(ovl_upper_mnt_idmap(ofs), &path, attr, NULL);
 }
 
 static inline int ovl_do_rmdir(struct ovl_fs *ofs,
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index f5fe3cf8ba32..45275b8f23b6 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -8780,7 +8780,7 @@ static int set_file_basic_info(struct ksmbd_file *fp,
 			return -EACCES;
 
 		inode_lock(inode);
-		rc = notify_change(idmap, dentry, &attrs, NULL);
+		rc = notify_change(idmap, &filp->f_path, &attrs, NULL);
 		inode_unlock(inode);
 	}
 	return rc;
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 664f01263611..c79aa9863c85 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1749,7 +1749,7 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
 	}
 
 	inode_lock(inode);
-	rc = notify_change(idmap, path->dentry, &newattrs, NULL);
+	rc = notify_change(idmap, path, &newattrs, NULL);
 	inode_unlock(inode);
 	if (rc)
 		goto out;
diff --git a/fs/utimes.c b/fs/utimes.c
index e22664e4115f..5193d504d289 100644
--- a/fs/utimes.c
+++ b/fs/utimes.c
@@ -64,7 +64,7 @@ int vfs_utimes(const struct path *path, struct timespec64 *times)
 	}
 retry_deleg:
 	inode_lock(inode);
-	error = notify_change(mnt_idmap(path->mnt), path->dentry, &newattrs,
+	error = notify_change(mnt_idmap(path->mnt), path, &newattrs,
 			      &delegated_inode);
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3e240579e2b5..960334128b83 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2490,8 +2490,8 @@ static inline bool is_idmapped_mnt(const struct vfsmount *mnt)
 }
 
 int vfs_truncate(const struct path *, loff_t);
-int do_truncate(const struct mnt_idmap *, struct dentry *, loff_t start,
-		unsigned int time_attrs, struct file *filp);
+int do_truncate(const struct mnt_idmap *, const struct path *,
+		loff_t length, unsigned int time_attrs, struct file *filp);
 extern int vfs_fallocate(struct file *file, int mode, loff_t offset,
 			loff_t len);
 int do_sys_open(int dfd, const char __user *filename, int flags,
@@ -2714,7 +2714,9 @@ static inline int bmap(struct inode *inode,  sector_t *block)
 }
 #endif
 
-int notify_change(const struct mnt_idmap *, struct dentry *,
+int __notify_change(const struct mnt_idmap *, struct dentry *,
+		    struct iattr *, struct delegated_inode *);
+int notify_change(const struct mnt_idmap *, const struct path *,
 		  struct iattr *, struct delegated_inode *);
 int inode_permission(const struct mnt_idmap *, struct inode *, int);
 int generic_permission(const struct mnt_idmap *, struct inode *, int);
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 02/18] LSM: pass struct path to the inode_setsecctx hook
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 01/18] fs: pass struct path to notify_change() Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 03/18] fs: pass struct path to xattr helpers Cai Xinchen
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_setsecctx hook and its security_inode_setsecctx() caller
in fs/nfsd operate on a dentry even though both have a struct path
available.  The SELinux and Smack implementations write the label
through __vfs_setxattr_locked(), whose idmap argument they hardcode
to &nop_mnt_idmap.

Switch the hook to take a const struct path so that the upcoming
conversion of the xattr helpers to struct path can be reflected
directly in the implementations.  For now the implementations keep
passing &nop_mnt_idmap together with path->dentry to
__vfs_setxattr_locked(), preserving the current behavior; they will
be switched to pass the path itself once the xattr helpers take one.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/nfsd/vfs.c                 | 2 +-
 include/linux/lsm_hook_defs.h | 7 ++++++-
 include/linux/security.h      | 4 ++--
 security/security.c           | 6 +++---
 security/selinux/hooks.c      | 6 +++---
 security/smack/smack_lsm.c    | 6 +++---
 6 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 54440029c235..8d5abfbc71aa 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -626,7 +626,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
 			break;
 	}
 	if (attr->na_seclabel && attr->na_seclabel->len)
-		attr->na_labelerr = security_inode_setsecctx(dentry,
+		attr->na_labelerr = security_inode_setsecctx(&path,
 			attr->na_seclabel->data, attr->na_seclabel->len);
 	if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && attr->na_dpacl) {
 		if (!S_ISDIR(inode->i_mode))
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 87d9d06710cb..539525648b30 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -313,7 +313,12 @@ LSM_HOOK(int, 0, secctx_to_secid, const char *secdata, u32 seclen, u32 *secid)
 LSM_HOOK(void, LSM_RET_VOID, release_secctx, struct lsm_context *cp)
 LSM_HOOK(void, LSM_RET_VOID, inode_invalidate_secctx, struct inode *inode)
 LSM_HOOK(int, 0, inode_notifysecctx, struct inode *inode, void *ctx, u32 ctxlen)
-LSM_HOOK(int, 0, inode_setsecctx, struct dentry *dentry, void *ctx, u32 ctxlen)
+/*
+ * The @path argument is only sanctioned to be passed to VFS helpers
+ * taking a struct path; the VFS makes no guarantees with regard to
+ * @path->mnt.
+ */
+LSM_HOOK(int, 0, inode_setsecctx, const struct path *path, void *ctx, u32 ctxlen)
 LSM_HOOK(int, -EOPNOTSUPP, inode_getsecctx, struct inode *inode,
 	 struct lsm_context *cp)
 
diff --git a/include/linux/security.h b/include/linux/security.h
index 40831fab5470..225f822470e2 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -588,7 +588,7 @@ int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid);
 void security_release_secctx(struct lsm_context *cp);
 void security_inode_invalidate_secctx(struct inode *inode);
 int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
-int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
+int security_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen);
 int security_inode_getsecctx(struct inode *inode, struct lsm_context *cp);
 int security_locked_down(enum lockdown_reason what);
 int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
@@ -1630,7 +1630,7 @@ static inline int security_inode_notifysecctx(struct inode *inode, void *ctx, u3
 {
 	return -EOPNOTSUPP;
 }
-static inline int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen)
+static inline int security_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/security/security.c b/security/security.c
index f6583d507395..49cd3393c207 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4109,7 +4109,7 @@ EXPORT_SYMBOL(security_inode_notifysecctx);
 
 /**
  * security_inode_setsecctx() - Change the security label of an inode
- * @dentry: inode
+ * @path: inode (the VFS makes no guarantees with regard to @path->mnt)
  * @ctx: secctx
  * @ctxlen: length of secctx
  *
@@ -4123,9 +4123,9 @@ EXPORT_SYMBOL(security_inode_notifysecctx);
  *
  * Return: Returns 0 on success, error on failure.
  */
-int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen)
+int security_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
-	return call_int_hook(inode_setsecctx, dentry, ctx, ctxlen);
+	return call_int_hook(inode_setsecctx, path, ctx, ctxlen);
 }
 EXPORT_SYMBOL(security_inode_setsecctx);
 
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 50ed4d6b4607..9d7467fe73c5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7143,10 +7143,10 @@ static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen
 /*
  *	called with inode->i_mutex locked
  */
-static int selinux_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen)
+static int selinux_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
-	return __vfs_setxattr_locked(&nop_mnt_idmap, dentry, XATTR_NAME_SELINUX,
-				     ctx, ctxlen, 0, NULL);
+	return __vfs_setxattr_locked(&nop_mnt_idmap, path->dentry,
+				     XATTR_NAME_SELINUX, ctx, ctxlen, 0, NULL);
 }
 
 static int selinux_inode_getsecctx(struct inode *inode, struct lsm_context *cp)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 3524649f402d..953461571026 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4978,10 +4978,10 @@ static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen)
 				       ctxlen, 0);
 }
 
-static int smack_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen)
+static int smack_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
-	return __vfs_setxattr_locked(&nop_mnt_idmap, dentry, XATTR_NAME_SMACK,
-				     ctx, ctxlen, 0, NULL);
+	return __vfs_setxattr_locked(&nop_mnt_idmap, path->dentry,
+				     XATTR_NAME_SMACK, ctx, ctxlen, 0, NULL);
 }
 
 static int smack_inode_getsecctx(struct inode *inode, struct lsm_context *cp)
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 03/18] fs: pass struct path to xattr helpers
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 01/18] fs: pass struct path to notify_change() Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 02/18] LSM: pass struct path to the inode_setsecctx hook Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 04/18] fs: pass struct path to POSIX ACL helpers Cai Xinchen
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

vfs_setxattr(), vfs_getxattr(), vfs_listxattr() and vfs_removexattr()
along with their __vfs_setxattr_locked()/__vfs_removexattr_locked()
counterparts take a struct dentry even though their callers either
already hold a struct path (or a struct file).  Switch them all to
take a struct path instead of the dentry.

The struct mnt_idmap stays a separate argument: it is taken from the
mount at the time the path is checked and must be passed down
unchanged so that the VFS and the filesystem are guaranteed to see
the same idmap, so it cannot be derived from the path inside the
helpers.  The do_setxattr()/do_getxattr()/removexattr() syscall
helpers that resolve the path themselves read the idmap once at their
boundary from the file or path they resolved via
file_mnt_idmap()/mnt_idmap() and pass it down.

__vfs_setxattr_noperm(), __vfs_getxattr(), __vfs_setxattr(),
__vfs_removexattr() and vfs_getxattr_alloc() keep taking the idmap
and dentry as they are only called from contexts that operate on
bare dentries such as LSM internals.  The SELinux and Smack
inode_setsecctx implementations pass the path they got from the
previous patch together with &nop_mnt_idmap.

The ksmbd VFS helpers wrapping these calls get the same treatment:
they keep taking the idmap and now take a struct path instead of a
dentry where they did not yet, and their callers pass the idmap they
already hold.  ksmbd_vfs_fill_dentry_attrs() used to wrap the dentry
it was given into a synthetic path using the share root's mount.
Take a proper struct path from the callers instead so that the mount
always matches the dentry: the directory being enumerated supplies
the mount, and ksmbd_populate_dot_dotdot_entries() now starts from
the enumerated directory's path and swaps in the dentry it pins
with dget()/dget_parent().

The security_inode_*xattr() hooks invoked from fs/xattr.c keep
taking the idmap and dentry for now; they will be converted together
with the LSM hooks themselves in separate patches.

evm_calc_hmac_or_hash() contains a redundant xattr size sanity check
which calls vfs_getxattr() only to emit a debug message when the
kernel-side and user-space-side xattr sizes differ, without ever
influencing the result.  Because that function only has a dentry, the
call cannot be migrated to the new path-based signature; drop the
check and the now unused user_space_size variable instead.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 drivers/block/zloop.c               |  4 +-
 fs/cachefiles/xattr.c               | 35 +++++++------
 fs/ecryptfs/inode.c                 |  9 ++--
 fs/nfsd/nfs4ctl.h                   |  4 +-
 fs/nfsd/vfs.c                       | 57 +++++++++++---------
 fs/overlayfs/copy_up.c              |  4 +-
 fs/overlayfs/overlayfs.h            | 17 ++++--
 fs/overlayfs/xattrs.c               | 12 +++--
 fs/smb/server/smb2pdu.c             | 48 +++++++++--------
 fs/smb/server/smb_common.c          | 12 ++---
 fs/smb/server/smbacl.c              | 10 ++--
 fs/smb/server/vfs.c                 | 80 +++++++++++++----------------
 fs/smb/server/vfs.h                 | 16 +++---
 fs/xattr.c                          | 78 +++++++++++++++-------------
 include/linux/xattr.h               | 25 +++++----
 security/integrity/evm/evm_crypto.c |  8 +--
 security/selinux/hooks.c            |  4 +-
 security/smack/smack_lsm.c          |  4 +-
 18 files changed, 222 insertions(+), 205 deletions(-)

diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 9145e0f98d56..4cf502454100 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -718,7 +718,7 @@ static int zloop_record_safe_wps(struct zloop_device *zlo)
 
 		if (!zloop_zone_is_active(zone))
 			continue;
-		ret = vfs_setxattr(file_mnt_idmap(file), file_dentry(file),
+		ret = vfs_setxattr(file_mnt_idmap(file), &file->f_path,
 				"user.zloop.wp", &zone->wp, sizeof(zone->wp), 0);
 		if (ret) {
 			pr_err("%pg: failed to record write pointer (%d)\n",
@@ -1422,7 +1422,7 @@ static void zloop_forget_cache(struct zloop_device *zlo)
 		if (!zloop_zone_is_active(zone))
 			continue;
 
-		ret = vfs_getxattr(file_mnt_idmap(file), file_dentry(file),
+		ret = vfs_getxattr(file_mnt_idmap(file), &file->f_path,
 				"user.zloop.wp", &old_wp, sizeof(old_wp));
 		if (ret == -ENODATA) {
 			old_wp = 0;
diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c
index 8ebb713482e3..afdce5ce3b73 100644
--- a/fs/cachefiles/xattr.c
+++ b/fs/cachefiles/xattr.c
@@ -40,7 +40,6 @@ struct cachefiles_vol_xattr {
 int cachefiles_preset_object_xattr(struct cachefiles_object *object, struct file *file)
 {
 	struct cachefiles_xattr *buf;
-	struct dentry *dentry = file->f_path.dentry;
 	unsigned int len = object->cookie->aux_len;
 	int ret;
 
@@ -55,7 +54,7 @@ int cachefiles_preset_object_xattr(struct cachefiles_object *object, struct file
 	if (ret == 0) {
 		ret = mnt_want_write_file(file);
 		if (ret == 0) {
-			ret = vfs_setxattr(&nop_mnt_idmap, dentry,
+			ret = vfs_setxattr(&nop_mnt_idmap, &file->f_path,
 					   cachefiles_xattr_cache, buf,
 					   sizeof(struct cachefiles_xattr) + len, 0);
 			mnt_drop_write_file(file);
@@ -91,7 +90,6 @@ int cachefiles_preset_object_xattr(struct cachefiles_object *object, struct file
 int cachefiles_set_object_xattr(struct cachefiles_object *object)
 {
 	struct cachefiles_xattr *buf;
-	struct dentry *dentry;
 	struct file *file = object->file;
 	unsigned int len = object->cookie->aux_len;
 	uoff_t object_size = object->cookie->object_size;
@@ -99,7 +97,6 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object)
 
 	if (!file)
 		return -ESTALE;
-	dentry = file->f_path.dentry;
 
 	_enter("%x,#%d", object->debug_id, len);
 
@@ -121,7 +118,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object)
 	if (ret == 0) {
 		ret = mnt_want_write_file(file);
 		if (ret == 0) {
-			ret = vfs_setxattr(&nop_mnt_idmap, dentry,
+			ret = vfs_setxattr(&nop_mnt_idmap, &file->f_path,
 					   cachefiles_xattr_cache, buf,
 					   sizeof(struct cachefiles_xattr) + len, 0);
 			mnt_drop_write_file(file);
@@ -160,7 +157,6 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object)
 int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file)
 {
 	struct cachefiles_xattr *buf;
-	struct dentry *dentry = file->f_path.dentry;
 	struct inode *inode = file_inode(file);
 	unsigned int len = object->cookie->aux_len, tlen;
 	const void *p = fscache_get_aux(object->cookie);
@@ -177,7 +173,8 @@ int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file
 
 	xlen = cachefiles_inject_read_error();
 	if (xlen == 0)
-		xlen = vfs_getxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache, buf, tlen);
+		xlen = vfs_getxattr(&nop_mnt_idmap, &file->f_path,
+				    cachefiles_xattr_cache, buf, tlen);
 	if (xlen != tlen) {
 		if (xlen < 0) {
 			ret = xlen;
@@ -226,6 +223,7 @@ int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
 				   struct cachefiles_object *object,
 				   struct dentry *dentry)
 {
+	struct path path = { .mnt = cache->mnt, .dentry = dentry };
 	int ret;
 
 	trace_cachefiles_coherency(object, d_inode(dentry)->i_ino, 0, NULL, 0,
@@ -235,7 +233,7 @@ int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
 	if (ret == 0) {
 		ret = mnt_want_write(cache->mnt);
 		if (ret == 0) {
-			ret = vfs_removexattr(&nop_mnt_idmap, dentry,
+			ret = vfs_removexattr(&nop_mnt_idmap, &path,
 					      cachefiles_xattr_cache);
 			mnt_drop_write(cache->mnt);
 		}
@@ -282,7 +280,8 @@ bool cachefiles_set_volume_xattr(struct cachefiles_volume *volume)
 	struct cachefiles_vol_xattr *buf;
 	unsigned int len = volume->vcookie->coherency_len;
 	const void *p = volume->vcookie->coherency;
-	struct dentry *dentry = volume->dentry;
+	struct path path = { .mnt = volume->cache->mnt,
+			     .dentry = volume->dentry };
 	int ret;
 
 	_enter("%x,#%d", volume->vcookie->debug_id, len);
@@ -298,22 +297,22 @@ bool cachefiles_set_volume_xattr(struct cachefiles_volume *volume)
 	if (ret == 0) {
 		ret = mnt_want_write(volume->cache->mnt);
 		if (ret == 0) {
-			ret = vfs_setxattr(&nop_mnt_idmap, dentry,
+			ret = vfs_setxattr(&nop_mnt_idmap, &path,
 					   cachefiles_xattr_cache,
 					   buf, len, 0);
 			mnt_drop_write(volume->cache->mnt);
 		}
 	}
 	if (ret < 0) {
-		trace_cachefiles_vfs_error(NULL, d_inode(dentry), ret,
+		trace_cachefiles_vfs_error(NULL, d_inode(path.dentry), ret,
 					   cachefiles_trace_setxattr_error);
-		trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino,
+		trace_cachefiles_vol_coherency(volume, d_inode(path.dentry)->i_ino,
 					       cachefiles_coherency_vol_set_fail);
 		if (ret != -ENOMEM)
 			cachefiles_io_error(
 				volume->cache, "Failed to set xattr with error %d", ret);
 	} else {
-		trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino,
+		trace_cachefiles_vol_coherency(volume, d_inode(path.dentry)->i_ino,
 					       cachefiles_coherency_vol_set_ok);
 	}
 
@@ -328,9 +327,10 @@ bool cachefiles_set_volume_xattr(struct cachefiles_volume *volume)
 int cachefiles_check_volume_xattr(struct cachefiles_volume *volume)
 {
 	struct cachefiles_vol_xattr *buf;
-	struct dentry *dentry = volume->dentry;
 	unsigned int len = volume->vcookie->coherency_len;
 	const void *p = volume->vcookie->coherency;
+	struct path path = { .mnt = volume->cache->mnt,
+			     .dentry = volume->dentry };
 	enum cachefiles_coherency_trace why;
 	ssize_t xlen;
 	int ret = -ESTALE;
@@ -344,11 +344,12 @@ int cachefiles_check_volume_xattr(struct cachefiles_volume *volume)
 
 	xlen = cachefiles_inject_read_error();
 	if (xlen == 0)
-		xlen = vfs_getxattr(&nop_mnt_idmap, dentry, cachefiles_xattr_cache, buf, len);
+		xlen = vfs_getxattr(&nop_mnt_idmap, &path,
+				    cachefiles_xattr_cache, buf, len);
 	if (xlen != len) {
 		if (xlen < 0) {
 			ret = xlen;
-			trace_cachefiles_vfs_error(NULL, d_inode(dentry), xlen,
+			trace_cachefiles_vfs_error(NULL, d_inode(path.dentry), xlen,
 						   cachefiles_trace_getxattr_error);
 			if (xlen == -EIO)
 				cachefiles_io_error(
@@ -365,7 +366,7 @@ int cachefiles_check_volume_xattr(struct cachefiles_volume *volume)
 		ret = 0;
 	}
 
-	trace_cachefiles_vol_coherency(volume, d_inode(dentry)->i_ino, why);
+	trace_cachefiles_vol_coherency(volume, d_inode(path.dentry)->i_ino, why);
 	kfree(buf);
 	_leave(" = %d", ret);
 	return ret;
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 26967e4e4660..c1be27e1f7eb 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -992,17 +992,18 @@ ecryptfs_setxattr(struct dentry *dentry, struct inode *inode,
 		  size_t size, int flags)
 {
 	int rc;
-	struct dentry *lower_dentry;
+	struct path lower_path;
 	struct inode *lower_inode;
 
-	lower_dentry = ecryptfs_dentry_to_lower(dentry);
-	lower_inode = d_inode(lower_dentry);
+	lower_path = ecryptfs_lower_path(dentry);
+	lower_inode = d_inode(lower_path.dentry);
 	if (!(lower_inode->i_opflags & IOP_XATTR)) {
 		rc = -EOPNOTSUPP;
 		goto out;
 	}
 	inode_lock(lower_inode);
-	rc = __vfs_setxattr_locked(&nop_mnt_idmap, lower_dentry, name, value, size, flags, NULL);
+	rc = __vfs_setxattr_locked(&nop_mnt_idmap, &lower_path, name, value,
+				   size, flags, NULL);
 	inode_unlock(lower_inode);
 	if (!rc && inode)
 		fsstack_copy_attr_all(inode, lower_inode);
diff --git a/fs/nfsd/nfs4ctl.h b/fs/nfsd/nfs4ctl.h
index bcec4c4ef1d5..97ab3d4212aa 100644
--- a/fs/nfsd/nfs4ctl.h
+++ b/fs/nfsd/nfs4ctl.h
@@ -38,7 +38,7 @@ int nfsd4_create_laundry_wq(void);
 void nfsd4_destroy_laundry_wq(void);
 bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode);
 
-extern int nfsd4_is_junction(struct dentry *dentry);
+extern int nfsd4_is_junction(const struct path *path);
 extern int register_cld_notifier(void);
 extern void unregister_cld_notifier(void);
 #ifdef CONFIG_NFSD_V4_2_INTER_SSC
@@ -68,7 +68,7 @@ static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp,
 	return false;
 }
 
-static inline int nfsd4_is_junction(struct dentry *dentry)
+static inline int nfsd4_is_junction(const struct path *path)
 {
 	return 0;
 }
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 8d5abfbc71aa..397f417e77b3 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -233,11 +233,13 @@ static int nfsd_lookup_parent(struct svc_rqst *rqstp, struct dentry *dparent, st
  */
 int nfsd_mountpoint(struct dentry *dentry, struct svc_export *exp)
 {
+	struct path path = { .mnt = exp->ex_path.mnt, .dentry = dentry };
+
 	if (!d_inode(dentry))
 		return 0;
 	if (exp->ex_flags & NFSEXP_V4ROOT)
 		return 1;
-	if (nfsd4_is_junction(dentry))
+	if (nfsd4_is_junction(&path))
 		return 1;
 	if (d_managed(dentry))
 		/*
@@ -685,14 +687,14 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
 /**
  * nfsd4_is_junction - Test if an object could be an NFS junction
  *
- * @dentry: object to test
+ * @path: object to test
  *
- * Returns 1 if "dentry" appears to contain NFS junction information.
+ * Returns 1 if "path" appears to contain NFS junction information.
  * Otherwise 0 is returned.
  */
-int nfsd4_is_junction(struct dentry *dentry)
+int nfsd4_is_junction(const struct path *path)
 {
-	struct inode *inode = d_inode(dentry);
+	struct inode *inode = d_inode(path->dentry);
 
 	if (inode == NULL)
 		return 0;
@@ -700,7 +702,7 @@ int nfsd4_is_junction(struct dentry *dentry)
 		return 0;
 	if (!(inode->i_mode & S_ISVTX))
 		return 0;
-	if (vfs_getxattr(&nop_mnt_idmap, dentry, NFSD_JUNCTION_XATTR_NAME,
+	if (vfs_getxattr(&nop_mnt_idmap, path, NFSD_JUNCTION_XATTR_NAME,
 			 NULL, 0) <= 0)
 		return 0;
 	return 1;
@@ -2653,19 +2655,20 @@ nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 	__be32 err;
 	char *buf;
 	struct inode *inode;
-	struct dentry *dentry;
+	struct path path;
 
 	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_READ);
 	if (err)
 		return err;
 
 	err = nfs_ok;
-	dentry = fhp->fh_dentry;
-	inode = d_inode(dentry);
+	path.mnt = fhp->fh_export->ex_path.mnt;
+	path.dentry = fhp->fh_dentry;
+	inode = d_inode(path.dentry);
 
 	inode_lock_shared(inode);
 
-	len = vfs_getxattr(&nop_mnt_idmap, dentry, name, NULL, 0);
+	len = vfs_getxattr(&nop_mnt_idmap, &path, name, NULL, 0);
 
 	/*
 	 * Zero-length attribute, just return.
@@ -2692,7 +2695,7 @@ nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 		goto out;
 	}
 
-	len = vfs_getxattr(&nop_mnt_idmap, dentry, name, buf, len);
+	len = vfs_getxattr(&nop_mnt_idmap, &path, name, buf, len);
 	if (len <= 0) {
 		kvfree(buf);
 		buf = NULL;
@@ -2726,19 +2729,20 @@ nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char **bufp,
 	__be32 err;
 	char *buf;
 	struct inode *inode;
-	struct dentry *dentry;
+	struct path path;
 
 	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_READ);
 	if (err)
 		return err;
 
-	dentry = fhp->fh_dentry;
-	inode = d_inode(dentry);
+	path.mnt = fhp->fh_export->ex_path.mnt;
+	path.dentry = fhp->fh_dentry;
+	inode = d_inode(path.dentry);
 	*lenp = 0;
 
 	inode_lock_shared(inode);
 
-	len = vfs_listxattr(dentry, NULL, 0);
+	len = vfs_listxattr(&path, NULL, 0);
 	if (len <= 0) {
 		err = nfsd_xattr_errno(len);
 		goto out;
@@ -2755,7 +2759,7 @@ nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char **bufp,
 		goto out;
 	}
 
-	len = vfs_listxattr(dentry, buf, len);
+	len = vfs_listxattr(&path, buf, len);
 	if (len <= 0) {
 		kvfree(buf);
 		err = nfsd_xattr_errno(len);
@@ -2788,6 +2792,7 @@ nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name)
 {
 	__be32 err;
 	int ret;
+	struct path path;
 
 	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_WRITE);
 	if (err)
@@ -2797,16 +2802,17 @@ nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name)
 	if (ret)
 		return nfserrno(ret);
 
-	inode_lock(fhp->fh_dentry->d_inode);
+	path.mnt = fhp->fh_export->ex_path.mnt;
+	path.dentry = fhp->fh_dentry;
+	inode_lock(d_inode(path.dentry));
 	err = fh_fill_pre_attrs(fhp);
 	if (err != nfs_ok)
 		goto out_unlock;
-	ret = __vfs_removexattr_locked(&nop_mnt_idmap, fhp->fh_dentry,
-				       name, NULL);
+	ret = __vfs_removexattr_locked(&nop_mnt_idmap, &path, name, NULL);
 	err = nfsd_xattr_errno(ret);
 	fh_fill_post_attrs(fhp);
 out_unlock:
-	inode_unlock(fhp->fh_dentry->d_inode);
+	inode_unlock(d_inode(path.dentry));
 	fh_drop_write(fhp);
 
 	return err;
@@ -2818,6 +2824,7 @@ nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 {
 	__be32 err;
 	int ret;
+	struct path path;
 
 	err = fh_verify(rqstp, fhp, 0, NFSD_MAY_WRITE);
 	if (err)
@@ -2826,16 +2833,18 @@ nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 	ret = fh_want_write(fhp);
 	if (ret)
 		return nfserrno(ret);
-	inode_lock(fhp->fh_dentry->d_inode);
+	path.mnt = fhp->fh_export->ex_path.mnt;
+	path.dentry = fhp->fh_dentry;
+	inode_lock(d_inode(path.dentry));
 	err = fh_fill_pre_attrs(fhp);
 	if (err != nfs_ok)
 		goto out_unlock;
-	ret = __vfs_setxattr_locked(&nop_mnt_idmap, fhp->fh_dentry,
-				    name, buf, len, flags, NULL);
+	ret = __vfs_setxattr_locked(&nop_mnt_idmap, &path, name, buf, len,
+				    flags, NULL);
 	fh_fill_post_attrs(fhp);
 	err = nfsd_xattr_errno(ret);
 out_unlock:
-	inode_unlock(fhp->fh_dentry->d_inode);
+	inode_unlock(d_inode(path.dentry));
 	fh_drop_write(fhp);
 	return err;
 }
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index e963701b4c87..6dc241f195a6 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -83,7 +83,7 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
 	if (!old->d_inode->i_op->listxattr || !new->d_inode->i_op->listxattr)
 		return 0;
 
-	list_size = vfs_listxattr(old, NULL, 0);
+	list_size = vfs_listxattr(oldpath, NULL, 0);
 	if (list_size <= 0) {
 		if (list_size == -EOPNOTSUPP)
 			return 0;
@@ -94,7 +94,7 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
 	if (!buf)
 		return -ENOMEM;
 
-	list_size = vfs_listxattr(old, buf, list_size);
+	list_size = vfs_listxattr(oldpath, buf, list_size);
 	if (list_size <= 0) {
 		error = list_size;
 		goto out;
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 0d5699ffadea..bf3fa54ee27a 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -295,8 +295,7 @@ static inline ssize_t ovl_do_getxattr(const struct path *path, const char *name,
 
 	WARN_ON(path->dentry->d_sb != path->mnt->mnt_sb);
 
-	err = vfs_getxattr(mnt_idmap(path->mnt), path->dentry,
-			       name, value, size);
+	err = vfs_getxattr(mnt_idmap(path->mnt), path, name, value, size);
 	len = (value && err > 0) ? err : 0;
 
 	pr_debug("getxattr(%pd2, \"%s\", \"%*pE\", %zu, 0) = %i\n",
@@ -329,8 +328,13 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry,
 				  const char *name, const void *value,
 				  size_t size, int flags)
 {
+	struct path path = {
+		.mnt = ovl_upper_mnt(ofs),
+		.dentry = dentry,
+	};
+
 	/* Use vfs_setxattr(), not __vfs_setxattr(): it idmaps the security.capability rootid. */
-	int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), dentry, name,
+	int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), &path, name,
 			       value, size, flags);
 
 	pr_debug("setxattr(%pd2, \"%s\", \"%*pE\", %zu, %d) = %i\n",
@@ -348,7 +352,12 @@ static inline int ovl_setxattr(struct ovl_fs *ofs, struct dentry *dentry,
 static inline int ovl_do_removexattr(struct ovl_fs *ofs, struct dentry *dentry,
 				     const char *name)
 {
-	int err = vfs_removexattr(ovl_upper_mnt_idmap(ofs), dentry, name);
+	struct path path = {
+		.mnt = ovl_upper_mnt(ofs),
+		.dentry = dentry,
+	};
+
+	int err = vfs_removexattr(ovl_upper_mnt_idmap(ofs), &path, name);
 	pr_debug("removexattr(%pd2, \"%s\") = %i\n", dentry, name, err);
 	return err;
 }
diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c
index acc54f6138ef..2a97a9ec642a 100644
--- a/fs/overlayfs/xattrs.c
+++ b/fs/overlayfs/xattrs.c
@@ -45,7 +45,8 @@ static int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char
 	if (!value && !upperdentry) {
 		ovl_path_lower(dentry, &realpath);
 		with_ovl_creds(dentry->d_sb)
-			err = vfs_getxattr(mnt_idmap(realpath.mnt), realdentry, name, NULL, 0);
+			err = vfs_getxattr(mnt_idmap(realpath.mnt), &realpath,
+					   name, NULL, 0);
 		if (err < 0)
 			goto out;
 	}
@@ -86,7 +87,8 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char
 	ovl_i_path_real(inode, &realpath);
 	/* Use vfs_getxattr(), not __vfs_getxattr(): it idmaps the security.capability rootid. */
 	with_ovl_creds(dentry->d_sb)
-		return vfs_getxattr(mnt_idmap(realpath.mnt), realpath.dentry, name, value, size);
+		return vfs_getxattr(mnt_idmap(realpath.mnt), &realpath,
+				    name, value, size);
 }
 
 static bool ovl_can_list(struct super_block *sb, const char *s)
@@ -105,15 +107,17 @@ static bool ovl_can_list(struct super_block *sb, const char *s)
 
 ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)
 {
-	struct dentry *realdentry = ovl_dentry_real(dentry);
+	struct path realpath;
 	struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
 	ssize_t res;
 	size_t len;
 	char *s;
 	size_t prefix_len, name_len;
 
+	ovl_path_real(dentry, &realpath);
+
 	with_ovl_creds(dentry->d_sb)
-		res = vfs_listxattr(realdentry, list, size);
+		res = vfs_listxattr(&realpath, list, size);
 	if (res <= 0 || size == 0)
 		return res;
 
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 45275b8f23b6..07b0a71a4353 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3442,16 +3442,14 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 		value = (char *)&eabuf->name + eabuf->EaNameLength + 1;
 
 		if (!eabuf->EaValueLength) {
-			rc = ksmbd_vfs_casexattr_len(idmap,
-						     path->dentry,
+			rc = ksmbd_vfs_casexattr_len(idmap, path,
 						     attr_name,
 						     XATTR_USER_PREFIX_LEN +
 						     eabuf->EaNameLength, NULL);
 
 			/* delete the EA only when it exits */
 			if (rc > 0) {
-				rc = ksmbd_vfs_remove_xattr(idmap,
-							    path,
+				rc = ksmbd_vfs_remove_xattr(idmap, path,
 							    attr_name,
 							    get_write);
 
@@ -3519,8 +3517,7 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
 	fp->stream.size = xattr_stream_size;
 
 	/* Keep the existing xattr's case for subsequent writes and removal. */
-	rc = ksmbd_vfs_casexattr_len(idmap,
-				     path->dentry,
+	rc = ksmbd_vfs_casexattr_len(idmap, path,
 				     xattr_stream_name,
 				     xattr_stream_size, xattr_stream_name);
 	if (rc >= 0)
@@ -3574,7 +3571,7 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
 static loff_t ksmbd_stream_eof(struct ksmbd_file *fp)
 {
 	ssize_t slen = ksmbd_vfs_casexattr_len(file_mnt_idmap(fp->filp),
-					       fp->filp->f_path.dentry,
+					       &fp->filp->f_path,
 					       fp->stream.name,
 					       fp->stream.size, NULL);
 	return slen < 0 ? 0 : (loff_t)slen;
@@ -3587,7 +3584,7 @@ static int smb2_remove_smb_xattrs(const struct path *path)
 	ssize_t xattr_list_len;
 	int err = 0;
 
-	xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+	xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (xattr_list_len < 0) {
 		goto out;
 	} else if (!xattr_list_len) {
@@ -3602,8 +3599,7 @@ static int smb2_remove_smb_xattrs(const struct path *path)
 		if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
 		    !strncmp(&name[XATTR_USER_PREFIX_LEN], STREAM_PREFIX,
 			     STREAM_PREFIX_LEN)) {
-			err = ksmbd_vfs_remove_xattr(idmap, path,
-						     name, true);
+			err = ksmbd_vfs_remove_xattr(idmap, path, name, true);
 			if (err)
 				ksmbd_debug(SMB, "remove xattr failed : %s\n",
 					    name);
@@ -3658,6 +3654,7 @@ static bool smb2_parent_compressed(struct ksmbd_tree_connect *tcon,
 				   const struct path *path)
 {
 	struct dentry *parent = dget_parent(path->dentry);
+	struct path parent_path = { .mnt = path->mnt, .dentry = parent };
 	struct file_kattr fa = { .flags_valid = true };
 	struct xattr_dos_attrib da;
 	bool compressed = false;
@@ -3669,7 +3666,8 @@ static bool smb2_parent_compressed(struct ksmbd_tree_connect *tcon,
 		goto out;
 	}
 
-	rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt), parent, &da);
+	rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt),
+					    &parent_path, &da);
 	if (rc > 0 && da.attr & FILE_ATTRIBUTE_COMPRESSED)
 		compressed = true;
 
@@ -3690,7 +3688,7 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
 
 	/* get FileAttributes from XATTR_NAME_DOS_ATTRIBUTE */
 	rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt),
-					    path->dentry, &da);
+					    path, &da);
 	if (rc > 0) {
 		if (store_dos_attrs) {
 			fp->f_ci->m_fattr = cpu_to_le32(da.attr);
@@ -4717,7 +4715,7 @@ int smb2_open(struct ksmbd_work *work)
 			if (test_share_config_flag(tcon->share_conf,
 						   KSMBD_SHARE_FLAG_STORE_DOS_ATTRS) &&
 			    ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path.mnt),
-							 path.dentry, &da) > 0 &&
+							   &path, &da) > 0 &&
 			    da.attr & FILE_ATTRIBUTE_READONLY) {
 				rsp->hdr.Status = STATUS_CANNOT_DELETE;
 				rc = -EACCES;
@@ -6261,13 +6259,14 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
 			continue;
 		}
 
+		path.mnt = priv->dir_fp->filp->f_path.mnt;
+		path.dentry = dent;
+
 		if (test_share_config_flag(priv->work->tcon->share_conf,
 					   KSMBD_SHARE_FLAG_HIDE_UNREADABLE)) {
 			__le32 daccess = FILE_READ_DATA_LE | FILE_READ_EA_LE |
 				FILE_READ_ATTRIBUTES_LE;
 
-			path.mnt = priv->dir_fp->filp->f_path.mnt;
-			path.dentry = dent;
 			rc = smb_check_perm_dacl(priv->work->conn, &path,
 						 &daccess, daccess,
 						 priv->work->sess->user->uid,
@@ -6282,7 +6281,7 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
 		if (priv->info_level != FILE_NAMES_INFORMATION) {
 			rc = ksmbd_vfs_fill_dentry_attrs(priv->work,
 							 idmap,
-							 dent,
+							 &path,
 							 &ksmbd_kstat);
 			if (rc) {
 				dput(dent);
@@ -6926,7 +6925,7 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 	if (buf_free_len < 0)
 		return -EINVAL;
 
-	rc = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+	rc = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (rc < 0) {
 		rsp->hdr.Status = STATUS_INVALID_HANDLE;
 		goto out;
@@ -6972,8 +6971,7 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 		buf_free_len -= (offsetof(struct smb2_ea_info, name) +
 				name_len + 1);
 		/* bailout if xattr can't fit in buf_free_len */
-		value_len = ksmbd_vfs_getxattr(idmap, path->dentry,
-					       name, &buf);
+		value_len = ksmbd_vfs_getxattr(idmap, path, name, &buf);
 		if (value_len <= 0) {
 			rc = -ENOENT;
 			rsp->hdr.Status = STATUS_INVALID_HANDLE;
@@ -7263,7 +7261,7 @@ static char *smb2_get_normalized_stream_name(struct ksmbd_file *fp)
 	if (!ksmbd_stream_fd(fp))
 		return NULL;
 
-	xattr_list_len = ksmbd_vfs_listxattr(fp->filp->f_path.dentry,
+	xattr_list_len = ksmbd_vfs_listxattr(&fp->filp->f_path,
 					     &xattr_list);
 	if (xattr_list_len <= 0)
 		goto out;
@@ -7366,7 +7364,7 @@ static int get_file_stream_info(struct ksmbd_work *work,
 	if (buf_free_len < 0)
 		goto out;
 
-	xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+	xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (xattr_list_len < 0) {
 		goto out;
 	} else if (!xattr_list_len) {
@@ -7413,7 +7411,7 @@ static int get_file_stream_info(struct ksmbd_work *work,
 		kfree(stream_buf);
 		file_info->StreamNameLength = cpu_to_le32(streamlen);
 		slen = ksmbd_vfs_xattr_len(file_mnt_idmap(fp->filp),
-					   path->dentry, stream_name);
+					   path, stream_name);
 		ssize = slen < 0 ? 0 : (loff_t)slen;
 		file_info->StreamSize = cpu_to_le64(ssize);
 		file_info->StreamAllocationSize = cpu_to_le64(ssize);
@@ -8222,7 +8220,7 @@ static int smb2_get_info_sec(struct ksmbd_work *work,
 	if (test_share_config_flag(work->tcon->share_conf,
 				   KSMBD_SHARE_FLAG_ACL_XATTR))
 		ppntsd_size = ksmbd_vfs_get_sd_xattr(work->conn, idmap,
-						     fp->filp->f_path.dentry,
+						     &fp->filp->f_path,
 						     &ppntsd);
 
 	/* Check if sd buffer size exceeds response buffer size */
@@ -8584,7 +8582,7 @@ static int smb2_rename(struct ksmbd_work *work,
 			}
 
 			stream_buf_len = ksmbd_vfs_getcasexattr(file_mnt_idmap(fp->filp),
-								fp->filp->f_path.dentry,
+								&fp->filp->f_path,
 								fp->stream.name,
 								fp->stream.size,
 								&stream_buf);
@@ -11471,7 +11469,7 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 		struct xattr_dos_attrib da = {0};
 
 		ret = ksmbd_vfs_get_dos_attrib_xattr(idmap,
-						     fp->filp->f_path.dentry, &da);
+						     &fp->filp->f_path, &da);
 		if (ret <= 0) {
 			da.version = 4;
 			da.itime = fp->itime;
diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c
index f9fb3741ada3..86ea69068fd3 100644
--- a/fs/smb/server/smb_common.c
+++ b/fs/smb/server/smb_common.c
@@ -472,32 +472,32 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
 	for (i = 0; i < 2; i++) {
 		struct kstat kstat;
 		struct ksmbd_kstat ksmbd_kstat;
-		struct dentry *dentry;
+		struct path path = dir->filp->f_path;
 
 		if (!dir->dot_dotdot[i]) { /* fill dot entry info */
 			if (i == 0) {
 				d_info->name = ".";
 				d_info->name_len = 1;
-				dentry = dget(dir->filp->f_path.dentry);
+				path.dentry = dget(path.dentry);
 			} else {
 				d_info->name = "..";
 				d_info->name_len = 2;
-				dentry = dget_parent(dir->filp->f_path.dentry);
+				path.dentry = dget_parent(path.dentry);
 			}
 
 			if (!match_pattern(d_info->name, d_info->name_len,
 					   search_pattern)) {
 				dir->dot_dotdot[i] = 1;
-				dput(dentry);
+				dput(path.dentry);
 				continue;
 			}
 
 			ksmbd_kstat.kstat = &kstat;
 			rc = ksmbd_vfs_fill_dentry_attrs(work,
 							 idmap,
-							 dentry,
+							 &path,
 							 &ksmbd_kstat);
-			dput(dentry);
+			dput(path.dentry);
 			if (rc)
 				break;
 
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index c79aa9863c85..94bf83d07452 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1207,7 +1207,8 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
 	struct smb_acl *parent_pdacl;
 	struct smb_ntsd *parent_pntsd = NULL;
 	struct smb_sid owner_sid, group_sid;
-	struct dentry *parent = path->dentry->d_parent;
+	struct path parent_path = { .mnt = path->mnt,
+				    .dentry = path->dentry->d_parent };
 	const struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	int inherited_flags = 0, flags = 0, i, nt_size = 0, pdacl_size;
 	int rc = 0, pntsd_type, ppntsd_size, acl_len, aces_size;
@@ -1217,8 +1218,8 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
 	char *aces_base;
 	bool is_dir = S_ISDIR(d_inode(path->dentry)->i_mode);
 
-	ppntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap,
-					    parent, &parent_pntsd);
+	ppntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap, &parent_path,
+					    &parent_pntsd);
 	if (ppntsd_size <= 0)
 		return -ENOENT;
 
@@ -1484,8 +1485,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
 	vfsuid_t vfsuid;
 
 	ksmbd_debug(SMB, "check permission using windows acl\n");
-	pntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap,
-					    path->dentry, &pntsd);
+	pntsd_size = ksmbd_vfs_get_sd_xattr(conn, idmap, path, &pntsd);
 	if (pntsd_size <= 0 || !pntsd)
 		goto err_out;
 
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 4ee72eda9bc0..96aca3e659e9 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -223,13 +223,13 @@ int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode)
 }
 
 ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap,
-			       struct dentry *dentry, char *attr_name,
+			       const struct path *path, char *attr_name,
 			       int attr_name_len, char **attr_value)
 {
 	char *name, *xattr_list = NULL;
 	ssize_t value_len = -ENOENT, xattr_list_len;
 
-	xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list);
+	xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (xattr_list_len <= 0)
 		goto out;
 
@@ -239,8 +239,7 @@ ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap,
 		if (strncasecmp(attr_name, name, attr_name_len))
 			continue;
 
-		value_len = ksmbd_vfs_getxattr(idmap,
-					       dentry,
+		value_len = ksmbd_vfs_getxattr(idmap, path,
 					       name,
 					       attr_value);
 		if (value_len < 0)
@@ -265,7 +264,7 @@ static int ksmbd_vfs_stream_read(struct ksmbd_file *fp, char *buf, loff_t *pos,
 
 	saved_cred = override_creds(fp->filp->f_cred);
 	v_len = ksmbd_vfs_getcasexattr(file_mnt_idmap(fp->filp),
-				       fp->filp->f_path.dentry,
+				       &fp->filp->f_path,
 				       fp->stream.name,
 				       fp->stream.size,
 				       &stream_buf);
@@ -410,8 +409,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 	write_end = *pos + count;
 
 	saved_cred = override_creds(fp->filp->f_cred);
-	v_len = ksmbd_vfs_getcasexattr(idmap,
-				       fp->filp->f_path.dentry,
+	v_len = ksmbd_vfs_getcasexattr(idmap, &fp->filp->f_path,
 				       fp->stream.name,
 				       fp->stream.size,
 				       &stream_buf);
@@ -438,8 +436,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 
 	memcpy(&stream_buf[*pos], buf, count);
 
-	err = ksmbd_vfs_setxattr(idmap,
-				 &fp->filp->f_path,
+	err = ksmbd_vfs_setxattr(idmap, &fp->filp->f_path,
 				 fp->stream.name,
 				 (void *)stream_buf,
 				 size,
@@ -480,7 +477,7 @@ int ksmbd_vfs_stream_truncate(struct ksmbd_file *fp, loff_t newsize)
 		return -EFBIG;
 
 	saved_cred = override_creds(fp->filp->f_cred);
-	v_len = ksmbd_vfs_getcasexattr(idmap, fp->filp->f_path.dentry,
+	v_len = ksmbd_vfs_getcasexattr(idmap, &fp->filp->f_path,
 				       fp->stream.name, fp->stream.size,
 				       &stream_buf);
 	if (v_len < 0) {
@@ -873,17 +870,17 @@ int ksmbd_vfs_truncate(struct ksmbd_work *work,
 
 /**
  * ksmbd_vfs_listxattr() - vfs helper for smb list extended attributes
- * @dentry:	dentry of file for listing xattrs
+ * @path:	path of file for listing xattrs
  * @list:	destination buffer
  *
  * Return:	xattr list length on success, otherwise error
  */
-ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list)
+ssize_t ksmbd_vfs_listxattr(const struct path *path, char **list)
 {
 	ssize_t size;
 	char *vlist = NULL;
 
-	size = vfs_listxattr(dentry, NULL, 0);
+	size = vfs_listxattr(path, NULL, 0);
 	if (size <= 0)
 		return size;
 
@@ -892,7 +889,7 @@ ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list)
 		return -ENOMEM;
 
 	*list = vlist;
-	size = vfs_listxattr(dentry, vlist, size);
+	size = vfs_listxattr(path, vlist, size);
 	if (size < 0) {
 		ksmbd_debug(VFS, "listxattr failed\n");
 		kvfree(vlist);
@@ -903,29 +900,29 @@ ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list)
 }
 
 ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap,
-			    struct dentry *dentry, char *xattr_name)
+			    const struct path *path, char *xattr_name)
 {
-	return vfs_getxattr(idmap, dentry, xattr_name, NULL, 0);
+	return vfs_getxattr(idmap, path, xattr_name, NULL, 0);
 }
 
 /**
  * ksmbd_vfs_getxattr() - vfs helper for smb get extended attributes value
  * @idmap:	idmap
- * @dentry:	dentry of file for getting xattrs
+ * @path:	path of file for getting xattrs
  * @xattr_name:	name of xattr name to query
  * @xattr_buf:	destination buffer xattr value
  *
  * Return:	read xattr value length on success, otherwise error
  */
 ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+			   const struct path *path,
 			   char *xattr_name, char **xattr_buf)
 {
 	ssize_t xattr_len;
 	char *buf;
 
 	*xattr_buf = NULL;
-	xattr_len = ksmbd_vfs_xattr_len(idmap, dentry, xattr_name);
+	xattr_len = ksmbd_vfs_xattr_len(idmap, path, xattr_name);
 	if (xattr_len < 0)
 		return xattr_len;
 
@@ -933,7 +930,7 @@ ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap,
 	if (!buf)
 		return -ENOMEM;
 
-	xattr_len = vfs_getxattr(idmap, dentry, xattr_name,
+	xattr_len = vfs_getxattr(idmap, path, xattr_name,
 				 (void *)buf, xattr_len);
 	if (xattr_len > 0)
 		*xattr_buf = buf;
@@ -967,8 +964,7 @@ int ksmbd_vfs_setxattr(const struct mnt_idmap *idmap,
 			return err;
 	}
 
-	err = vfs_setxattr(idmap,
-			   path->dentry,
+	err = vfs_setxattr(idmap, path,
 			   attr_name,
 			   attr_value,
 			   attr_size,
@@ -1259,7 +1255,7 @@ int ksmbd_vfs_remove_xattr(const struct mnt_idmap *idmap,
 			return err;
 	}
 
-	err = vfs_removexattr(idmap, path->dentry, attr_name);
+	err = vfs_removexattr(idmap, path, attr_name);
 
 	if (get_write == true)
 		mnt_drop_write(path->mnt);
@@ -1548,7 +1544,7 @@ int ksmbd_vfs_remove_acl_xattrs(const struct mnt_idmap *idmap,
 	ssize_t xattr_list_len;
 	int err = 0;
 
-	xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+	xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (xattr_list_len < 0) {
 		goto out;
 	} else if (!xattr_list_len) {
@@ -1587,7 +1583,7 @@ int ksmbd_vfs_remove_sd_xattrs(const struct mnt_idmap *idmap, const struct path
 	ssize_t xattr_list_len;
 	int err = 0;
 
-	xattr_list_len = ksmbd_vfs_listxattr(path->dentry, &xattr_list);
+	xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (xattr_list_len < 0) {
 		goto out;
 	} else if (!xattr_list_len) {
@@ -1745,18 +1741,18 @@ EXPORT_SYMBOL_IF_KUNIT(ksmbd_vfs_set_sd_xattr);
 
 int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
 			   const struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+			   const struct path *path,
 			   struct smb_ntsd **pntsd)
 {
 	int rc;
 	struct ndr n;
-	struct inode *inode = d_inode(dentry);
+	struct inode *inode = d_inode(path->dentry);
 	struct ndr acl_ndr = {0};
 	struct xattr_ntacl acl = {0};
 	struct xattr_smb_acl *smb_acl = NULL, *def_smb_acl = NULL;
 	__u8 cmp_hash[XATTR_SD_HASH_SIZE] = {0};
 
-	rc = ksmbd_vfs_getxattr(idmap, dentry, XATTR_NAME_SD, &n.data);
+	rc = ksmbd_vfs_getxattr(idmap, path, XATTR_NAME_SD, &n.data);
 	if (rc <= 0)
 		return rc;
 
@@ -1836,13 +1832,13 @@ int ksmbd_vfs_set_dos_attrib_xattr(const struct mnt_idmap *idmap,
 }
 
 int ksmbd_vfs_get_dos_attrib_xattr(const struct mnt_idmap *idmap,
-				   struct dentry *dentry,
+				   const struct path *path,
 				   struct xattr_dos_attrib *da)
 {
 	struct ndr n;
 	int err;
 
-	err = ksmbd_vfs_getxattr(idmap, dentry, XATTR_NAME_DOS_ATTRIBUTE,
+	err = ksmbd_vfs_getxattr(idmap, path, XATTR_NAME_DOS_ATTRIBUTE,
 				 (char **)&n.data);
 	if (err > 0) {
 		n.length = err;
@@ -1892,18 +1888,13 @@ void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat)
 
 int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 				const struct mnt_idmap *idmap,
-				struct dentry *dentry,
+				const struct path *path,
 				struct ksmbd_kstat *ksmbd_kstat)
 {
-	struct ksmbd_share_config *share_conf = work->tcon->share_conf;
 	u64 time;
 	int rc;
-	struct path path = {
-		.mnt = share_conf->vfs_path.mnt,
-		.dentry = dentry,
-	};
 
-	rc = vfs_getattr(&path, ksmbd_kstat->kstat,
+	rc = vfs_getattr(path, ksmbd_kstat->kstat,
 			 STATX_BASIC_STATS | STATX_BTIME,
 			 AT_STATX_SYNC_AS_STAT);
 	if (rc)
@@ -1925,7 +1916,7 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 				   KSMBD_SHARE_FLAG_STORE_DOS_ATTRS)) {
 		struct xattr_dos_attrib da;
 
-		rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, dentry, &da);
+		rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, path, &da);
 		if (rc > 0) {
 			ksmbd_kstat->file_attributes = cpu_to_le32(da.attr);
 			ksmbd_kstat->create_time = da.create_time;
@@ -1948,7 +1939,7 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 		char *xattr_list = NULL, *name;
 		ssize_t xattr_list_len;
 
-		xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list);
+		xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 		if (xattr_list_len > 0) {
 			for (name = xattr_list;
 			     name - xattr_list < xattr_list_len;
@@ -1967,13 +1958,13 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 }
 
 ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap,
-				struct dentry *dentry, char *attr_name,
+				const struct path *path, char *attr_name,
 				int attr_name_len, char *actual_name)
 {
 	char *name, *xattr_list = NULL;
 	ssize_t value_len = -ENOENT, xattr_list_len;
 
-	xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list);
+	xattr_list_len = ksmbd_vfs_listxattr(path, &xattr_list);
 	if (xattr_list_len < 0) {
 		value_len = xattr_list_len;
 		goto out;
@@ -1989,7 +1980,7 @@ ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap,
 		if (actual_name && strlen(name) + 1 != attr_name_len)
 			continue;
 
-		value_len = ksmbd_vfs_xattr_len(idmap, dentry, name);
+		value_len = ksmbd_vfs_xattr_len(idmap, path, name);
 		/* The caller provides attr_name_len bytes for the actual name. */
 		if (value_len >= 0 && actual_name)
 			memcpy(actual_name, name, attr_name_len);
@@ -2146,7 +2137,7 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
 		saved_cred = override_creds(src_fp->filp->f_cred);
 		src_file_size = ksmbd_vfs_casexattr_len(
 				file_mnt_idmap(src_fp->filp),
-				src_fp->filp->f_path.dentry,
+				&src_fp->filp->f_path,
 				src_fp->stream.name, src_fp->stream.size, NULL);
 		revert_creds(saved_cred);
 		if (src_file_size < 0)
@@ -2464,7 +2455,8 @@ static int __ksmbd_vfs_set_compression(struct ksmbd_work *work,
 	if (fp->f_ci->m_fattr != old_fattr) {
 		struct xattr_dos_attrib da = {0};
 
-		rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, dentry, &da);
+		rc = ksmbd_vfs_get_dos_attrib_xattr(idmap, &fp->filp->f_path,
+						    &da);
 		if (rc <= 0) {
 			da.version = 4;
 			da.itime = fp->itime;
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 21b0d29a3db0..c1ea94cf153f 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -104,18 +104,18 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
 			       unsigned int *chunk_count_written,
 			       unsigned int *chunk_size_written,
 			       loff_t  *total_size_written);
-ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list);
+ssize_t ksmbd_vfs_listxattr(const struct path *path, char **list);
 ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+			   const struct path *path,
 			   char *xattr_name,
 			   char **xattr_buf);
 ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap,
-			    struct dentry *dentry, char *xattr_name);
+			    const struct path *path, char *xattr_name);
 ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap,
-			       struct dentry *dentry, char *attr_name,
+			       const struct path *path, char *attr_name,
 			       int attr_name_len, char **attr_value);
 ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap,
-				struct dentry *dentry, char *attr_name,
+				const struct path *path, char *attr_name,
 				int attr_name_len, char *actual_name);
 int ksmbd_vfs_setxattr(const struct mnt_idmap *idmap,
 		       const struct path *path, const char *attr_name,
@@ -154,7 +154,7 @@ int ksmbd_vfs_unlink(struct file *filp);
 void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat);
 int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 				const struct mnt_idmap *idmap,
-				struct dentry *dentry,
+				const struct path *path,
 				struct ksmbd_kstat *ksmbd_kstat);
 void ksmbd_vfs_posix_lock_wait(struct file_lock *flock);
 void ksmbd_vfs_posix_lock_unblock(struct file_lock *flock);
@@ -168,14 +168,14 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
 			   bool get_write);
 int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
 			   const struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+			   const struct path *path,
 			   struct smb_ntsd **pntsd);
 int ksmbd_vfs_set_dos_attrib_xattr(const struct mnt_idmap *idmap,
 				   const struct path *path,
 				   struct xattr_dos_attrib *da,
 				   bool get_write);
 int ksmbd_vfs_get_dos_attrib_xattr(const struct mnt_idmap *idmap,
-				   struct dentry *dentry,
+				   const struct path *path,
 				   struct xattr_dos_attrib *da);
 int ksmbd_vfs_set_init_posix_acl(const struct mnt_idmap *idmap,
 				 const struct path *path);
diff --git a/fs/xattr.c b/fs/xattr.c
index d9f035610f0b..6234ca550982 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -286,7 +286,7 @@ int __vfs_setxattr_noperm(const struct mnt_idmap *idmap,
  * lock
  *
  *  @idmap: idmap of the mount of the target inode
- *  @dentry: object to perform setxattr on
+ *  @path: object to perform setxattr on
  *  @name: xattr name to set
  *  @value: value to set @name to
  *  @size: size of @value
@@ -295,10 +295,11 @@ int __vfs_setxattr_noperm(const struct mnt_idmap *idmap,
  *  a delegation was broken on, NULL if none.
  */
 int
-__vfs_setxattr_locked(const struct mnt_idmap *idmap, struct dentry *dentry,
+__vfs_setxattr_locked(const struct mnt_idmap *idmap, const struct path *path,
 		      const char *name, const void *value, size_t size,
 		      int flags, struct delegated_inode *delegated_inode)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 
@@ -324,9 +325,10 @@ __vfs_setxattr_locked(const struct mnt_idmap *idmap, struct dentry *dentry,
 EXPORT_SYMBOL_GPL(__vfs_setxattr_locked);
 
 int
-vfs_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry,
+vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path,
 	     const char *name, const void *value, size_t size, int flags)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	struct delegated_inode delegated_inode = { };
 	const void  *orig_value = value;
@@ -341,7 +343,7 @@ vfs_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry,
 
 retry_deleg:
 	inode_lock(inode);
-	error = __vfs_setxattr_locked(idmap, dentry, name, value, size,
+	error = __vfs_setxattr_locked(idmap, path, name, value, size,
 				      flags, &delegated_inode);
 	inode_unlock(inode);
 
@@ -448,9 +450,10 @@ __vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name,
 EXPORT_SYMBOL(__vfs_getxattr);
 
 ssize_t
-vfs_getxattr(const struct mnt_idmap *idmap, struct dentry *dentry,
+vfs_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 	     const char *name, void *value, size_t size)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 
@@ -482,12 +485,12 @@ EXPORT_SYMBOL_GPL(vfs_getxattr);
 
 /**
  * vfs_listxattr - retrieve \0 separated list of xattr names
- * @dentry: the dentry from whose inode the xattr names are retrieved
+ * @path: the path from whose inode the xattr names are retrieved
  * @list: buffer to store xattr names into
  * @size: size of the buffer
  *
  * This function returns the names of all xattrs associated with the
- * inode of @dentry.
+ * inode of @path->dentry.
  *
  * Note, for legacy reasons the vfs_listxattr() function lists POSIX
  * ACLs as well. Since POSIX ACLs are decoupled from IOP_XATTR the
@@ -503,8 +506,9 @@ EXPORT_SYMBOL_GPL(vfs_getxattr);
  *         negative error code.
  */
 ssize_t
-vfs_listxattr(struct dentry *dentry, char *list, size_t size)
+vfs_listxattr(const struct path *path, char *list, size_t size)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
 	ssize_t error;
 
@@ -551,16 +555,17 @@ EXPORT_SYMBOL(__vfs_removexattr);
  * lock
  *
  *  @idmap: idmap of the mount of the target inode
- *  @dentry: object to perform setxattr on
+ *  @path: object to perform removexattr on
  *  @name: name of xattr to remove
  *  @delegated_inode: on return, will contain an inode pointer that
  *  a delegation was broken on, NULL if none.
  */
 int
 __vfs_removexattr_locked(const struct mnt_idmap *idmap,
-			 struct dentry *dentry, const char *name,
+			 const struct path *path, const char *name,
 			 struct delegated_inode *delegated_inode)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 
@@ -589,17 +594,17 @@ __vfs_removexattr_locked(const struct mnt_idmap *idmap,
 EXPORT_SYMBOL_GPL(__vfs_removexattr_locked);
 
 int
-vfs_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry,
+vfs_removexattr(const struct mnt_idmap *idmap, const struct path *path,
 		const char *name)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	struct delegated_inode delegated_inode = { };
 	int error;
 
 retry_deleg:
 	inode_lock(inode);
-	error = __vfs_removexattr_locked(idmap, dentry,
-					 name, &delegated_inode);
+	error = __vfs_removexattr_locked(idmap, path, name, &delegated_inode);
 	inode_unlock(inode);
 
 	if (is_delegated(&delegated_inode)) {
@@ -652,14 +657,14 @@ int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx)
 	return error;
 }
 
-static int do_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry,
-		struct kernel_xattr_ctx *ctx)
+static int do_setxattr(const struct mnt_idmap *idmap, const struct path *path,
+		       struct kernel_xattr_ctx *ctx)
 {
 	if (is_posix_acl_xattr(ctx->kname->name))
-		return do_set_acl(idmap, dentry, ctx->kname->name,
-				  ctx->kvalue, ctx->size);
+		return do_set_acl(idmap, path->dentry,
+				  ctx->kname->name, ctx->kvalue, ctx->size);
 
-	return vfs_setxattr(idmap, dentry, ctx->kname->name,
+	return vfs_setxattr(idmap, path, ctx->kname->name,
 			ctx->kvalue, ctx->size, ctx->flags);
 }
 
@@ -669,7 +674,7 @@ int file_setxattr(struct file *f, struct kernel_xattr_ctx *ctx)
 
 	if (!error) {
 		audit_file(f);
-		error = do_setxattr(file_mnt_idmap(f), f->f_path.dentry, ctx);
+		error = do_setxattr(file_mnt_idmap(f), &f->f_path, ctx);
 		mnt_drop_write_file(f);
 	}
 	return error;
@@ -687,7 +692,7 @@ int filename_setxattr(int dfd, struct filename *filename,
 		return error;
 	error = mnt_want_write(path.mnt);
 	if (!error) {
-		error = do_setxattr(mnt_idmap(path.mnt), path.dentry, ctx);
+		error = do_setxattr(mnt_idmap(path.mnt), &path, ctx);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
@@ -787,8 +792,8 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
  * Extended attribute GET operations
  */
 static ssize_t
-do_getxattr(const struct mnt_idmap *idmap, struct dentry *d,
-	struct kernel_xattr_ctx *ctx)
+do_getxattr(const struct mnt_idmap *idmap, const struct path *path,
+	    struct kernel_xattr_ctx *ctx)
 {
 	ssize_t error;
 	char *kname = ctx->kname->name;
@@ -803,9 +808,10 @@ do_getxattr(const struct mnt_idmap *idmap, struct dentry *d,
 	}
 
 	if (is_posix_acl_xattr(kname))
-		error = do_get_acl(idmap, d, kname, kvalue, ctx->size);
+		error = do_get_acl(idmap, path->dentry,
+				   kname, kvalue, ctx->size);
 	else
-		error = vfs_getxattr(idmap, d, kname, kvalue, ctx->size);
+		error = vfs_getxattr(idmap, path, kname, kvalue, ctx->size);
 	if (error > 0) {
 		if (ctx->size && copy_to_user(ctx->value, kvalue, error))
 			error = -EFAULT;
@@ -822,7 +828,7 @@ do_getxattr(const struct mnt_idmap *idmap, struct dentry *d,
 ssize_t file_getxattr(struct file *f, struct kernel_xattr_ctx *ctx)
 {
 	audit_file(f);
-	return do_getxattr(file_mnt_idmap(f), f->f_path.dentry, ctx);
+	return do_getxattr(file_mnt_idmap(f), &f->f_path, ctx);
 }
 
 ssize_t filename_getxattr(int dfd, struct filename *filename,
@@ -834,7 +840,7 @@ ssize_t filename_getxattr(int dfd, struct filename *filename,
 	error = filename_lookup(dfd, filename, lookup_flags, &path, NULL);
 	if (error)
 		return error;
-	error = do_getxattr(mnt_idmap(path.mnt), path.dentry, ctx);
+	error = do_getxattr(mnt_idmap(path.mnt), &path, ctx);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -925,7 +931,7 @@ SYSCALL_DEFINE4(fgetxattr, int, fd, const char __user *, name,
  * Extended attribute LIST operations
  */
 static ssize_t
-listxattr(struct dentry *d, char __user *list, size_t size)
+listxattr(const struct path *path, char __user *list, size_t size)
 {
 	ssize_t error;
 	char *klist = NULL;
@@ -938,7 +944,7 @@ listxattr(struct dentry *d, char __user *list, size_t size)
 			return -ENOMEM;
 	}
 
-	error = vfs_listxattr(d, klist, size);
+	error = vfs_listxattr(path, klist, size);
 	if (error > 0) {
 		if (size && copy_to_user(list, klist, error))
 			error = -EFAULT;
@@ -957,7 +963,7 @@ static
 ssize_t file_listxattr(struct file *f, char __user *list, size_t size)
 {
 	audit_file(f);
-	return listxattr(f->f_path.dentry, list, size);
+	return listxattr(&f->f_path, list, size);
 }
 
 static
@@ -971,7 +977,7 @@ ssize_t filename_listxattr(int dfd, struct filename *filename,
 	error = filename_lookup(dfd, filename, lookup_flags, &path, NULL);
 	if (error)
 		return error;
-	error = listxattr(path.dentry, list, size);
+	error = listxattr(&path, list, size);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -1029,11 +1035,12 @@ SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size)
  * Extended attribute REMOVE operations
  */
 static long
-removexattr(const struct mnt_idmap *idmap, struct dentry *d, const char *name)
+removexattr(const struct mnt_idmap *idmap, const struct path *path,
+	    const char *name)
 {
 	if (is_posix_acl_xattr(name))
-		return vfs_remove_acl(idmap, d, name);
-	return vfs_removexattr(idmap, d, name);
+		return vfs_remove_acl(idmap, path->dentry, name);
+	return vfs_removexattr(idmap, path, name);
 }
 
 static int file_removexattr(struct file *f, struct xattr_name *kname)
@@ -1042,8 +1049,7 @@ static int file_removexattr(struct file *f, struct xattr_name *kname)
 
 	if (!error) {
 		audit_file(f);
-		error = removexattr(file_mnt_idmap(f),
-				    f->f_path.dentry, kname->name);
+		error = removexattr(file_mnt_idmap(f), &f->f_path, kname->name);
 		mnt_drop_write_file(f);
 	}
 	return error;
@@ -1061,7 +1067,7 @@ static int filename_removexattr(int dfd, struct filename *filename,
 		return error;
 	error = mnt_want_write(path.mnt);
 	if (!error) {
-		error = removexattr(mnt_idmap(path.mnt), path.dentry, kname->name);
+		error = removexattr(mnt_idmap(path.mnt), &path, kname->name);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
diff --git a/include/linux/xattr.h b/include/linux/xattr.h
index 4cc4257de084..68301f0b3de2 100644
--- a/include/linux/xattr.h
+++ b/include/linux/xattr.h
@@ -77,22 +77,25 @@ struct xattr {
 };
 
 ssize_t __vfs_getxattr(struct dentry *, struct inode *, const char *, void *, size_t);
-ssize_t vfs_getxattr(const struct mnt_idmap *, struct dentry *, const char *,
-		     void *, size_t);
-ssize_t vfs_listxattr(struct dentry *d, char *list, size_t size);
+ssize_t vfs_getxattr(const struct mnt_idmap *idmap, const struct path *path,
+		     const char *name, void *value, size_t size);
+ssize_t vfs_listxattr(const struct path *path, char *list, size_t size);
 int __vfs_setxattr(const struct mnt_idmap *, struct dentry *, struct inode *,
 		   const char *, const void *, size_t, int);
 int __vfs_setxattr_noperm(const struct mnt_idmap *, struct dentry *,
 			  const char *, const void *, size_t, int);
-int __vfs_setxattr_locked(const struct mnt_idmap *, struct dentry *,
-			  const char *, const void *, size_t, int,
-			  struct delegated_inode *);
-int vfs_setxattr(const struct mnt_idmap *, struct dentry *, const char *,
-		 const void *, size_t, int);
+int __vfs_setxattr_locked(const struct mnt_idmap *idmap,
+			  const struct path *path, const char *name,
+			  const void *value, size_t size, int flags,
+			  struct delegated_inode *delegated_inode);
+int vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path,
+		 const char *name, const void *value, size_t size, int flags);
 int __vfs_removexattr(const struct mnt_idmap *, struct dentry *, const char *);
-int __vfs_removexattr_locked(const struct mnt_idmap *, struct dentry *,
-			     const char *, struct delegated_inode *);
-int vfs_removexattr(const struct mnt_idmap *, struct dentry *, const char *);
+int __vfs_removexattr_locked(const struct mnt_idmap *idmap,
+			     const struct path *path, const char *name,
+			     struct delegated_inode *delegated_inode);
+int vfs_removexattr(const struct mnt_idmap *idmap, const struct path *path,
+		    const char *name);
 
 ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size);
 int vfs_getxattr_alloc(const struct mnt_idmap *idmap,
diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c
index 1c41af2f91a6..3d759efd3c6c 100644
--- a/security/integrity/evm/evm_crypto.c
+++ b/security/integrity/evm/evm_crypto.c
@@ -237,7 +237,7 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry,
 	size_t xattr_size = 0;
 	char *xattr_value = NULL;
 	int error;
-	int size, user_space_size;
+	int size;
 	bool ima_present = false;
 	u64 i_version = 0;
 
@@ -287,12 +287,6 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry,
 		if (size < 0)
 			continue;
 
-		user_space_size = vfs_getxattr(&nop_mnt_idmap, dentry,
-					       xattr->name, NULL, 0);
-		if (user_space_size != size)
-			pr_debug("file %s: xattr %s size mismatch (kernel: %d, user: %d)\n",
-				 dentry->d_name.name, xattr->name, size,
-				 user_space_size);
 		error = 0;
 		xattr_size = size;
 		crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 9d7467fe73c5..548a14ff0664 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7145,8 +7145,8 @@ static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen
  */
 static int selinux_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
-	return __vfs_setxattr_locked(&nop_mnt_idmap, path->dentry,
-				     XATTR_NAME_SELINUX, ctx, ctxlen, 0, NULL);
+	return __vfs_setxattr_locked(&nop_mnt_idmap, path, XATTR_NAME_SELINUX,
+				     ctx, ctxlen, 0, NULL);
 }
 
 static int selinux_inode_getsecctx(struct inode *inode, struct lsm_context *cp)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 953461571026..b39bee15b70c 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4980,8 +4980,8 @@ static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen)
 
 static int smack_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
-	return __vfs_setxattr_locked(&nop_mnt_idmap, path->dentry,
-				     XATTR_NAME_SMACK, ctx, ctxlen, 0, NULL);
+	return __vfs_setxattr_locked(&nop_mnt_idmap, path, XATTR_NAME_SMACK,
+				     ctx, ctxlen, 0, NULL);
 }
 
 static int smack_inode_getsecctx(struct inode *inode, struct lsm_context *cp)
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 04/18] fs: pass struct path to POSIX ACL helpers
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (2 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 03/18] fs: pass struct path to xattr helpers Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 05/18] LSM: pass struct path and struct file to the inode_setattr hook Cai Xinchen
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

vfs_set_acl(), vfs_get_acl() and vfs_remove_acl() together with the
do_set_acl()/do_get_acl() syscall helpers take a struct dentry even
though their callers either already hold a struct path (or a struct
file).  Switch them all to take a struct path instead of the dentry.

The struct mnt_idmap stays a separate argument, now consistently in
first position: it is taken from the mount at the time the path is
checked and must be passed down unchanged so that the VFS and the
filesystem are guaranteed to see the same idmap, so it cannot be
derived from the path inside the helpers.  A stacking filesystem may
need to decouple the idmap from the path it forwards to the lower
filesystem: ecryptfs_set_acl() is invoked with ACLs that have already
been translated into global filesystem ids, so it must keep passing
&nop_mnt_idmap just like it does for the other vfs_* helpers it
calls, while ecryptfs_get_acl() threads the idmap of the upper mount
through to the lower filesystem.

set_posix_acl() keeps taking the idmap and dentry as it is called
from inode_operations->set_acl, which operates on a bare dentry.

The security_inode_*_acl() hooks invoked from fs/posix_acl.c keep
taking the idmap and dentry for now; they will be converted together
with the LSM hooks themselves in separate patches.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/ecryptfs/inode.c       | 11 ++++++-----
 fs/internal.h             | 11 ++++++-----
 fs/overlayfs/inode.c      |  5 +++--
 fs/overlayfs/overlayfs.h  | 14 ++++++++++++--
 fs/posix_acl.c            | 32 ++++++++++++++++++--------------
 fs/smb/server/vfs.c       |  2 +-
 fs/xattr.c                |  9 ++++-----
 include/linux/posix_acl.h | 14 ++++++++------
 8 files changed, 58 insertions(+), 40 deletions(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index c1be27e1f7eb..7db6fa6e5274 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -1095,8 +1095,9 @@ static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap,
 static struct posix_acl *ecryptfs_get_acl(const struct mnt_idmap *idmap,
 					  struct dentry *dentry, int type)
 {
-	return vfs_get_acl(idmap, ecryptfs_dentry_to_lower(dentry),
-			   posix_acl_xattr_name(type));
+	struct path lower_path = ecryptfs_lower_path(dentry);
+
+	return vfs_get_acl(idmap, &lower_path, posix_acl_xattr_name(type));
 }
 
 static int ecryptfs_set_acl(const struct mnt_idmap *idmap,
@@ -1104,10 +1105,10 @@ static int ecryptfs_set_acl(const struct mnt_idmap *idmap,
 			    int type)
 {
 	int rc;
-	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
-	struct inode *lower_inode = d_inode(lower_dentry);
+	struct path lower_path = ecryptfs_lower_path(dentry);
+	struct inode *lower_inode = d_inode(lower_path.dentry);
 
-	rc = vfs_set_acl(&nop_mnt_idmap, lower_dentry,
+	rc = vfs_set_acl(&nop_mnt_idmap, &lower_path,
 			 posix_acl_xattr_name(type), acl);
 	if (!rc)
 		fsstack_copy_attr_all(d_inode(dentry), lower_inode);
diff --git a/fs/internal.h b/fs/internal.h
index e833c7e6e14f..dcbc54f9803c 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -304,20 +304,21 @@ int import_xattr_name(struct xattr_name *kname, const char __user *name);
 int may_write_xattr(const struct mnt_idmap *idmap, struct inode *inode);
 
 #ifdef CONFIG_FS_POSIX_ACL
-int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+int do_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 	       const char *acl_name, const void *kvalue, size_t size);
-ssize_t do_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+ssize_t do_get_acl(const struct mnt_idmap *idmap, const struct path *path,
 		   const char *acl_name, void *kvalue, size_t size);
 #else
 static inline int do_set_acl(const struct mnt_idmap *idmap,
-			     struct dentry *dentry, const char *acl_name,
+			     const struct path *path, const char *acl_name,
 			     const void *kvalue, size_t size)
 {
 	return -EOPNOTSUPP;
 }
 static inline ssize_t do_get_acl(const struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name,
-				 void *kvalue, size_t size)
+				 const struct path *path,
+				 const char *acl_name, void *kvalue,
+				 size_t size)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 63906b3dafcb..c50c9899ab09 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -414,7 +414,7 @@ struct posix_acl *ovl_get_acl_path(const struct path *path,
 	if (noperm)
 		real_acl = get_inode_acl(realinode, posix_acl_type(acl_name));
 	else
-		real_acl = vfs_get_acl(idmap, path->dentry, acl_name);
+		real_acl = vfs_get_acl(idmap, path, acl_name);
 	if (IS_ERR_OR_NULL(real_acl))
 		return real_acl;
 
@@ -502,7 +502,8 @@ static int ovl_set_or_remove_acl(struct dentry *dentry, struct inode *inode,
 
 		ovl_path_lower(dentry, &realpath);
 		with_ovl_creds(dentry->d_sb)
-			real_acl = vfs_get_acl(mnt_idmap(realpath.mnt), realdentry, acl_name);
+			real_acl = vfs_get_acl(mnt_idmap(realpath.mnt),
+					       &realpath, acl_name);
 		if (IS_ERR(real_acl)) {
 			err = PTR_ERR(real_acl);
 			goto out;
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index bf3fa54ee27a..fc5e7accc1f0 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -371,13 +371,23 @@ static inline int ovl_removexattr(struct ovl_fs *ofs, struct dentry *dentry,
 static inline int ovl_do_set_acl(struct ovl_fs *ofs, struct dentry *dentry,
 				 const char *acl_name, struct posix_acl *acl)
 {
-	return vfs_set_acl(ovl_upper_mnt_idmap(ofs), dentry, acl_name, acl);
+	struct path path = {
+		.mnt = ovl_upper_mnt(ofs),
+		.dentry = dentry,
+	};
+
+	return vfs_set_acl(ovl_upper_mnt_idmap(ofs), &path, acl_name, acl);
 }
 
 static inline int ovl_do_remove_acl(struct ovl_fs *ofs, struct dentry *dentry,
 				    const char *acl_name)
 {
-	return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), dentry, acl_name);
+	struct path path = {
+		.mnt = ovl_upper_mnt(ofs),
+		.dentry = dentry,
+	};
+
+	return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), &path, acl_name);
 }
 
 static inline int ovl_do_rename_rd(struct renamedata *rd)
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index fe77934ea8f2..1dbc59f50932 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -1081,8 +1081,8 @@ static int vfs_set_acl_idmapped_mnt(const struct mnt_idmap *idmap,
 
 /**
  * vfs_set_acl - set posix acls
- * @idmap: idmap of the mount
- * @dentry: the dentry based on which to set the posix acls
+ * @idmap: idmap of the mount the posix acls are set through
+ * @path: the path based on which to set the posix acls
  * @acl_name: the name of the posix acl
  * @kacl: the posix acls in the appropriate VFS format
  *
@@ -1091,9 +1091,10 @@ static int vfs_set_acl_idmapped_mnt(const struct mnt_idmap *idmap,
  *
  * Return: On success 0, on error negative errno.
  */
-int vfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+int vfs_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 		const char *acl_name, struct posix_acl *kacl)
 {
+	struct dentry *dentry = path->dentry;
 	int acl_type;
 	int error;
 	struct inode *inode = d_inode(dentry);
@@ -1159,8 +1160,8 @@ EXPORT_SYMBOL_GPL(vfs_set_acl);
 
 /**
  * vfs_get_acl - get posix acls
- * @idmap: idmap of the mount
- * @dentry: the dentry based on which to retrieve the posix acls
+ * @idmap: idmap of the mount the posix acls are retrieved through
+ * @path: the path based on which to retrieve the posix acls
  * @acl_name: the name of the posix acl
  *
  * This function retrieves @kacl from the filesystem. The caller must all
@@ -1169,8 +1170,10 @@ EXPORT_SYMBOL_GPL(vfs_set_acl);
  * Return: On success POSIX ACLs in VFS format, on error negative errno.
  */
 struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name)
+			      const struct path *path,
+			      const char *acl_name)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
 	struct posix_acl *acl;
 	int acl_type, error;
@@ -1204,17 +1207,18 @@ EXPORT_SYMBOL_GPL(vfs_get_acl);
 
 /**
  * vfs_remove_acl - remove posix acls
- * @idmap: idmap of the mount
- * @dentry: the dentry based on which to retrieve the posix acls
+ * @idmap: idmap of the mount the posix acls are removed through
+ * @path: the path based on which to retrieve the posix acls
  * @acl_name: the name of the posix acl
  *
  * This function removes posix acls.
  *
  * Return: On success 0, on error negative errno.
  */
-int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
 		   const char *acl_name)
 {
+	struct dentry *dentry = path->dentry;
 	int acl_type;
 	int error;
 	struct inode *inode = d_inode(dentry);
@@ -1265,7 +1269,7 @@ int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
 }
 EXPORT_SYMBOL_GPL(vfs_remove_acl);
 
-int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+int do_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 	       const char *acl_name, const void *kvalue, size_t size)
 {
 	int error;
@@ -1281,22 +1285,22 @@ int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
 			return PTR_ERR(acl);
 	}
 
-	error = vfs_set_acl(idmap, dentry, acl_name, acl);
+	error = vfs_set_acl(idmap, path, acl_name, acl);
 	posix_acl_release(acl);
 	return error;
 }
 
-ssize_t do_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+ssize_t do_get_acl(const struct mnt_idmap *idmap, const struct path *path,
 		   const char *acl_name, void *kvalue, size_t size)
 {
 	ssize_t error;
 	struct posix_acl *acl;
 
-	acl = vfs_get_acl(idmap, dentry, acl_name);
+	acl = vfs_get_acl(idmap, path, acl_name);
 	if (IS_ERR(acl))
 		return PTR_ERR(acl);
 
-	error = vfs_posix_acl_to_xattr(idmap, d_inode(dentry),
+	error = vfs_posix_acl_to_xattr(idmap, d_inode(path->dentry),
 				       acl, kvalue, size);
 	posix_acl_release(acl);
 	return error;
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 96aca3e659e9..63af977d8767 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1564,7 +1564,7 @@ int ksmbd_vfs_remove_acl_xattrs(const struct mnt_idmap *idmap,
 			     sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1) ||
 		    !strncmp(name, XATTR_NAME_POSIX_ACL_DEFAULT,
 			     sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1)) {
-			err = vfs_remove_acl(idmap, path->dentry, name);
+			err = vfs_remove_acl(idmap, path, name);
 			if (err)
 				ksmbd_debug(SMB,
 					    "remove acl xattr failed : %s\n", name);
diff --git a/fs/xattr.c b/fs/xattr.c
index 6234ca550982..ad0d0cc1cca8 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -661,8 +661,8 @@ static int do_setxattr(const struct mnt_idmap *idmap, const struct path *path,
 		       struct kernel_xattr_ctx *ctx)
 {
 	if (is_posix_acl_xattr(ctx->kname->name))
-		return do_set_acl(idmap, path->dentry,
-				  ctx->kname->name, ctx->kvalue, ctx->size);
+		return do_set_acl(idmap, path, ctx->kname->name,
+				  ctx->kvalue, ctx->size);
 
 	return vfs_setxattr(idmap, path, ctx->kname->name,
 			ctx->kvalue, ctx->size, ctx->flags);
@@ -808,8 +808,7 @@ do_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 	}
 
 	if (is_posix_acl_xattr(kname))
-		error = do_get_acl(idmap, path->dentry,
-				   kname, kvalue, ctx->size);
+		error = do_get_acl(idmap, path, kname, kvalue, ctx->size);
 	else
 		error = vfs_getxattr(idmap, path, kname, kvalue, ctx->size);
 	if (error > 0) {
@@ -1039,7 +1038,7 @@ removexattr(const struct mnt_idmap *idmap, const struct path *path,
 	    const char *name)
 {
 	if (is_posix_acl_xattr(name))
-		return vfs_remove_acl(idmap, path->dentry, name);
+		return vfs_remove_acl(idmap, path, name);
 	return vfs_removexattr(idmap, path, name);
 }
 
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index caf500bed993..4bd2ed8378c9 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -105,11 +105,12 @@ static inline void cache_no_acl(struct inode *inode)
 	inode->i_default_acl = NULL;
 }
 
-int vfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+int vfs_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 		const char *acl_name, struct posix_acl *kacl);
 struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name);
-int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
+			      const struct path *path,
+			      const char *acl_name);
+int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
 		   const char *acl_name);
 int posix_acl_listxattr(struct inode *inode, char **buffer,
 			ssize_t *remaining_size);
@@ -142,21 +143,22 @@ static inline void forget_all_cached_acls(struct inode *inode)
 }
 
 static inline int vfs_set_acl(const struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *name,
+			      const struct path *path, const char *name,
 			      struct posix_acl *acl)
 {
 	return -EOPNOTSUPP;
 }
 
 static inline struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
-					    struct dentry *dentry,
+					    const struct path *path,
 					    const char *acl_name)
 {
 	return ERR_PTR(-EOPNOTSUPP);
 }
 
 static inline int vfs_remove_acl(const struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name)
+				 const struct path *path,
+				 const char *acl_name)
 {
 	return -EOPNOTSUPP;
 }
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 05/18] LSM: pass struct path and struct file to the inode_setattr hook
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (3 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 04/18] fs: pass struct path to POSIX ACL helpers Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:48   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks Cai Xinchen
                   ` (12 subsequent siblings)
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_setattr hook is called from notify_change(), which now
holds a struct path and a struct file, together with the idmap that
was taken from the mount when the path was checked.  The other
in-kernel caller, the fat and exfat attribute ioctls, already holds
a struct file and can pass its f_path and its file_mnt_idmap()
directly.

Convert the hook and its SELinux, Smack and EVM implementations to
take the struct path and the struct file, next to the idmap: the
dentry is taken from the path, while the idmap remains a separate
argument, as it must be passed down unchanged so that the VFS and
the LSM are guaranteed to see the same idmap; ecryptfs, for example,
applies attribute changes to a lower path with &nop_mnt_idmap,
which deriving the idmap from that path would not reproduce.

Also pass the struct file through which an attribute change is
requested, when there is one, so that LSMs can later authorize
file descriptor-based operations (fchmod(2), fchown(2),
fchmodat2(2), fchownat(2) and utimensat(2) with AT_EMPTY_PATH, and
the fat/exfat attribute ioctls) with the rights available at
open(2) time.  To that end, do_fchmodat(), do_fchownat() and
do_utimes_path() now follow the vfs_fstatat() pattern and dispatch
an empty path with AT_EMPTY_PATH to the file descriptor-based
vfs_fchmod(), vfs_fchown() and vfs_utimes() callers.  The file is
threaded through notify_change(), chmod_common(), chown_common()
and vfs_utimes(), and set to NULL for path-based operations, for
server-side callers that only hold a path, and for internal kernel
changes: the implicit setuid clearing through __remove_privs(), the
size changes in do_truncate(), which are governed by the truncate
hooks, and the nfsd delegation cmtime updates.  No implementation
uses the file yet, so this is a purely mechanical change with no
behavior change.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 drivers/base/devtmpfs.c           |  4 +--
 fs/attr.c                         | 14 +++++----
 fs/btrfs/volumes.c                |  2 +-
 fs/ecryptfs/inode.c               |  4 +--
 fs/exfat/file.c                   |  2 +-
 fs/fat/file.c                     |  2 +-
 fs/init.c                         |  6 ++--
 fs/inode.c                        |  2 +-
 fs/internal.h                     |  6 ++--
 fs/nfsd/nfs4state.c               |  4 +--
 fs/nfsd/vfs.c                     |  4 +--
 fs/open.c                         | 50 +++++++++++++++++++++++--------
 fs/overlayfs/overlayfs.h          |  2 +-
 fs/smb/server/smb2pdu.c           |  2 +-
 fs/smb/server/smbacl.c            |  2 +-
 fs/utimes.c                       | 21 +++++++++----
 include/linux/fs.h                |  6 ++--
 include/linux/lsm_hook_defs.h     | 12 ++++++--
 include/linux/security.h          |  8 +++--
 init/initramfs.c                  |  2 +-
 security/integrity/evm/evm_main.c |  9 ++++--
 security/security.c               | 10 ++++---
 security/selinux/hooks.c          |  6 ++--
 security/smack/smack_lsm.c        |  9 ++++--
 24 files changed, 127 insertions(+), 62 deletions(-)

diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c
index 410906a66ee9..00f2e3e7e27e 100644
--- a/drivers/base/devtmpfs.c
+++ b/drivers/base/devtmpfs.c
@@ -291,7 +291,7 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
 		newattrs.ia_gid = gid;
 		newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
 		inode_lock(d_inode(dentry));
-		notify_change(&nop_mnt_idmap, &attr_path, &newattrs, NULL);
+		notify_change(&nop_mnt_idmap, &attr_path, &newattrs, NULL, NULL);
 		inode_unlock(d_inode(dentry));
 
 		/* mark as kernel-created inode */
@@ -392,7 +392,7 @@ static int handle_remove(const char *nodename, struct device *dev)
 		newattrs.ia_valid =
 			ATTR_UID|ATTR_GID|ATTR_MODE;
 		inode_lock(d_inode(dentry));
-		notify_change(&nop_mnt_idmap, &attr_path, &newattrs, NULL);
+		notify_change(&nop_mnt_idmap, &attr_path, &newattrs, NULL, NULL);
 		inode_unlock(d_inode(dentry));
 		err = vfs_unlink(&nop_mnt_idmap, d_inode(parent.dentry),
 				 dentry, NULL);
diff --git a/fs/attr.c b/fs/attr.c
index cc06fd676e6b..67fa5ee6b30e 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -400,7 +400,8 @@ EXPORT_SYMBOL(may_setattr);
 static int do_notify_change(const struct path *const path,
 			    const struct mnt_idmap *const idmap,
 			    struct dentry *const dentry, struct iattr *attr,
-			    struct delegated_inode *delegated_inode)
+			    struct delegated_inode *delegated_inode,
+			    struct file *const file)
 {
 	struct inode *inode = dentry->d_inode;
 	umode_t mode = inode->i_mode;
@@ -518,7 +519,7 @@ static int do_notify_change(const struct path *const path,
 	 * filesystem, were already checked on the user-facing path.
 	 */
 	if (path) {
-		error = security_inode_setattr(idmap, dentry, attr);
+		error = security_inode_setattr(idmap, path, attr, file);
 		if (error)
 			return error;
 	}
@@ -570,7 +571,8 @@ static int do_notify_change(const struct path *const path,
 int __notify_change(const struct mnt_idmap *idmap, struct dentry *dentry,
 		    struct iattr *attr, struct delegated_inode *delegated_inode)
 {
-	return do_notify_change(NULL, idmap, dentry, attr, delegated_inode);
+	return do_notify_change(NULL, idmap, dentry, attr, delegated_inode,
+				NULL);
 }
 EXPORT_SYMBOL(__notify_change);
 
@@ -580,6 +582,7 @@ EXPORT_SYMBOL(__notify_change);
  * @path:	object affected
  * @attr:	new attributes
  * @delegated_inode: returns inode, if the inode is delegated
+ * @file:	file through which the attribute change is requested, if any
  *
  * The caller must hold the i_rwsem exclusively on the affected object.
  *
@@ -604,9 +607,10 @@ EXPORT_SYMBOL(__notify_change);
  * Return: 0 on success, or a negative error on failure.
  */
 int notify_change(const struct mnt_idmap *idmap, const struct path *path,
-		  struct iattr *attr, struct delegated_inode *delegated_inode)
+		  struct iattr *attr, struct delegated_inode *delegated_inode,
+		  struct file *file)
 {
 	return do_notify_change(path, idmap, path->dentry, attr,
-				delegated_inode);
+				delegated_inode, file);
 }
 EXPORT_SYMBOL(notify_change);
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index f1305f68b1f8..2f91b81bbd30 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -2152,7 +2152,7 @@ static void update_dev_time(const char *device_path)
 	}
 
 	if (!err) {
-		vfs_utimes(&path, NULL);
+		vfs_utimes(&path, NULL, NULL);
 		path_put(&path);
 	}
 }
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 7db6fa6e5274..a6de40fcce4c 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -822,7 +822,7 @@ static int __ecryptfs_truncate(struct dentry *dentry, const struct iattr *ia)
 set_size:
 	lower_ia.ia_valid |= ATTR_SIZE;
 	inode_lock(d_inode(lower_dentry));
-	rc = notify_change(&nop_mnt_idmap, &lower_path, &lower_ia, NULL);
+	rc = notify_change(&nop_mnt_idmap, &lower_path, &lower_ia, NULL, NULL);
 	inode_unlock(d_inode(lower_dentry));
 out:
 	ecryptfs_put_lower_file(inode);
@@ -932,7 +932,7 @@ static int ecryptfs_setattr(const struct mnt_idmap *idmap,
 
 		inode_lock(d_inode(lower_path.dentry));
 		rc = notify_change(&nop_mnt_idmap, &lower_path, &lower_ia,
-				   NULL);
+				   NULL, NULL);
 		inode_unlock(d_inode(lower_path.dentry));
 	}
 out:
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 1386149916bb..27b4917e02cb 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -552,7 +552,7 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
 	 * module, just because it maps to a file mode.
 	 */
 	err = security_inode_setattr(file_mnt_idmap(file),
-				     file->f_path.dentry, &ia);
+			     &file->f_path, &ia, file);
 	if (err)
 		goto out_unlock_inode;
 
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 70583e791bdc..a044270379e4 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -93,7 +93,7 @@ static int fat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
 	 * module, just because it maps to a file mode.
 	 */
 	err = security_inode_setattr(file_mnt_idmap(file),
-				     file->f_path.dentry, &ia);
+			     &file->f_path, &ia, file);
 	if (err)
 		goto out_unlock_inode;
 
diff --git a/fs/init.c b/fs/init.c
index 33e312d74f58..b65e47e59ee0 100644
--- a/fs/init.c
+++ b/fs/init.c
@@ -107,7 +107,7 @@ int __init init_chown(const char *filename, uid_t user, gid_t group, int flags)
 		return error;
 	error = mnt_want_write(path.mnt);
 	if (!error) {
-		error = chown_common(&path, user, group);
+		error = chown_common(&path, user, group, NULL);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
@@ -122,7 +122,7 @@ int __init init_chmod(const char *filename, umode_t mode)
 	error = kern_path(filename, LOOKUP_FOLLOW, &path);
 	if (error)
 		return error;
-	error = chmod_common(&path, mode);
+	error = chmod_common(&path, mode, NULL);
 	path_put(&path);
 	return error;
 }
@@ -201,7 +201,7 @@ int __init init_utimes(char *filename, struct timespec64 *ts)
 	error = kern_path(filename, 0, &path);
 	if (error)
 		return error;
-	error = vfs_utimes(&path, ts);
+	error = vfs_utimes(&path, ts, NULL);
 	path_put(&path);
 	return error;
 }
diff --git a/fs/inode.c b/fs/inode.c
index fe176293fd36..d69973fabd52 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2366,7 +2366,7 @@ static int __remove_privs(const struct mnt_idmap *idmap,
 	 * Note we call this on write, so notify_change will not
 	 * encounter any conflicting delegations:
 	 */
-	return notify_change(idmap, path, &newattrs, NULL);
+	return notify_change(idmap, path, &newattrs, NULL, NULL);
 }
 
 static int file_remove_privs_flags(struct file *file, unsigned int flags)
diff --git a/fs/internal.h b/fs/internal.h
index dcbc54f9803c..e90de99de5ea 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -201,10 +201,12 @@ struct file *file_close_fd_locked(struct files_struct *files, unsigned fd);
 int filp_close_sync(struct file *filp, fl_owner_t id);
 
 int do_ftruncate(struct file *file, loff_t length, unsigned int flags);
-int chmod_common(const struct path *path, umode_t mode);
+int chmod_common(const struct path *path, umode_t mode,
+		  struct file *file);
 int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
 		int flag);
-int chown_common(const struct path *path, uid_t user, gid_t group);
+int chown_common(const struct path *path, uid_t user, gid_t group,
+		  struct file *file);
 extern int vfs_open(const struct path *, struct file *);
 int vfs_open_consume(struct path *, struct file *);
 
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 3a08c56a27c6..38451d1de587 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -10510,7 +10510,7 @@ static int cb_getattr_update_times(const struct path *path,
 
 	attrs.ia_valid |= ATTR_DELEG;
 	inode_lock(inode);
-	ret = notify_change(&nop_mnt_idmap, path, &attrs, NULL);
+	ret = notify_change(&nop_mnt_idmap, path, &attrs, NULL, NULL);
 	inode_unlock(inode);
 	return ret;
 }
@@ -10791,7 +10791,7 @@ void nfsd_update_cmtime_attr(struct file *f, unsigned int flags)
 	};
 
 	inode_lock(inode);
-	ret = notify_change(&nop_mnt_idmap, &f->f_path, &attr, NULL);
+	ret = notify_change(&nop_mnt_idmap, &f->f_path, &attr, NULL, NULL);
 	inode_unlock(inode);
 	if (ret)
 		pr_notice_ratelimited("nfsd: Unable to update timestamps on "
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 397f417e77b3..2ab92a0e3b6b 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -478,7 +478,7 @@ static int __nfsd_setattr(const struct path *path, struct iattr *iap)
 			return -EFBIG;
 
 		host_err = notify_change(&nop_mnt_idmap, path, &size_attr,
-					  NULL);
+					  NULL, NULL);
 		if (host_err)
 			return host_err;
 		iap->ia_valid &= ~ATTR_SIZE;
@@ -504,7 +504,7 @@ static int __nfsd_setattr(const struct path *path, struct iattr *iap)
 	if (!(iap->ia_valid & ATTR_DELEG))
 		iap->ia_valid |= ATTR_CTIME;
 
-	return notify_change(&nop_mnt_idmap, path, iap, NULL);
+	return notify_change(&nop_mnt_idmap, path, iap, NULL, NULL);
 }
 
 /**
diff --git a/fs/open.c b/fs/open.c
index 294d0ea7488e..882985d4008b 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -65,8 +65,13 @@ int do_truncate(const struct mnt_idmap *idmap, const struct path *path,
 	if (ret)
 		return ret;
 
-	/* Note any delegations or leases have already been broken: */
-	ret = notify_change(idmap, path, &newattrs, NULL);
+	/*
+	 * Note any delegations or leases have already been broken.  Size
+	 * changes are governed by the truncate hooks, which are checked at
+	 * ftruncate(2) time on the opened file, so no struct file is passed
+	 * to the setattr hook.
+	 */
+	ret = notify_change(idmap, path, &newattrs, NULL, NULL);
 	inode_unlock(dentry->d_inode);
 	return ret;
 }
@@ -665,7 +670,7 @@ SYSCALL_DEFINE2(fchroot, int, fd, unsigned int, flags)
 	return error;
 }
 
-int chmod_common(const struct path *path, umode_t mode)
+int chmod_common(const struct path *path, umode_t mode, struct file *file)
 {
 	struct inode *inode = path->dentry->d_inode;
 	struct delegated_inode delegated_inode = { };
@@ -685,7 +690,7 @@ int chmod_common(const struct path *path, umode_t mode)
 	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
 	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
 	error = notify_change(mnt_idmap(path->mnt), path, &newattrs,
-			      &delegated_inode);
+			      &delegated_inode, file);
 out_unlock:
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
@@ -701,7 +706,7 @@ int chmod_common(const struct path *path, umode_t mode)
 int vfs_fchmod(struct file *file, umode_t mode)
 {
 	audit_file(file);
-	return chmod_common(&file->f_path, mode);
+	return chmod_common(&file->f_path, mode, file);
 }
 
 SYSCALL_DEFINE2(fchmod, unsigned int, fd, umode_t, mode)
@@ -724,12 +729,22 @@ static int do_fchmodat(int dfd, const char __user *filename, umode_t mode,
 	if (unlikely(flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)))
 		return -EINVAL;
 
+	CLASS(filename_maybe_null, name)(filename, flags);
+
+	if (!name && dfd >= 0) {
+		CLASS(fd, f)(dfd);
+
+		if (fd_empty(f))
+			return -EBADF;
+
+		return vfs_fchmod(fd_file(f), mode);
+	}
+
 	lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
-	CLASS(filename_uflags, name)(filename, flags);
 retry:
 	error = filename_lookup(dfd, name, lookup_flags, &path, NULL);
 	if (!error) {
-		error = chmod_common(&path, mode);
+		error = chmod_common(&path, mode, NULL);
 		path_put(&path);
 		if (retry_estale(error, lookup_flags)) {
 			lookup_flags |= LOOKUP_REVAL;
@@ -786,7 +801,8 @@ static inline bool setattr_vfsgid(struct iattr *attr, kgid_t kgid)
 	return true;
 }
 
-int chown_common(const struct path *path, uid_t user, gid_t group)
+int chown_common(const struct path *path, uid_t user, gid_t group,
+		 struct file *file)
 {
 	const struct mnt_idmap *idmap;
 	struct user_namespace *fs_userns;
@@ -824,7 +840,7 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
 		from_vfsgid(idmap, fs_userns, newattrs.ia_vfsgid));
 	if (!error)
 		error = notify_change(idmap, path, &newattrs,
-				      &delegated_inode);
+				      &delegated_inode, file);
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
 		error = break_deleg_wait(&delegated_inode);
@@ -844,14 +860,24 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
 	if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
 		return -EINVAL;
 
+	CLASS(filename_maybe_null, name)(filename, flag);
+
+	if (!name && dfd >= 0) {
+		CLASS(fd, f)(dfd);
+
+		if (fd_empty(f))
+			return -EBADF;
+
+		return vfs_fchown(fd_file(f), user, group);
+	}
+
 	lookup_flags = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
-	CLASS(filename_uflags, name)(filename, flag);
 retry:
 	error = filename_lookup(dfd, name, lookup_flags, &path, NULL);
 	if (!error) {
 		error = mnt_want_write(path.mnt);
 		if (!error) {
-			error = chown_common(&path, user, group);
+			error = chown_common(&path, user, group, NULL);
 			mnt_drop_write(path.mnt);
 		}
 		path_put(&path);
@@ -888,7 +914,7 @@ int vfs_fchown(struct file *file, uid_t user, gid_t group)
 	if (error)
 		return error;
 	audit_file(file);
-	error = chown_common(&file->f_path, user, group);
+	error = chown_common(&file->f_path, user, group, file);
 	mnt_drop_write_file(file);
 	return error;
 }
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index fc5e7accc1f0..a169f27258a0 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -213,7 +213,7 @@ static inline int ovl_do_notify_change(struct ovl_fs *ofs,
 		.dentry = upperdentry,
 	};
 
-	return notify_change(ovl_upper_mnt_idmap(ofs), &path, attr, NULL);
+	return notify_change(ovl_upper_mnt_idmap(ofs), &path, attr, NULL, NULL);
 }
 
 static inline int ovl_do_rmdir(struct ovl_fs *ofs,
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 07b0a71a4353..91e201af66c2 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -8778,7 +8778,7 @@ static int set_file_basic_info(struct ksmbd_file *fp,
 			return -EACCES;
 
 		inode_lock(inode);
-		rc = notify_change(idmap, &filp->f_path, &attrs, NULL);
+		rc = notify_change(idmap, &filp->f_path, &attrs, NULL, filp);
 		inode_unlock(inode);
 	}
 	return rc;
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 94bf83d07452..c0f8f6d8ea98 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1749,7 +1749,7 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
 	}
 
 	inode_lock(inode);
-	rc = notify_change(idmap, path, &newattrs, NULL);
+	rc = notify_change(idmap, path, &newattrs, NULL, NULL);
 	inode_unlock(inode);
 	if (rc)
 		goto out;
diff --git a/fs/utimes.c b/fs/utimes.c
index 5193d504d289..6021c481e742 100644
--- a/fs/utimes.c
+++ b/fs/utimes.c
@@ -18,7 +18,8 @@ static bool nsec_valid(long nsec)
 	return nsec >= 0 && nsec <= 999999999;
 }
 
-int vfs_utimes(const struct path *path, struct timespec64 *times)
+int vfs_utimes(const struct path *path, struct timespec64 *times,
+	       struct file *file)
 {
 	int error;
 	struct iattr newattrs;
@@ -65,7 +66,7 @@ int vfs_utimes(const struct path *path, struct timespec64 *times)
 retry_deleg:
 	inode_lock(inode);
 	error = notify_change(mnt_idmap(path->mnt), path, &newattrs,
-			      &delegated_inode);
+			      &delegated_inode, file);
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
 		error = break_deleg_wait(&delegated_inode);
@@ -88,15 +89,25 @@ static int do_utimes_path(int dfd, const char __user *filename,
 	if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))
 		return -EINVAL;
 
+	CLASS(filename_maybe_null, name)(filename, flags);
+
+	if (!name && dfd >= 0) {
+		CLASS(fd, f)(dfd);
+
+		if (fd_empty(f))
+			return -EBADF;
+
+		return vfs_utimes(&fd_file(f)->f_path, times, fd_file(f));
+	}
+
 	if (!(flags & AT_SYMLINK_NOFOLLOW))
 		lookup_flags |= LOOKUP_FOLLOW;
 
-	CLASS(filename_uflags, name)(filename, flags);
 retry:
 	error = filename_lookup(dfd, name, lookup_flags, &path, NULL);
 	if (error)
 		return error;
-	error = vfs_utimes(&path, times);
+	error = vfs_utimes(&path, times, NULL);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -113,7 +124,7 @@ static int do_utimes_fd(int fd, struct timespec64 *times, int flags)
 	CLASS(fd, f)(fd);
 	if (fd_empty(f))
 		return -EBADF;
-	return vfs_utimes(&fd_file(f)->f_path, times);
+	return vfs_utimes(&fd_file(f)->f_path, times, fd_file(f));
 }
 
 /*
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 960334128b83..9c344cbad296 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1818,7 +1818,8 @@ int vfs_mkobj(struct dentry *, umode_t,
 
 int vfs_fchown(struct file *file, uid_t user, gid_t group);
 int vfs_fchmod(struct file *file, umode_t mode);
-int vfs_utimes(const struct path *path, struct timespec64 *times);
+int vfs_utimes(const struct path *path, struct timespec64 *times,
+		struct file *file);
 
 #ifdef CONFIG_COMPAT
 extern long compat_ptr_ioctl(struct file *file, unsigned int cmd,
@@ -2717,7 +2718,8 @@ static inline int bmap(struct inode *inode,  sector_t *block)
 int __notify_change(const struct mnt_idmap *, struct dentry *,
 		    struct iattr *, struct delegated_inode *);
 int notify_change(const struct mnt_idmap *, const struct path *,
-		  struct iattr *, struct delegated_inode *);
+		  struct iattr *, struct delegated_inode *,
+		  struct file *);
 int inode_permission(const struct mnt_idmap *, struct inode *, int);
 int generic_permission(const struct mnt_idmap *, struct inode *, int);
 static inline int file_permission(struct file *file, int mask)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 539525648b30..ea6e0fbaa3b9 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -141,8 +141,16 @@ LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode,
 	 bool rcu)
 LSM_HOOK(int, 0, inode_permission, const struct mnt_idmap *idmap,
 	 struct inode *inode, int mask)
-LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap, struct dentry *dentry,
-	 struct iattr *attr)
+/*
+ * For the hooks that pass a struct path: the VFS only sanctions
+ * passing the path to VFS helpers taking a struct path (e.g.,
+ * notify_change(), the *xattr() helpers); it makes no guarantees
+ * with regard to path->mnt, whose layout and implementation details
+ * are subject to change.  Implementations must not rely on its
+ * properties.
+ */
+LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap,
+	 const struct path *path, struct iattr *attr, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
 	 struct dentry *dentry, int ia_valid)
 LSM_HOOK(int, 0, inode_getattr, const struct path *path)
diff --git a/include/linux/security.h b/include/linux/security.h
index 225f822470e2..8b660c118dee 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -429,7 +429,8 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode,
 int security_inode_permission(const struct mnt_idmap *idmap,
 			      struct inode *inode, int mask);
 int security_inode_setattr(const struct mnt_idmap *idmap,
-			   struct dentry *dentry, struct iattr *attr);
+			   const struct path *path, struct iattr *attr,
+			   struct file *file);
 void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
 				 int ia_valid);
 int security_inode_getattr(const struct path *path);
@@ -995,8 +996,9 @@ static inline int security_inode_permission(const struct mnt_idmap *idmap,
 }
 
 static inline int security_inode_setattr(const struct mnt_idmap *idmap,
-					 struct dentry *dentry,
-					 struct iattr *attr)
+					 const struct path *path,
+					 struct iattr *attr,
+					 struct file *file)
 {
 	return 0;
 }
diff --git a/init/initramfs.c b/init/initramfs.c
index ebddde9c8f0d..c8172440276e 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -144,7 +144,7 @@ static void __init do_utime(char *filename, time64_t mtime)
 static void __init do_utime_path(const struct path *path, time64_t mtime)
 {
 	struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
-	vfs_utimes(path, t);
+	vfs_utimes(path, t, NULL);
 }
 
 static __initdata LIST_HEAD(dir_list);
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index ac8328812e6e..9526bffe34aa 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -950,17 +950,20 @@ static int evm_attr_change(const struct mnt_idmap *idmap,
 /**
  * evm_inode_setattr - prevent updating an invalid EVM extended attribute
  * @idmap: idmap of the mount
- * @dentry: pointer to the affected dentry
+ * @path: pointer to the affected object
  * @attr: iattr structure containing the new file attributes
+ * @file: file through which the attribute change is requested, if any
  *
  * Permit update of file attributes when files have a valid EVM signature,
  * except in the case of them having an immutable portable signature.
  */
-static int evm_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
-			     struct iattr *attr)
+static int evm_inode_setattr(const struct mnt_idmap *idmap,
+			     const struct path *path,
+			     struct iattr *attr, struct file *file)
 {
 	unsigned int ia_valid = attr->ia_valid;
 	enum integrity_status evm_status;
+	struct dentry *dentry = path->dentry;
 
 	/* Policy permits modification of the protected attrs even though
 	 * there's no HMAC key loaded
diff --git a/security/security.c b/security/security.c
index 49cd3393c207..9c1e27f0e5cb 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1867,8 +1867,9 @@ int security_inode_permission(const struct mnt_idmap *idmap,
 /**
  * security_inode_setattr() - Check if setting file attributes is allowed
  * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @attr: new attributes
+ * @file: file through which the attribute change is requested, if any
  *
  * Check permission before setting file attributes.  Note that the kernel call
  * to notify_change is performed from several locations, whenever file
@@ -1878,11 +1879,12 @@ int security_inode_permission(const struct mnt_idmap *idmap,
  * Return: Returns 0 if permission is granted.
  */
 int security_inode_setattr(const struct mnt_idmap *idmap,
-			   struct dentry *dentry, struct iattr *attr)
+			   const struct path *path, struct iattr *attr,
+			   struct file *file)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_setattr, idmap, dentry, attr);
+	return call_int_hook(inode_setattr, idmap, path, attr, file);
 }
 EXPORT_SYMBOL_GPL(security_inode_setattr);
 
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 548a14ff0664..3d8636149f59 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3316,10 +3316,12 @@ static int selinux_inode_permission(const struct mnt_idmap *idmap,
 	return rc;
 }
 
-static int selinux_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
-				 struct iattr *iattr)
+static int selinux_inode_setattr(const struct mnt_idmap *idmap,
+				 const struct path *path,
+				 struct iattr *iattr, struct file *file)
 {
 	const struct cred *cred = current_cred();
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_backing_inode(dentry);
 	unsigned int ia_valid = iattr->ia_valid;
 	u32 av = FILE__WRITE;
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index b39bee15b70c..ede1314030af 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1267,15 +1267,18 @@ static int smack_inode_permission(const struct mnt_idmap *idmap, struct inode *i
 /**
  * smack_inode_setattr - Smack check for setting attributes
  * @idmap: idmap of the mount
- * @dentry: the object
+ * @path: the object
  * @iattr: for the force flag
+ * @file: file through which the attribute change is requested, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
-			       struct iattr *iattr)
+static int smack_inode_setattr(const struct mnt_idmap *idmap,
+			       const struct path *path,
+			       struct iattr *iattr, struct file *file)
 {
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	int rc;
 
 	/*
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (4 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 05/18] LSM: pass struct path and struct file to the inode_setattr hook Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 13:02   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 07/18] LSM: pass struct path and struct file to the inode posix acl hooks Cai Xinchen
                   ` (11 subsequent siblings)
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_setxattr, inode_getxattr, inode_listxattr and
inode_removexattr hooks are called from fs/xattr.c, whose helpers
now hold a struct path together with the idmap that was taken from
the mount when the path was checked, and derive the dentry from
the path just for the hook calls.

Convert the hooks and their commoncap, SELinux, Smack, EVM and IMA
implementations to take a const struct path.  The implementations
take the dentry they still need from the path, while the idmap
remains a separate argument for the inode_setxattr and
inode_removexattr hooks, which already take one, as it must be
passed down unchanged so that the VFS and the LSM are guaranteed
to see the same idmap; the inode_getxattr and inode_listxattr
hooks take no idmap.  This is a purely mechanical change with no
behavior change.

Also pass the struct file through which an xattr operation is
requested, when there is one, so that LSMs can later authorize
file descriptor-based operations (fsetxattr(2), fgetxattr(2),
flistxattr(2), fremovexattr(2), the *xattrat(2) syscalls with
AT_EMPTY_PATH, and io_uring's IORING_OP_FSETXATTR and
IORING_OP_FGETXATTR) with the rights available at open(2) time.
The file is threaded through __vfs_setxattr_locked(),
vfs_setxattr(), vfs_getxattr(), vfs_listxattr(),
__vfs_removexattr_locked() and vfs_removexattr(), and set to NULL
for path-based operations and for in-kernel users: overlayfs,
ecryptfs, nfsd, the SMB server, cachefiles, zloop, and the SELinux
and Smack setsecctx implementations.  io_uring and the f*xattr(2)
family get the file for free through file_setxattr(),
file_getxattr() and the other file-based helpers.  No
implementation uses the file yet, so this part is a purely
mechanical change with no behavior change either.

Also update the BPF selftest programs that attach to the changed
hooks (test_get_xattr.c, test_set_remove_xattr.c and
verifier_lsm_init_xattr.c) to the new signatures, passing
path->dentry to the dentry xattr kfuncs.  The bpf_lsm_* attach
stubs themselves are generated from lsm_hook_defs.h and need no
changes.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 drivers/block/zloop.c                         |  5 +-
 fs/cachefiles/xattr.c                         | 14 ++--
 fs/ecryptfs/inode.c                           |  2 +-
 fs/nfsd/vfs.c                                 | 15 ++--
 fs/overlayfs/copy_up.c                        |  4 +-
 fs/overlayfs/overlayfs.h                      |  8 ++-
 fs/overlayfs/xattrs.c                         |  6 +-
 fs/smb/server/vfs.c                           | 12 ++--
 fs/xattr.c                                    | 70 +++++++++++--------
 include/linux/lsm_hook_defs.h                 | 12 ++--
 include/linux/security.h                      | 43 +++++++-----
 include/linux/xattr.h                         | 18 +++--
 security/commoncap.c                          | 24 +++----
 security/integrity/evm/evm_main.c             | 20 ++++--
 security/integrity/ima/ima_appraise.c         | 16 +++--
 security/security.c                           | 55 ++++++++-------
 security/selinux/hooks.c                      | 23 +++---
 security/smack/smack_lsm.c                    | 25 ++++---
 .../selftests/bpf/progs/test_get_xattr.c      |  5 +-
 .../bpf/progs/test_set_remove_xattr.c         | 30 ++++----
 .../bpf/progs/verifier_lsm_init_xattr.c       |  6 +-
 21 files changed, 243 insertions(+), 170 deletions(-)

diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 4cf502454100..107457fed18e 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -719,7 +719,8 @@ static int zloop_record_safe_wps(struct zloop_device *zlo)
 		if (!zloop_zone_is_active(zone))
 			continue;
 		ret = vfs_setxattr(file_mnt_idmap(file), &file->f_path,
-				"user.zloop.wp", &zone->wp, sizeof(zone->wp), 0);
+				"user.zloop.wp", &zone->wp, sizeof(zone->wp),
+				0, NULL);
 		if (ret) {
 			pr_err("%pg: failed to record write pointer (%d)\n",
 				zlo->disk->part0, ret);
@@ -1423,7 +1424,7 @@ static void zloop_forget_cache(struct zloop_device *zlo)
 			continue;
 
 		ret = vfs_getxattr(file_mnt_idmap(file), &file->f_path,
-				"user.zloop.wp", &old_wp, sizeof(old_wp));
+				"user.zloop.wp", &old_wp, sizeof(old_wp), NULL);
 		if (ret == -ENODATA) {
 			old_wp = 0;
 		} else if (ret != sizeof(old_wp)) {
diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c
index afdce5ce3b73..1e814183ade4 100644
--- a/fs/cachefiles/xattr.c
+++ b/fs/cachefiles/xattr.c
@@ -56,7 +56,8 @@ int cachefiles_preset_object_xattr(struct cachefiles_object *object, struct file
 		if (ret == 0) {
 			ret = vfs_setxattr(&nop_mnt_idmap, &file->f_path,
 					   cachefiles_xattr_cache, buf,
-					   sizeof(struct cachefiles_xattr) + len, 0);
+					   sizeof(struct cachefiles_xattr) + len,
+					   0, NULL);
 			mnt_drop_write_file(file);
 		}
 	}
@@ -120,7 +121,8 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object)
 		if (ret == 0) {
 			ret = vfs_setxattr(&nop_mnt_idmap, &file->f_path,
 					   cachefiles_xattr_cache, buf,
-					   sizeof(struct cachefiles_xattr) + len, 0);
+					   sizeof(struct cachefiles_xattr) + len,
+					   0, NULL);
 			mnt_drop_write_file(file);
 		}
 	}
@@ -174,7 +176,7 @@ int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file
 	xlen = cachefiles_inject_read_error();
 	if (xlen == 0)
 		xlen = vfs_getxattr(&nop_mnt_idmap, &file->f_path,
-				    cachefiles_xattr_cache, buf, tlen);
+				    cachefiles_xattr_cache, buf, tlen, NULL);
 	if (xlen != tlen) {
 		if (xlen < 0) {
 			ret = xlen;
@@ -234,7 +236,7 @@ int cachefiles_remove_object_xattr(struct cachefiles_cache *cache,
 		ret = mnt_want_write(cache->mnt);
 		if (ret == 0) {
 			ret = vfs_removexattr(&nop_mnt_idmap, &path,
-					      cachefiles_xattr_cache);
+					      cachefiles_xattr_cache, NULL);
 			mnt_drop_write(cache->mnt);
 		}
 	}
@@ -299,7 +301,7 @@ bool cachefiles_set_volume_xattr(struct cachefiles_volume *volume)
 		if (ret == 0) {
 			ret = vfs_setxattr(&nop_mnt_idmap, &path,
 					   cachefiles_xattr_cache,
-					   buf, len, 0);
+					   buf, len, 0, NULL);
 			mnt_drop_write(volume->cache->mnt);
 		}
 	}
@@ -345,7 +347,7 @@ int cachefiles_check_volume_xattr(struct cachefiles_volume *volume)
 	xlen = cachefiles_inject_read_error();
 	if (xlen == 0)
 		xlen = vfs_getxattr(&nop_mnt_idmap, &path,
-				    cachefiles_xattr_cache, buf, len);
+				    cachefiles_xattr_cache, buf, len, NULL);
 	if (xlen != len) {
 		if (xlen < 0) {
 			ret = xlen;
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index a6de40fcce4c..782bcca83874 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -1003,7 +1003,7 @@ ecryptfs_setxattr(struct dentry *dentry, struct inode *inode,
 	}
 	inode_lock(lower_inode);
 	rc = __vfs_setxattr_locked(&nop_mnt_idmap, &lower_path, name, value,
-				   size, flags, NULL);
+				   size, flags, NULL, NULL);
 	inode_unlock(lower_inode);
 	if (!rc && inode)
 		fsstack_copy_attr_all(inode, lower_inode);
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 2ab92a0e3b6b..7050ec0992a1 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -703,7 +703,7 @@ int nfsd4_is_junction(const struct path *path)
 	if (!(inode->i_mode & S_ISVTX))
 		return 0;
 	if (vfs_getxattr(&nop_mnt_idmap, path, NFSD_JUNCTION_XATTR_NAME,
-			 NULL, 0) <= 0)
+			 NULL, 0, NULL) <= 0)
 		return 0;
 	return 1;
 }
@@ -2668,7 +2668,7 @@ nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 
 	inode_lock_shared(inode);
 
-	len = vfs_getxattr(&nop_mnt_idmap, &path, name, NULL, 0);
+	len = vfs_getxattr(&nop_mnt_idmap, &path, name, NULL, 0, NULL);
 
 	/*
 	 * Zero-length attribute, just return.
@@ -2695,7 +2695,7 @@ nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 		goto out;
 	}
 
-	len = vfs_getxattr(&nop_mnt_idmap, &path, name, buf, len);
+	len = vfs_getxattr(&nop_mnt_idmap, &path, name, buf, len, NULL);
 	if (len <= 0) {
 		kvfree(buf);
 		buf = NULL;
@@ -2742,7 +2742,7 @@ nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char **bufp,
 
 	inode_lock_shared(inode);
 
-	len = vfs_listxattr(&path, NULL, 0);
+	len = vfs_listxattr(&path, NULL, 0, NULL);
 	if (len <= 0) {
 		err = nfsd_xattr_errno(len);
 		goto out;
@@ -2759,7 +2759,7 @@ nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char **bufp,
 		goto out;
 	}
 
-	len = vfs_listxattr(&path, buf, len);
+	len = vfs_listxattr(&path, buf, len, NULL);
 	if (len <= 0) {
 		kvfree(buf);
 		err = nfsd_xattr_errno(len);
@@ -2808,7 +2808,8 @@ nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name)
 	err = fh_fill_pre_attrs(fhp);
 	if (err != nfs_ok)
 		goto out_unlock;
-	ret = __vfs_removexattr_locked(&nop_mnt_idmap, &path, name, NULL);
+	ret = __vfs_removexattr_locked(&nop_mnt_idmap, &path, name,
+				       NULL, NULL);
 	err = nfsd_xattr_errno(ret);
 	fh_fill_post_attrs(fhp);
 out_unlock:
@@ -2840,7 +2841,7 @@ nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name,
 	if (err != nfs_ok)
 		goto out_unlock;
 	ret = __vfs_setxattr_locked(&nop_mnt_idmap, &path, name, buf, len,
-				    flags, NULL);
+				    flags, NULL, NULL);
 	fh_fill_post_attrs(fhp);
 	err = nfsd_xattr_errno(ret);
 out_unlock:
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 6dc241f195a6..94ca09fc1c5f 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -83,7 +83,7 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
 	if (!old->d_inode->i_op->listxattr || !new->d_inode->i_op->listxattr)
 		return 0;
 
-	list_size = vfs_listxattr(oldpath, NULL, 0);
+	list_size = vfs_listxattr(oldpath, NULL, 0, NULL);
 	if (list_size <= 0) {
 		if (list_size == -EOPNOTSUPP)
 			return 0;
@@ -94,7 +94,7 @@ int ovl_copy_xattr(struct super_block *sb, const struct path *oldpath, struct de
 	if (!buf)
 		return -ENOMEM;
 
-	list_size = vfs_listxattr(oldpath, buf, list_size);
+	list_size = vfs_listxattr(oldpath, buf, list_size, NULL);
 	if (list_size <= 0) {
 		error = list_size;
 		goto out;
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index a169f27258a0..9ffa985fe8f2 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -295,7 +295,8 @@ static inline ssize_t ovl_do_getxattr(const struct path *path, const char *name,
 
 	WARN_ON(path->dentry->d_sb != path->mnt->mnt_sb);
 
-	err = vfs_getxattr(mnt_idmap(path->mnt), path, name, value, size);
+	err = vfs_getxattr(mnt_idmap(path->mnt), path, name, value, size,
+			   NULL);
 	len = (value && err > 0) ? err : 0;
 
 	pr_debug("getxattr(%pd2, \"%s\", \"%*pE\", %zu, 0) = %i\n",
@@ -335,7 +336,7 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry,
 
 	/* Use vfs_setxattr(), not __vfs_setxattr(): it idmaps the security.capability rootid. */
 	int err = vfs_setxattr(ovl_upper_mnt_idmap(ofs), &path, name,
-			       value, size, flags);
+			       value, size, flags, NULL);
 
 	pr_debug("setxattr(%pd2, \"%s\", \"%*pE\", %zu, %d) = %i\n",
 		 dentry, name, min((int)size, 48), value, size, flags, err);
@@ -357,7 +358,8 @@ static inline int ovl_do_removexattr(struct ovl_fs *ofs, struct dentry *dentry,
 		.dentry = dentry,
 	};
 
-	int err = vfs_removexattr(ovl_upper_mnt_idmap(ofs), &path, name);
+	int err = vfs_removexattr(ovl_upper_mnt_idmap(ofs), &path, name,
+			  NULL);
 	pr_debug("removexattr(%pd2, \"%s\") = %i\n", dentry, name, err);
 	return err;
 }
diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c
index 2a97a9ec642a..7bd12daba28a 100644
--- a/fs/overlayfs/xattrs.c
+++ b/fs/overlayfs/xattrs.c
@@ -46,7 +46,7 @@ static int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char
 		ovl_path_lower(dentry, &realpath);
 		with_ovl_creds(dentry->d_sb)
 			err = vfs_getxattr(mnt_idmap(realpath.mnt), &realpath,
-					   name, NULL, 0);
+					   name, NULL, 0, NULL);
 		if (err < 0)
 			goto out;
 	}
@@ -88,7 +88,7 @@ static int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char
 	/* Use vfs_getxattr(), not __vfs_getxattr(): it idmaps the security.capability rootid. */
 	with_ovl_creds(dentry->d_sb)
 		return vfs_getxattr(mnt_idmap(realpath.mnt), &realpath,
-				    name, value, size);
+				    name, value, size, NULL);
 }
 
 static bool ovl_can_list(struct super_block *sb, const char *s)
@@ -117,7 +117,7 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size)
 	ovl_path_real(dentry, &realpath);
 
 	with_ovl_creds(dentry->d_sb)
-		res = vfs_listxattr(&realpath, list, size);
+		res = vfs_listxattr(&realpath, list, size, NULL);
 	if (res <= 0 || size == 0)
 		return res;
 
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 63af977d8767..cfb9a811363e 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -880,7 +880,7 @@ ssize_t ksmbd_vfs_listxattr(const struct path *path, char **list)
 	ssize_t size;
 	char *vlist = NULL;
 
-	size = vfs_listxattr(path, NULL, 0);
+	size = vfs_listxattr(path, NULL, 0, NULL);
 	if (size <= 0)
 		return size;
 
@@ -889,7 +889,7 @@ ssize_t ksmbd_vfs_listxattr(const struct path *path, char **list)
 		return -ENOMEM;
 
 	*list = vlist;
-	size = vfs_listxattr(path, vlist, size);
+	size = vfs_listxattr(path, vlist, size, NULL);
 	if (size < 0) {
 		ksmbd_debug(VFS, "listxattr failed\n");
 		kvfree(vlist);
@@ -902,7 +902,7 @@ ssize_t ksmbd_vfs_listxattr(const struct path *path, char **list)
 ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap,
 			    const struct path *path, char *xattr_name)
 {
-	return vfs_getxattr(idmap, path, xattr_name, NULL, 0);
+	return vfs_getxattr(idmap, path, xattr_name, NULL, 0, NULL);
 }
 
 /**
@@ -931,7 +931,7 @@ ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap,
 		return -ENOMEM;
 
 	xattr_len = vfs_getxattr(idmap, path, xattr_name,
-				 (void *)buf, xattr_len);
+				 (void *)buf, xattr_len, NULL);
 	if (xattr_len > 0)
 		*xattr_buf = buf;
 	else
@@ -968,7 +968,7 @@ int ksmbd_vfs_setxattr(const struct mnt_idmap *idmap,
 			   attr_name,
 			   attr_value,
 			   attr_size,
-			   flags);
+			   flags, NULL);
 	if (err)
 		ksmbd_debug(VFS, "setxattr failed, err %d\n", err);
 	if (get_write == true)
@@ -1255,7 +1255,7 @@ int ksmbd_vfs_remove_xattr(const struct mnt_idmap *idmap,
 			return err;
 	}
 
-	err = vfs_removexattr(idmap, path, attr_name);
+	err = vfs_removexattr(idmap, path, attr_name, NULL);
 
 	if (get_write == true)
 		mnt_drop_write(path->mnt);
diff --git a/fs/xattr.c b/fs/xattr.c
index ad0d0cc1cca8..747c041e66fa 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -293,11 +293,13 @@ int __vfs_setxattr_noperm(const struct mnt_idmap *idmap,
  *  @flags: flags to pass into filesystem operations
  *  @delegated_inode: on return, will contain an inode pointer that
  *  a delegation was broken on, NULL if none.
+ *  @file: file to perform setxattr on, or NULL
  */
 int
 __vfs_setxattr_locked(const struct mnt_idmap *idmap, const struct path *path,
 		      const char *name, const void *value, size_t size,
-		      int flags, struct delegated_inode *delegated_inode)
+		      int flags, struct delegated_inode *delegated_inode,
+		      struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
@@ -307,8 +309,8 @@ __vfs_setxattr_locked(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		return error;
 
-	error = security_inode_setxattr(idmap, dentry, name, value, size,
-					flags);
+	error = security_inode_setxattr(idmap, path, name, value, size,
+					flags, file);
 	if (error)
 		goto out;
 
@@ -326,7 +328,8 @@ EXPORT_SYMBOL_GPL(__vfs_setxattr_locked);
 
 int
 vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path,
-	     const char *name, const void *value, size_t size, int flags)
+	     const char *name, const void *value, size_t size, int flags,
+	     struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
@@ -344,7 +347,7 @@ vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path,
 retry_deleg:
 	inode_lock(inode);
 	error = __vfs_setxattr_locked(idmap, path, name, value, size,
-				      flags, &delegated_inode);
+				      flags, &delegated_inode, file);
 	inode_unlock(inode);
 
 	if (is_delegated(&delegated_inode)) {
@@ -451,7 +454,7 @@ EXPORT_SYMBOL(__vfs_getxattr);
 
 ssize_t
 vfs_getxattr(const struct mnt_idmap *idmap, const struct path *path,
-	     const char *name, void *value, size_t size)
+	     const char *name, void *value, size_t size, struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
@@ -461,7 +464,7 @@ vfs_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		return error;
 
-	error = security_inode_getxattr(dentry, name);
+	error = security_inode_getxattr(path, name, file);
 	if (error)
 		return error;
 
@@ -488,6 +491,7 @@ EXPORT_SYMBOL_GPL(vfs_getxattr);
  * @path: the path from whose inode the xattr names are retrieved
  * @list: buffer to store xattr names into
  * @size: size of the buffer
+ * @file: file through which the xattr names are retrieved, if any
  *
  * This function returns the names of all xattrs associated with the
  * inode of @path->dentry.
@@ -506,13 +510,14 @@ EXPORT_SYMBOL_GPL(vfs_getxattr);
  *         negative error code.
  */
 ssize_t
-vfs_listxattr(const struct path *path, char *list, size_t size)
+vfs_listxattr(const struct path *path, char *list, size_t size,
+	      struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
 	ssize_t error;
 
-	error = security_inode_listxattr(dentry);
+	error = security_inode_listxattr(path, file);
 	if (error)
 		return error;
 
@@ -559,11 +564,13 @@ EXPORT_SYMBOL(__vfs_removexattr);
  *  @name: name of xattr to remove
  *  @delegated_inode: on return, will contain an inode pointer that
  *  a delegation was broken on, NULL if none.
+ *  @file: file to perform removexattr on, or NULL
  */
 int
 __vfs_removexattr_locked(const struct mnt_idmap *idmap,
 			 const struct path *path, const char *name,
-			 struct delegated_inode *delegated_inode)
+			 struct delegated_inode *delegated_inode,
+			 struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
@@ -573,7 +580,7 @@ __vfs_removexattr_locked(const struct mnt_idmap *idmap,
 	if (error)
 		return error;
 
-	error = security_inode_removexattr(idmap, dentry, name);
+	error = security_inode_removexattr(idmap, path, name, file);
 	if (error)
 		goto out;
 
@@ -595,7 +602,7 @@ EXPORT_SYMBOL_GPL(__vfs_removexattr_locked);
 
 int
 vfs_removexattr(const struct mnt_idmap *idmap, const struct path *path,
-		const char *name)
+		const char *name, struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
@@ -604,7 +611,8 @@ vfs_removexattr(const struct mnt_idmap *idmap, const struct path *path,
 
 retry_deleg:
 	inode_lock(inode);
-	error = __vfs_removexattr_locked(idmap, path, name, &delegated_inode);
+	error = __vfs_removexattr_locked(idmap, path, name, &delegated_inode,
+					  file);
 	inode_unlock(inode);
 
 	if (is_delegated(&delegated_inode)) {
@@ -658,14 +666,14 @@ int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx)
 }
 
 static int do_setxattr(const struct mnt_idmap *idmap, const struct path *path,
-		       struct kernel_xattr_ctx *ctx)
+		       struct kernel_xattr_ctx *ctx, struct file *file)
 {
 	if (is_posix_acl_xattr(ctx->kname->name))
 		return do_set_acl(idmap, path, ctx->kname->name,
 				  ctx->kvalue, ctx->size);
 
 	return vfs_setxattr(idmap, path, ctx->kname->name,
-			ctx->kvalue, ctx->size, ctx->flags);
+			ctx->kvalue, ctx->size, ctx->flags, file);
 }
 
 int file_setxattr(struct file *f, struct kernel_xattr_ctx *ctx)
@@ -674,7 +682,7 @@ int file_setxattr(struct file *f, struct kernel_xattr_ctx *ctx)
 
 	if (!error) {
 		audit_file(f);
-		error = do_setxattr(file_mnt_idmap(f), &f->f_path, ctx);
+		error = do_setxattr(file_mnt_idmap(f), &f->f_path, ctx, f);
 		mnt_drop_write_file(f);
 	}
 	return error;
@@ -692,7 +700,7 @@ int filename_setxattr(int dfd, struct filename *filename,
 		return error;
 	error = mnt_want_write(path.mnt);
 	if (!error) {
-		error = do_setxattr(mnt_idmap(path.mnt), &path, ctx);
+		error = do_setxattr(mnt_idmap(path.mnt), &path, ctx, NULL);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
@@ -793,7 +801,7 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
  */
 static ssize_t
 do_getxattr(const struct mnt_idmap *idmap, const struct path *path,
-	    struct kernel_xattr_ctx *ctx)
+	    struct kernel_xattr_ctx *ctx, struct file *file)
 {
 	ssize_t error;
 	char *kname = ctx->kname->name;
@@ -810,7 +818,8 @@ do_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 	if (is_posix_acl_xattr(kname))
 		error = do_get_acl(idmap, path, kname, kvalue, ctx->size);
 	else
-		error = vfs_getxattr(idmap, path, kname, kvalue, ctx->size);
+		error = vfs_getxattr(idmap, path, kname, kvalue,
+				     ctx->size, file);
 	if (error > 0) {
 		if (ctx->size && copy_to_user(ctx->value, kvalue, error))
 			error = -EFAULT;
@@ -827,7 +836,7 @@ do_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 ssize_t file_getxattr(struct file *f, struct kernel_xattr_ctx *ctx)
 {
 	audit_file(f);
-	return do_getxattr(file_mnt_idmap(f), &f->f_path, ctx);
+	return do_getxattr(file_mnt_idmap(f), &f->f_path, ctx, f);
 }
 
 ssize_t filename_getxattr(int dfd, struct filename *filename,
@@ -839,7 +848,7 @@ ssize_t filename_getxattr(int dfd, struct filename *filename,
 	error = filename_lookup(dfd, filename, lookup_flags, &path, NULL);
 	if (error)
 		return error;
-	error = do_getxattr(mnt_idmap(path.mnt), &path, ctx);
+	error = do_getxattr(mnt_idmap(path.mnt), &path, ctx, NULL);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -930,7 +939,8 @@ SYSCALL_DEFINE4(fgetxattr, int, fd, const char __user *, name,
  * Extended attribute LIST operations
  */
 static ssize_t
-listxattr(const struct path *path, char __user *list, size_t size)
+listxattr(const struct path *path, char __user *list, size_t size,
+	   struct file *file)
 {
 	ssize_t error;
 	char *klist = NULL;
@@ -943,7 +953,7 @@ listxattr(const struct path *path, char __user *list, size_t size)
 			return -ENOMEM;
 	}
 
-	error = vfs_listxattr(path, klist, size);
+	error = vfs_listxattr(path, klist, size, file);
 	if (error > 0) {
 		if (size && copy_to_user(list, klist, error))
 			error = -EFAULT;
@@ -962,7 +972,7 @@ static
 ssize_t file_listxattr(struct file *f, char __user *list, size_t size)
 {
 	audit_file(f);
-	return listxattr(&f->f_path, list, size);
+	return listxattr(&f->f_path, list, size, f);
 }
 
 static
@@ -976,7 +986,7 @@ ssize_t filename_listxattr(int dfd, struct filename *filename,
 	error = filename_lookup(dfd, filename, lookup_flags, &path, NULL);
 	if (error)
 		return error;
-	error = listxattr(&path, list, size);
+	error = listxattr(&path, list, size, NULL);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -1035,11 +1045,11 @@ SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size)
  */
 static long
 removexattr(const struct mnt_idmap *idmap, const struct path *path,
-	    const char *name)
+	    const char *name, struct file *file)
 {
 	if (is_posix_acl_xattr(name))
 		return vfs_remove_acl(idmap, path, name);
-	return vfs_removexattr(idmap, path, name);
+	return vfs_removexattr(idmap, path, name, file);
 }
 
 static int file_removexattr(struct file *f, struct xattr_name *kname)
@@ -1048,7 +1058,8 @@ static int file_removexattr(struct file *f, struct xattr_name *kname)
 
 	if (!error) {
 		audit_file(f);
-		error = removexattr(file_mnt_idmap(f), &f->f_path, kname->name);
+		error = removexattr(file_mnt_idmap(f), &f->f_path,
+				    kname->name, f);
 		mnt_drop_write_file(f);
 	}
 	return error;
@@ -1066,7 +1077,8 @@ static int filename_removexattr(int dfd, struct filename *filename,
 		return error;
 	error = mnt_want_write(path.mnt);
 	if (!error) {
-		error = removexattr(mnt_idmap(path.mnt), &path, kname->name);
+		error = removexattr(mnt_idmap(path.mnt), &path,
+				    kname->name, NULL);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index ea6e0fbaa3b9..2315961b6d71 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -156,14 +156,16 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
 LSM_HOOK(int, 0, inode_getattr, const struct path *path)
 LSM_HOOK(int, 0, inode_xattr_skipcap, const char *name)
 LSM_HOOK(int, 0, inode_setxattr, const struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *name, const void *value,
-	 size_t size, int flags)
+	 const struct path *path, const char *name, const void *value,
+	 size_t size, int flags, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_setxattr, struct dentry *dentry,
 	 const char *name, const void *value, size_t size, int flags)
-LSM_HOOK(int, 0, inode_getxattr, struct dentry *dentry, const char *name)
-LSM_HOOK(int, 0, inode_listxattr, struct dentry *dentry)
+LSM_HOOK(int, 0, inode_getxattr, const struct path *path, const char *name,
+	 struct file *file)
+LSM_HOOK(int, 0, inode_listxattr, const struct path *path,
+	 struct file *file)
 LSM_HOOK(int, 0, inode_removexattr, const struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *name)
+	 const struct path *path, const char *name, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry,
 	 const char *name)
 LSM_HOOK(int, 0, inode_file_setattr, struct dentry *dentry, struct file_kattr *fa)
diff --git a/include/linux/security.h b/include/linux/security.h
index 8b660c118dee..35693bc328d3 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -185,10 +185,12 @@ extern int cap_capset(struct cred *new, const struct cred *old,
 		      const kernel_cap_t *inheritable,
 		      const kernel_cap_t *permitted);
 extern int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file);
-int cap_inode_setxattr(struct dentry *dentry, const char *name,
-		       const void *value, size_t size, int flags);
+int cap_inode_setxattr(const struct path *path, const char *name,
+		       const void *value, size_t size, int flags,
+		       struct file *file);
 int cap_inode_removexattr(const struct mnt_idmap *idmap,
-			  struct dentry *dentry, const char *name);
+			  const struct path *path, const char *name,
+			  struct file *file);
 int cap_inode_need_killpriv(struct dentry *dentry);
 int cap_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry);
 int cap_inode_getsecurity(const struct mnt_idmap *idmap,
@@ -435,8 +437,9 @@ void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *d
 				 int ia_valid);
 int security_inode_getattr(const struct path *path);
 int security_inode_setxattr(const struct mnt_idmap *idmap,
-			    struct dentry *dentry, const char *name,
-			    const void *value, size_t size, int flags);
+			    const struct path *path, const char *name,
+			    const void *value, size_t size, int flags,
+			    struct file *file);
 int security_inode_set_acl(const struct mnt_idmap *idmap,
 			   struct dentry *dentry, const char *acl_name,
 			   struct posix_acl *kacl);
@@ -451,10 +454,13 @@ void security_inode_post_remove_acl(const struct mnt_idmap *idmap,
 				    const char *acl_name);
 void security_inode_post_setxattr(struct dentry *dentry, const char *name,
 				  const void *value, size_t size, int flags);
-int security_inode_getxattr(struct dentry *dentry, const char *name);
-int security_inode_listxattr(struct dentry *dentry);
+int security_inode_getxattr(const struct path *path,
+			    const char *name, struct file *file);
+int security_inode_listxattr(const struct path *path,
+			      struct file *file);
 int security_inode_removexattr(const struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *name);
+			       const struct path *path, const char *name,
+			       struct file *file);
 void security_inode_post_removexattr(struct dentry *dentry, const char *name);
 int security_inode_file_setattr(struct dentry *dentry,
 			      struct file_kattr *fa);
@@ -1014,10 +1020,11 @@ static inline int security_inode_getattr(const struct path *path)
 }
 
 static inline int security_inode_setxattr(const struct mnt_idmap *idmap,
-		struct dentry *dentry, const char *name, const void *value,
-		size_t size, int flags)
+		const struct path *path, const char *name,
+		const void *value, size_t size, int flags,
+		struct file *file)
 {
-	return cap_inode_setxattr(dentry, name, value, size, flags);
+	return cap_inode_setxattr(path, name, value, size, flags, file);
 }
 
 static inline int security_inode_set_acl(const struct mnt_idmap *idmap,
@@ -1056,22 +1063,24 @@ static inline void security_inode_post_setxattr(struct dentry *dentry,
 		const char *name, const void *value, size_t size, int flags)
 { }
 
-static inline int security_inode_getxattr(struct dentry *dentry,
-			const char *name)
+static inline int security_inode_getxattr(const struct path *path,
+			const char *name, struct file *file)
 {
 	return 0;
 }
 
-static inline int security_inode_listxattr(struct dentry *dentry)
+static inline int security_inode_listxattr(const struct path *path,
+					   struct file *file)
 {
 	return 0;
 }
 
 static inline int security_inode_removexattr(const struct mnt_idmap *idmap,
-					     struct dentry *dentry,
-					     const char *name)
+					     const struct path *path,
+					     const char *name,
+					     struct file *file)
 {
-	return cap_inode_removexattr(idmap, dentry, name);
+	return cap_inode_removexattr(idmap, path, name, file);
 }
 
 static inline void security_inode_post_removexattr(struct dentry *dentry,
diff --git a/include/linux/xattr.h b/include/linux/xattr.h
index 68301f0b3de2..158e269cfe7a 100644
--- a/include/linux/xattr.h
+++ b/include/linux/xattr.h
@@ -26,6 +26,7 @@
 
 struct inode;
 struct dentry;
+struct file;
 
 static inline bool is_posix_acl_xattr(const char *name)
 {
@@ -78,8 +79,10 @@ struct xattr {
 
 ssize_t __vfs_getxattr(struct dentry *, struct inode *, const char *, void *, size_t);
 ssize_t vfs_getxattr(const struct mnt_idmap *idmap, const struct path *path,
-		     const char *name, void *value, size_t size);
-ssize_t vfs_listxattr(const struct path *path, char *list, size_t size);
+		     const char *name, void *value, size_t size,
+		     struct file *file);
+ssize_t vfs_listxattr(const struct path *path, char *list, size_t size,
+		      struct file *file);
 int __vfs_setxattr(const struct mnt_idmap *, struct dentry *, struct inode *,
 		   const char *, const void *, size_t, int);
 int __vfs_setxattr_noperm(const struct mnt_idmap *, struct dentry *,
@@ -87,15 +90,18 @@ int __vfs_setxattr_noperm(const struct mnt_idmap *, struct dentry *,
 int __vfs_setxattr_locked(const struct mnt_idmap *idmap,
 			  const struct path *path, const char *name,
 			  const void *value, size_t size, int flags,
-			  struct delegated_inode *delegated_inode);
+			  struct delegated_inode *delegated_inode,
+			  struct file *file);
 int vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path,
-		 const char *name, const void *value, size_t size, int flags);
+		 const char *name, const void *value, size_t size, int flags,
+		 struct file *file);
 int __vfs_removexattr(const struct mnt_idmap *, struct dentry *, const char *);
 int __vfs_removexattr_locked(const struct mnt_idmap *idmap,
 			     const struct path *path, const char *name,
-			     struct delegated_inode *delegated_inode);
+			     struct delegated_inode *delegated_inode,
+			     struct file *file);
 int vfs_removexattr(const struct mnt_idmap *idmap, const struct path *path,
-		    const char *name);
+		    const char *name, struct file *file);
 
 ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size);
 int vfs_getxattr_alloc(const struct mnt_idmap *idmap,
diff --git a/security/commoncap.c b/security/commoncap.c
index 565c893c6d4b..0a94b9cd231f 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -14,6 +14,7 @@
 #include <linux/netlink.h>
 #include <linux/ptrace.h>
 #include <linux/xattr.h>
+#include <linux/path.h>
 #include <linux/mount.h>
 #include <linux/sched.h>
 #include <linux/prctl.h>
@@ -1007,11 +1008,12 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
 
 /**
  * cap_inode_setxattr - Determine whether an xattr may be altered
- * @dentry: The inode/dentry being altered
+ * @path: The inode/path being altered
  * @name: The name of the xattr to be changed
  * @value: The value that the xattr will be changed to
  * @size: The size of value
  * @flags: The replacement flag
+ * @file: the file through which the xattr change is requested, if any
  *
  * Determine whether an xattr may be altered or set on an inode, returning 0 if
  * permission is granted, -ve if denied.
@@ -1019,9 +1021,11 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
  * This is used to make sure security xattrs don't get updated or set by those
  * who aren't privileged to do so.
  */
-int cap_inode_setxattr(struct dentry *dentry, const char *name,
-		       const void *value, size_t size, int flags)
+int cap_inode_setxattr(const struct path *path, const char *name,
+		       const void *value, size_t size, int flags,
+		       struct file *file)
 {
+	struct dentry *dentry = path->dentry;
 	struct user_namespace *user_ns = dentry->d_sb->s_user_ns;
 
 	/* Ignore non-security xattrs */
@@ -1044,25 +1048,21 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name,
 /**
  * cap_inode_removexattr - Determine whether an xattr may be removed
  *
- * @idmap:	idmap of the mount the inode was found from
- * @dentry:	The inode/dentry being altered
+ * @path:	The inode/path being altered
  * @name:	The name of the xattr to be changed
+ * @file:	the file through which the xattr removal is requested, if any
  *
  * Determine whether an xattr may be removed from an inode, returning 0 if
  * permission is granted, -ve if denied.
  *
- * If the inode has been found through an idmapped mount the idmap of
- * the vfsmount must be passed through @idmap. This function will then
- * take care to map the inode according to @idmap before checking
- * permissions. On non-idmapped mounts or if permission checking is to be
- * performed on the raw inode simply pass @nop_mnt_idmap.
- *
  * This is used to make sure security xattrs don't get removed by those who
  * aren't privileged to remove them.
  */
 int cap_inode_removexattr(const struct mnt_idmap *idmap,
-			  struct dentry *dentry, const char *name)
+			  const struct path *path, const char *name,
+			  struct file *file)
 {
+	struct dentry *dentry = path->dentry;
 	struct user_namespace *user_ns = dentry->d_sb->s_user_ns;
 
 	/* Ignore non-security xattrs */
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 9526bffe34aa..2e1978cbe369 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -595,11 +595,12 @@ static int evm_protect_xattr(const struct mnt_idmap *idmap,
 /**
  * evm_inode_setxattr - protect the EVM extended attribute
  * @idmap: idmap of the mount
- * @dentry: pointer to the affected dentry
+ * @path: pointer to the affected object
  * @xattr_name: pointer to the affected extended attribute name
  * @xattr_value: pointer to the new extended attribute value
  * @xattr_value_len: pointer to the new extended attribute value length
  * @flags: flags to pass into filesystem operations
+ * @file: file through which the xattr change is requested, if any
  *
  * Before allowing the 'security.evm' protected xattr to be updated,
  * verify the existing value is valid.  As only the kernel should have
@@ -607,11 +608,14 @@ static int evm_protect_xattr(const struct mnt_idmap *idmap,
  * userspace from writing HMAC value.  Writing 'security.evm' requires
  * requires CAP_SYS_ADMIN privileges.
  */
-static int evm_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry,
+static int evm_inode_setxattr(const struct mnt_idmap *idmap,
+			      const struct path *path,
 			      const char *xattr_name, const void *xattr_value,
-			      size_t xattr_value_len, int flags)
+			      size_t xattr_value_len, int flags,
+			      struct file *file)
 {
 	const struct evm_ima_xattr_data *xattr_data = xattr_value;
+	struct dentry *dentry = path->dentry;
 
 	/* Policy permits modification of the protected xattrs even though
 	 * there's no HMAC key loaded
@@ -633,15 +637,19 @@ static int evm_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dent
 /**
  * evm_inode_removexattr - protect the EVM extended attribute
  * @idmap: idmap of the mount
- * @dentry: pointer to the affected dentry
+ * @path: pointer to the affected object
  * @xattr_name: pointer to the affected extended attribute name
+ * @file: file through which the xattr removal is requested, if any
  *
  * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that
  * the current value is valid.
  */
-static int evm_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry,
-				 const char *xattr_name)
+static int evm_inode_removexattr(const struct mnt_idmap *idmap,
+				 const struct path *path,
+				 const char *xattr_name, struct file *file)
 {
+	struct dentry *dentry = path->dentry;
+
 	/* Policy permits modification of the protected xattrs even though
 	 * there's no HMAC key loaded
 	 */
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 134174d2e3f0..ebf050195cf0 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -759,11 +759,14 @@ static int validate_hash_algo(struct dentry *dentry,
 	return -EACCES;
 }
 
-static int ima_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry,
+static int ima_inode_setxattr(const struct mnt_idmap *idmap,
+			      const struct path *path,
 			      const char *xattr_name, const void *xattr_value,
-			      size_t xattr_value_len, int flags)
+			      size_t xattr_value_len, int flags,
+			      struct file *file)
 {
 	const struct evm_ima_xattr_data *xvalue = xattr_value;
+	struct dentry *dentry = path->dentry;
 	int digsig = 0;
 	int result;
 	int err;
@@ -801,16 +804,17 @@ static int ima_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentr
 	return 0;
 }
 
-static int ima_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry,
-				 const char *xattr_name)
+static int ima_inode_removexattr(const struct mnt_idmap *idmap,
+				 const struct path *path,
+				 const char *xattr_name, struct file *file)
 {
 	int result, digsig = -1;
 
-	result = ima_protect_xattr(dentry, xattr_name, NULL, 0);
+	result = ima_protect_xattr(path->dentry, xattr_name, NULL, 0);
 	if (result == 1 || evm_revalidate_status(xattr_name)) {
 		if (!strcmp(xattr_name, XATTR_NAME_IMA))
 			digsig = 0;
-		ima_reset_appraise_flags(d_backing_inode(dentry), digsig);
+		ima_reset_appraise_flags(d_backing_inode(path->dentry), digsig);
 		if (result == 1)
 			result = 0;
 	}
diff --git a/security/security.c b/security/security.c
index 9c1e27f0e5cb..83132d40cc9c 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1922,14 +1922,15 @@ int security_inode_getattr(const struct path *path)
 /**
  * security_inode_setxattr() - Check if setting file xattrs is allowed
  * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @name: xattr name
  * @value: xattr value
  * @size: size of xattr value
  * @flags: flags
+ * @file: file through which the xattr change is requested, if any
  *
  * This hook performs the desired permission checks before setting the extended
- * attributes (xattrs) on @dentry.  It is important to note that we have some
+ * attributes (xattrs) on @path.  It is important to note that we have some
  * additional logic before the main LSM implementation calls to detect if we
  * need to perform an additional capability check at the LSM layer.
  *
@@ -1946,23 +1947,24 @@ int security_inode_getattr(const struct path *path)
  * Return: Returns 0 if permission is granted.
  */
 int security_inode_setxattr(const struct mnt_idmap *idmap,
-			    struct dentry *dentry, const char *name,
-			    const void *value, size_t size, int flags)
+			    const struct path *path, const char *name,
+			    const void *value, size_t size, int flags,
+			    struct file *file)
 {
 	int rc;
 
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
 
 	/* enforce the capability checks at the lsm layer, if needed */
 	if (!call_int_hook(inode_xattr_skipcap, name)) {
-		rc = cap_inode_setxattr(dentry, name, value, size, flags);
+		rc = cap_inode_setxattr(path, name, value, size, flags, file);
 		if (rc)
 			return rc;
 	}
 
-	return call_int_hook(inode_setxattr, idmap, dentry, name, value, size,
-			     flags);
+	return call_int_hook(inode_setxattr, idmap, path, name, value,
+			     size, flags, file);
 }
 
 /**
@@ -2078,45 +2080,49 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name,
 
 /**
  * security_inode_getxattr() - Check if xattr access is allowed
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @name: xattr name
+ * @file: file through which the xattr access is requested, if any
  *
  * Check permission before obtaining the extended attributes identified by
- * @name for @dentry.
+ * @name for @path.
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_getxattr(struct dentry *dentry, const char *name)
+int security_inode_getxattr(const struct path *path,
+			    const char *name, struct file *file)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_getxattr, dentry, name);
+	return call_int_hook(inode_getxattr, path, name, file);
 }
 
 /**
  * security_inode_listxattr() - Check if listing xattrs is allowed
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
+ * @file: file through which the xattr names are listed, if any
  *
  * Check permission before obtaining the list of extended attribute names for
- * @dentry.
+ * @path.
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_listxattr(struct dentry *dentry)
+int security_inode_listxattr(const struct path *path, struct file *file)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_listxattr, dentry);
+	return call_int_hook(inode_listxattr, path, file);
 }
 
 /**
  * security_inode_removexattr() - Check if removing an xattr is allowed
  * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @name: xattr name
+ * @file: file through which the xattr removal is requested, if any
  *
  * This hook performs the desired permission checks before setting the extended
- * attributes (xattrs) on @dentry.  It is important to note that we have some
+ * attributes (xattrs) on @path.  It is important to note that we have some
  * additional logic before the main LSM implementation calls to detect if we
  * need to perform an additional capability check at the LSM layer.
  *
@@ -2133,21 +2139,22 @@ int security_inode_listxattr(struct dentry *dentry)
  * Return: Returns 0 if permission is granted.
  */
 int security_inode_removexattr(const struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *name)
+			       const struct path *path, const char *name,
+			       struct file *file)
 {
 	int rc;
 
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
 
 	/* enforce the capability checks at the lsm layer, if needed */
 	if (!call_int_hook(inode_xattr_skipcap, name)) {
-		rc = cap_inode_removexattr(idmap, dentry, name);
+		rc = cap_inode_removexattr(idmap, path, name, file);
 		if (rc)
 			return rc;
 	}
 
-	return call_int_hook(inode_removexattr, idmap, dentry, name);
+	return call_int_hook(inode_removexattr, idmap, path, name, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 3d8636149f59..4a88bcdbd99a 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3389,9 +3389,11 @@ static int selinux_inode_xattr_skipcap(const char *name)
 }
 
 static int selinux_inode_setxattr(const struct mnt_idmap *idmap,
-				  struct dentry *dentry, const char *name,
-				  const void *value, size_t size, int flags)
+				  const struct path *path,
+				  const char *name, const void *value,
+				  size_t size, int flags, struct file *file)
 {
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_backing_inode(dentry);
 	struct inode_security_struct *isec;
 	struct superblock_security_struct *sbsec;
@@ -3533,26 +3535,29 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name,
 	spin_unlock(&isec->lock);
 }
 
-static int selinux_inode_getxattr(struct dentry *dentry, const char *name)
+static int selinux_inode_getxattr(const struct path *path,
+				  const char *name, struct file *file)
 {
 	const struct cred *cred = current_cred();
 
-	return dentry_has_perm(cred, dentry, FILE__GETATTR);
+	return dentry_has_perm(cred, path->dentry, FILE__GETATTR);
 }
 
-static int selinux_inode_listxattr(struct dentry *dentry)
+static int selinux_inode_listxattr(const struct path *path, struct file *file)
 {
 	const struct cred *cred = current_cred();
 
-	return dentry_has_perm(cred, dentry, FILE__GETATTR);
+	return dentry_has_perm(cred, path->dentry, FILE__GETATTR);
 }
 
 static int selinux_inode_removexattr(const struct mnt_idmap *idmap,
-				     struct dentry *dentry, const char *name)
+				     const struct path *path,
+				     const char *name, struct file *file)
 {
 	/* if not a selinux xattr, only check the ordinary setattr perm */
 	if (strcmp(name, XATTR_NAME_SELINUX))
-		return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
+		return dentry_has_perm(current_cred(), path->dentry,
+				       FILE__SETATTR);
 
 	if (!selinux_initialized())
 		return 0;
@@ -7148,7 +7153,7 @@ static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen
 static int selinux_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
 	return __vfs_setxattr_locked(&nop_mnt_idmap, path, XATTR_NAME_SELINUX,
-				     ctx, ctxlen, 0, NULL);
+				     ctx, ctxlen, 0, NULL, NULL);
 }
 
 static int selinux_inode_getsecctx(struct inode *inode, struct lsm_context *cp)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index ede1314030af..f739756acb3e 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1343,21 +1343,24 @@ static int smack_inode_xattr_skipcap(const char *name)
 /**
  * smack_inode_setxattr - Smack check for setting xattrs
  * @idmap: idmap of the mount
- * @dentry: the object
+ * @path: the object
  * @name: name of the attribute
  * @value: value of the attribute
  * @size: size of the value
  * @flags: unused
+ * @file: file through which the xattr change is requested, if any
  *
  * This protects the Smack attribute explicitly.
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
 static int smack_inode_setxattr(const struct mnt_idmap *idmap,
-				struct dentry *dentry, const char *name,
-				const void *value, size_t size, int flags)
+				const struct path *path,
+				const char *name, const void *value,
+				size_t size, int flags, struct file *file)
 {
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	struct smack_known *skp;
 	int check_priv = 0;
 	int check_import = 0;
@@ -1459,14 +1462,17 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name,
 
 /**
  * smack_inode_getxattr - Smack check on getxattr
- * @dentry: the object
+ * @path: the object
  * @name: unused
+ * @file: file through which the xattr access is requested, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_getxattr(struct dentry *dentry, const char *name)
+static int smack_inode_getxattr(const struct path *path,
+			const char *name, struct file *file)
 {
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	int rc;
 
 	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
@@ -1480,18 +1486,21 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name)
 /**
  * smack_inode_removexattr - Smack check on removexattr
  * @idmap: idmap of the mount
- * @dentry: the object
+ * @path: the object
  * @name: name of the attribute
+ * @file: file through which the xattr removal is requested, if any
  *
  * Removing the Smack attribute requires CAP_MAC_ADMIN
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
 static int smack_inode_removexattr(const struct mnt_idmap *idmap,
-				   struct dentry *dentry, const char *name)
+				   const struct path *path,
+				   const char *name, struct file *file)
 {
 	struct inode_smack *isp;
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	int rc = 0;
 
 	if (strcmp(name, XATTR_NAME_SMACK) == 0 ||
@@ -4984,7 +4993,7 @@ static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen)
 static int smack_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
 	return __vfs_setxattr_locked(&nop_mnt_idmap, path, XATTR_NAME_SMACK,
-				     ctx, ctxlen, 0, NULL);
+				     ctx, ctxlen, 0, NULL, NULL);
 }
 
 static int smack_inode_getsecctx(struct inode *inode, struct lsm_context *cp)
diff --git a/tools/testing/selftests/bpf/progs/test_get_xattr.c b/tools/testing/selftests/bpf/progs/test_get_xattr.c
index 54305f4c9f2d..97361ff10e9f 100644
--- a/tools/testing/selftests/bpf/progs/test_get_xattr.c
+++ b/tools/testing/selftests/bpf/progs/test_get_xattr.c
@@ -56,7 +56,8 @@ int BPF_PROG(test_file_open, struct file *f)
 }
 
 SEC("lsm.s/inode_getxattr")
-int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
+int BPF_PROG(test_inode_getxattr, const struct path *path, char *name,
+	     struct file *file)
 {
 	struct bpf_dynptr value_ptr;
 	__u32 pid;
@@ -69,7 +70,7 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
 	bpf_dynptr_from_mem(value2, sizeof(value2), 0, &value_ptr);
 
 	for (i = 0; i < ARRAY_SIZE(xattr_names); i++) {
-		ret = bpf_get_dentry_xattr(dentry, xattr_names[i], &value_ptr);
+		ret = bpf_get_dentry_xattr(path->dentry, xattr_names[i], &value_ptr);
 		if (ret == sizeof(expected_value))
 			break;
 	}
diff --git a/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c b/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
index 6a612cf168d3..06656ac9a777 100644
--- a/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
+++ b/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
@@ -33,7 +33,8 @@ static inline bool name_match_foo(const char *name)
 
 /* Test bpf_set_dentry_xattr and bpf_remove_dentry_xattr */
 SEC("lsm.s/inode_getxattr")
-int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
+int BPF_PROG(test_inode_getxattr, const struct path *path, char *name,
+	     struct file *file)
 {
 	struct bpf_dynptr value_ptr;
 	__u32 pid;
@@ -50,25 +51,25 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
 	bpf_dynptr_from_mem(read_value, sizeof(read_value), 0, &value_ptr);
 
 	/* read security.bpf.bar */
-	ret = bpf_get_dentry_xattr(dentry, xattr_bar, &value_ptr);
+	ret = bpf_get_dentry_xattr(path->dentry, xattr_bar, &value_ptr);
 
 	if (ret < 0) {
 		/* If security.bpf.bar doesn't exist, set it */
 		bpf_dynptr_from_mem(value_bar, sizeof(value_bar), 0, &value_ptr);
 
-		ret = bpf_set_dentry_xattr(dentry, xattr_bar, &value_ptr, 0);
+		ret = bpf_set_dentry_xattr(path->dentry, xattr_bar, &value_ptr, 0);
 		if (!ret)
 			set_security_bpf_bar_success = true;
-		ret = bpf_set_dentry_xattr(dentry, xattr_selinux, &value_ptr, 0);
+		ret = bpf_set_dentry_xattr(path->dentry, xattr_selinux, &value_ptr, 0);
 		if (ret)
 			set_security_selinux_fail = true;
 	} else {
 		/* If security.bpf.bar exists, remove it */
-		ret = bpf_remove_dentry_xattr(dentry, xattr_bar);
+		ret = bpf_remove_dentry_xattr(path->dentry, xattr_bar);
 		if (!ret)
 			remove_security_bpf_bar_success = true;
 
-		ret = bpf_remove_dentry_xattr(dentry, xattr_selinux);
+		ret = bpf_remove_dentry_xattr(path->dentry, xattr_selinux);
 		if (ret)
 			remove_security_selinux_fail = true;
 	}
@@ -87,9 +88,10 @@ bool locked_remove_security_selinux_fail;
  * properly.
  */
 SEC("lsm.s/inode_setxattr")
-int BPF_PROG(test_inode_setxattr, struct mnt_idmap *idmap,
-	     struct dentry *dentry, const char *name,
-	     const void *value, size_t size, int flags)
+int BPF_PROG(test_inode_setxattr, const struct mnt_idmap *idmap,
+	     const struct path *path,
+	     const char *name, const void *value, size_t size, int flags,
+	     struct file *file)
 {
 	struct bpf_dynptr value_ptr;
 	__u32 pid;
@@ -106,25 +108,25 @@ int BPF_PROG(test_inode_setxattr, struct mnt_idmap *idmap,
 	bpf_dynptr_from_mem(read_value, sizeof(read_value), 0, &value_ptr);
 
 	/* read security.bpf.bar */
-	ret = bpf_get_dentry_xattr(dentry, xattr_bar, &value_ptr);
+	ret = bpf_get_dentry_xattr(path->dentry, xattr_bar, &value_ptr);
 
 	if (ret < 0) {
 		/* If security.bpf.bar doesn't exist, set it */
 		bpf_dynptr_from_mem(value_bar, sizeof(value_bar), 0, &value_ptr);
 
-		ret = bpf_set_dentry_xattr(dentry, xattr_bar, &value_ptr, 0);
+		ret = bpf_set_dentry_xattr(path->dentry, xattr_bar, &value_ptr, 0);
 		if (!ret)
 			locked_set_security_bpf_bar_success = true;
-		ret = bpf_set_dentry_xattr(dentry, xattr_selinux, &value_ptr, 0);
+		ret = bpf_set_dentry_xattr(path->dentry, xattr_selinux, &value_ptr, 0);
 		if (ret)
 			locked_set_security_selinux_fail = true;
 	} else {
 		/* If security.bpf.bar exists, remove it */
-		ret = bpf_remove_dentry_xattr(dentry, xattr_bar);
+		ret = bpf_remove_dentry_xattr(path->dentry, xattr_bar);
 		if (!ret)
 			locked_remove_security_bpf_bar_success = true;
 
-		ret = bpf_remove_dentry_xattr(dentry, xattr_selinux);
+		ret = bpf_remove_dentry_xattr(path->dentry, xattr_selinux);
 		if (ret)
 			locked_remove_security_selinux_fail = true;
 	}
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
index b706bf17b556..9b7a15c1862f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
@@ -234,8 +234,10 @@ int BPF_PROG(reject_lsm_cgroup, struct inode *inode, struct inode *dir,
 
 SEC("lsm/inode_setxattr")
 __failure __msg("calling kernel function bpf_inode_init_xattr is not allowed")
-int BPF_PROG(reject_wrong_hook, struct mnt_idmap *idmap, struct dentry *dentry,
-	     const char *name, const void *value, size_t size, int flags)
+int BPF_PROG(reject_wrong_hook, const struct mnt_idmap *idmap,
+	     const struct path *path,
+	     const char *name, const void *value, size_t size, int flags,
+	     struct file *file)
 {
 	struct bpf_dynptr val;
 
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 07/18] LSM: pass struct path and struct file to the inode posix acl hooks
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (5 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 08/18] LSM: pass struct path and struct file to the inode fileattr hooks Cai Xinchen
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_set_acl, inode_get_acl and inode_remove_acl hooks are
called from fs/posix_acl.c, whose helpers now hold a struct path and
used to derive the dentry from it just for the hook calls.

Convert the hooks and their SELinux, Smack, EVM and IMA
implementations to take a const struct path, keeping the idmap as a
separate argument.  The implementations derive the dentry they still
need from the path, so this is a purely mechanical change with no
behavior change.

Also pass the struct file through which a posix acl operation is
requested, when there is one, so that LSMs can later authorize
file descriptor-based operations (fsetxattr(2), fgetxattr(2) and
fremovexattr(2) on the system.posix_acl_access and
system.posix_acl_default names, the *xattrat(2) syscalls with
AT_EMPTY_PATH, and io_uring's IORING_OP_FSETXATTR and
IORING_OP_FGETXATTR) with the rights available at open(2) time.
The file is threaded through vfs_set_acl(), vfs_get_acl(),
vfs_remove_acl(), do_set_acl() and do_get_acl(), and set to NULL
for path-based operations and for in-kernel users: overlayfs,
ecryptfs and the SMB server.  No implementation uses the file yet,
so this part is a purely mechanical change with no behavior change
either.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/ecryptfs/inode.c                   |  5 ++--
 fs/internal.h                         | 11 +++++---
 fs/overlayfs/inode.c                  |  4 +--
 fs/overlayfs/overlayfs.h              |  6 +++--
 fs/posix_acl.c                        | 26 +++++++++++-------
 fs/smb/server/vfs.c                   |  2 +-
 fs/xattr.c                            |  7 ++---
 include/linux/lsm_hook_defs.h         |  9 ++++---
 include/linux/posix_acl.h             | 14 +++++-----
 include/linux/security.h              | 25 +++++++++++-------
 security/integrity/evm/evm_main.c     | 22 ++++++++++------
 security/integrity/ima/ima_appraise.c | 15 ++++++-----
 security/security.c                   | 32 +++++++++++++---------
 security/selinux/hooks.c              | 17 +++++++-----
 security/smack/smack_lsm.c            | 38 ++++++++++++++++-----------
 15 files changed, 140 insertions(+), 93 deletions(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 782bcca83874..fcb5b4de897e 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -1097,7 +1097,8 @@ static struct posix_acl *ecryptfs_get_acl(const struct mnt_idmap *idmap,
 {
 	struct path lower_path = ecryptfs_lower_path(dentry);
 
-	return vfs_get_acl(idmap, &lower_path, posix_acl_xattr_name(type));
+	return vfs_get_acl(idmap, &lower_path, posix_acl_xattr_name(type),
+			   NULL);
 }
 
 static int ecryptfs_set_acl(const struct mnt_idmap *idmap,
@@ -1109,7 +1110,7 @@ static int ecryptfs_set_acl(const struct mnt_idmap *idmap,
 	struct inode *lower_inode = d_inode(lower_path.dentry);
 
 	rc = vfs_set_acl(&nop_mnt_idmap, &lower_path,
-			 posix_acl_xattr_name(type), acl);
+			 posix_acl_xattr_name(type), acl, NULL);
 	if (!rc)
 		fsstack_copy_attr_all(d_inode(dentry), lower_inode);
 	return rc;
diff --git a/fs/internal.h b/fs/internal.h
index e90de99de5ea..4ba895665889 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -307,20 +307,23 @@ int may_write_xattr(const struct mnt_idmap *idmap, struct inode *inode);
 
 #ifdef CONFIG_FS_POSIX_ACL
 int do_set_acl(const struct mnt_idmap *idmap, const struct path *path,
-	       const char *acl_name, const void *kvalue, size_t size);
+	       const char *acl_name, const void *kvalue, size_t size,
+	       struct file *file);
 ssize_t do_get_acl(const struct mnt_idmap *idmap, const struct path *path,
-		   const char *acl_name, void *kvalue, size_t size);
+		   const char *acl_name, void *kvalue, size_t size,
+		   struct file *file);
 #else
 static inline int do_set_acl(const struct mnt_idmap *idmap,
 			     const struct path *path, const char *acl_name,
-			     const void *kvalue, size_t size)
+			     const void *kvalue, size_t size,
+			     struct file *file)
 {
 	return -EOPNOTSUPP;
 }
 static inline ssize_t do_get_acl(const struct mnt_idmap *idmap,
 				 const struct path *path,
 				 const char *acl_name, void *kvalue,
-				 size_t size)
+				 size_t size, struct file *file)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index c50c9899ab09..eb408284df08 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -414,7 +414,7 @@ struct posix_acl *ovl_get_acl_path(const struct path *path,
 	if (noperm)
 		real_acl = get_inode_acl(realinode, posix_acl_type(acl_name));
 	else
-		real_acl = vfs_get_acl(idmap, path, acl_name);
+		real_acl = vfs_get_acl(idmap, path, acl_name, NULL);
 	if (IS_ERR_OR_NULL(real_acl))
 		return real_acl;
 
@@ -503,7 +503,7 @@ static int ovl_set_or_remove_acl(struct dentry *dentry, struct inode *inode,
 		ovl_path_lower(dentry, &realpath);
 		with_ovl_creds(dentry->d_sb)
 			real_acl = vfs_get_acl(mnt_idmap(realpath.mnt),
-					       &realpath, acl_name);
+					       &realpath, acl_name, NULL);
 		if (IS_ERR(real_acl)) {
 			err = PTR_ERR(real_acl);
 			goto out;
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index 9ffa985fe8f2..9e969ab6ec40 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -378,7 +378,8 @@ static inline int ovl_do_set_acl(struct ovl_fs *ofs, struct dentry *dentry,
 		.dentry = dentry,
 	};
 
-	return vfs_set_acl(ovl_upper_mnt_idmap(ofs), &path, acl_name, acl);
+	return vfs_set_acl(ovl_upper_mnt_idmap(ofs), &path, acl_name, acl,
+		   NULL);
 }
 
 static inline int ovl_do_remove_acl(struct ovl_fs *ofs, struct dentry *dentry,
@@ -389,7 +390,8 @@ static inline int ovl_do_remove_acl(struct ovl_fs *ofs, struct dentry *dentry,
 		.dentry = dentry,
 	};
 
-	return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), &path, acl_name);
+	return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), &path, acl_name,
+			 NULL);
 }
 
 static inline int ovl_do_rename_rd(struct renamedata *rd)
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 1dbc59f50932..487e17e91e2b 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -1085,6 +1085,7 @@ static int vfs_set_acl_idmapped_mnt(const struct mnt_idmap *idmap,
  * @path: the path based on which to set the posix acls
  * @acl_name: the name of the posix acl
  * @kacl: the posix acls in the appropriate VFS format
+ * @file: file through which the posix acls are set, or NULL
  *
  * This function sets @kacl. The caller must all posix_acl_release() on @kacl
  * afterwards.
@@ -1092,7 +1093,8 @@ static int vfs_set_acl_idmapped_mnt(const struct mnt_idmap *idmap,
  * Return: On success 0, on error negative errno.
  */
 int vfs_set_acl(const struct mnt_idmap *idmap, const struct path *path,
-		const char *acl_name, struct posix_acl *kacl)
+		const char *acl_name, struct posix_acl *kacl,
+		struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	int acl_type;
@@ -1128,7 +1130,7 @@ int vfs_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		goto out_inode_unlock;
 
-	error = security_inode_set_acl(idmap, dentry, acl_name, kacl);
+	error = security_inode_set_acl(idmap, path, acl_name, kacl, file);
 	if (error)
 		goto out_inode_unlock;
 
@@ -1163,6 +1165,7 @@ EXPORT_SYMBOL_GPL(vfs_set_acl);
  * @idmap: idmap of the mount the posix acls are retrieved through
  * @path: the path based on which to retrieve the posix acls
  * @acl_name: the name of the posix acl
+ * @file: file through which the posix acls are retrieved, or NULL
  *
  * This function retrieves @kacl from the filesystem. The caller must all
  * posix_acl_release() on @kacl.
@@ -1171,7 +1174,7 @@ EXPORT_SYMBOL_GPL(vfs_set_acl);
  */
 struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
 			      const struct path *path,
-			      const char *acl_name)
+			      const char *acl_name, struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
@@ -1186,7 +1189,7 @@ struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
 	 * The VFS has no restrictions on reading POSIX ACLs so calling
 	 * something like xattr_permission() isn't needed. Only LSMs get a say.
 	 */
-	error = security_inode_get_acl(idmap, dentry, acl_name);
+	error = security_inode_get_acl(idmap, path, acl_name, file);
 	if (error)
 		return ERR_PTR(error);
 
@@ -1210,13 +1213,14 @@ EXPORT_SYMBOL_GPL(vfs_get_acl);
  * @idmap: idmap of the mount the posix acls are removed through
  * @path: the path based on which to retrieve the posix acls
  * @acl_name: the name of the posix acl
+ * @file: file through which the posix acls are removed, or NULL
  *
  * This function removes posix acls.
  *
  * Return: On success 0, on error negative errno.
  */
 int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
-		   const char *acl_name)
+		   const char *acl_name, struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 	int acl_type;
@@ -1239,7 +1243,7 @@ int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		goto out_inode_unlock;
 
-	error = security_inode_remove_acl(idmap, dentry, acl_name);
+	error = security_inode_remove_acl(idmap, path, acl_name, file);
 	if (error)
 		goto out_inode_unlock;
 
@@ -1270,7 +1274,8 @@ int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
 EXPORT_SYMBOL_GPL(vfs_remove_acl);
 
 int do_set_acl(const struct mnt_idmap *idmap, const struct path *path,
-	       const char *acl_name, const void *kvalue, size_t size)
+	       const char *acl_name, const void *kvalue, size_t size,
+	       struct file *file)
 {
 	int error;
 	struct posix_acl *acl = NULL;
@@ -1285,18 +1290,19 @@ int do_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 			return PTR_ERR(acl);
 	}
 
-	error = vfs_set_acl(idmap, path, acl_name, acl);
+	error = vfs_set_acl(idmap, path, acl_name, acl, file);
 	posix_acl_release(acl);
 	return error;
 }
 
 ssize_t do_get_acl(const struct mnt_idmap *idmap, const struct path *path,
-		   const char *acl_name, void *kvalue, size_t size)
+		   const char *acl_name, void *kvalue, size_t size,
+		   struct file *file)
 {
 	ssize_t error;
 	struct posix_acl *acl;
 
-	acl = vfs_get_acl(idmap, path, acl_name);
+	acl = vfs_get_acl(idmap, path, acl_name, file);
 	if (IS_ERR(acl))
 		return PTR_ERR(acl);
 
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index cfb9a811363e..54b5f17995c3 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1564,7 +1564,7 @@ int ksmbd_vfs_remove_acl_xattrs(const struct mnt_idmap *idmap,
 			     sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1) ||
 		    !strncmp(name, XATTR_NAME_POSIX_ACL_DEFAULT,
 			     sizeof(XATTR_NAME_POSIX_ACL_DEFAULT) - 1)) {
-			err = vfs_remove_acl(idmap, path, name);
+			err = vfs_remove_acl(idmap, path, name, NULL);
 			if (err)
 				ksmbd_debug(SMB,
 					    "remove acl xattr failed : %s\n", name);
diff --git a/fs/xattr.c b/fs/xattr.c
index 747c041e66fa..7ef3d28e5979 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -670,7 +670,7 @@ static int do_setxattr(const struct mnt_idmap *idmap, const struct path *path,
 {
 	if (is_posix_acl_xattr(ctx->kname->name))
 		return do_set_acl(idmap, path, ctx->kname->name,
-				  ctx->kvalue, ctx->size);
+				  ctx->kvalue, ctx->size, file);
 
 	return vfs_setxattr(idmap, path, ctx->kname->name,
 			ctx->kvalue, ctx->size, ctx->flags, file);
@@ -816,7 +816,8 @@ do_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 	}
 
 	if (is_posix_acl_xattr(kname))
-		error = do_get_acl(idmap, path, kname, kvalue, ctx->size);
+		error = do_get_acl(idmap, path, kname, kvalue,
+				   ctx->size, file);
 	else
 		error = vfs_getxattr(idmap, path, kname, kvalue,
 				     ctx->size, file);
@@ -1048,7 +1049,7 @@ removexattr(const struct mnt_idmap *idmap, const struct path *path,
 	    const char *name, struct file *file)
 {
 	if (is_posix_acl_xattr(name))
-		return vfs_remove_acl(idmap, path, name);
+		return vfs_remove_acl(idmap, path, name, file);
 	return vfs_removexattr(idmap, path, name, file);
 }
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 2315961b6d71..8b191df5b169 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -171,13 +171,16 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry,
 LSM_HOOK(int, 0, inode_file_setattr, struct dentry *dentry, struct file_kattr *fa)
 LSM_HOOK(int, 0, inode_file_getattr, struct dentry *dentry, struct file_kattr *fa)
 LSM_HOOK(int, 0, inode_set_acl, const struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *acl_name, struct posix_acl *kacl)
+	 const struct path *path, const char *acl_name,
+	 struct posix_acl *kacl, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_set_acl, struct dentry *dentry,
 	 const char *acl_name, struct posix_acl *kacl)
 LSM_HOOK(int, 0, inode_get_acl, const struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *acl_name)
+	 const struct path *path, const char *acl_name,
+	 struct file *file)
 LSM_HOOK(int, 0, inode_remove_acl, const struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *acl_name)
+	 const struct path *path, const char *acl_name,
+	 struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_remove_acl, const struct mnt_idmap *idmap,
 	 struct dentry *dentry, const char *acl_name)
 LSM_HOOK(int, 0, inode_need_killpriv, struct dentry *dentry)
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 4bd2ed8378c9..f5e381553fee 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -106,12 +106,13 @@ static inline void cache_no_acl(struct inode *inode)
 }
 
 int vfs_set_acl(const struct mnt_idmap *idmap, const struct path *path,
-		const char *acl_name, struct posix_acl *kacl);
+		const char *acl_name, struct posix_acl *kacl,
+		struct file *file);
 struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
 			      const struct path *path,
-			      const char *acl_name);
+			      const char *acl_name, struct file *file);
 int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
-		   const char *acl_name);
+		   const char *acl_name, struct file *file);
 int posix_acl_listxattr(struct inode *inode, char **buffer,
 			ssize_t *remaining_size);
 #else
@@ -144,21 +145,22 @@ static inline void forget_all_cached_acls(struct inode *inode)
 
 static inline int vfs_set_acl(const struct mnt_idmap *idmap,
 			      const struct path *path, const char *name,
-			      struct posix_acl *acl)
+			      struct posix_acl *acl, struct file *file)
 {
 	return -EOPNOTSUPP;
 }
 
 static inline struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
 					    const struct path *path,
-					    const char *acl_name)
+					    const char *acl_name,
+					    struct file *file)
 {
 	return ERR_PTR(-EOPNOTSUPP);
 }
 
 static inline int vfs_remove_acl(const struct mnt_idmap *idmap,
 				 const struct path *path,
-				 const char *acl_name)
+				 const char *acl_name, struct file *file)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/include/linux/security.h b/include/linux/security.h
index 35693bc328d3..27291542a7e2 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -441,14 +441,16 @@ int security_inode_setxattr(const struct mnt_idmap *idmap,
 			    const void *value, size_t size, int flags,
 			    struct file *file);
 int security_inode_set_acl(const struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name,
-			   struct posix_acl *kacl);
+			   const struct path *path, const char *acl_name,
+			   struct posix_acl *kacl, struct file *file);
 void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
 				 struct posix_acl *kacl);
 int security_inode_get_acl(const struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name);
+			   const struct path *path, const char *acl_name,
+			   struct file *file);
 int security_inode_remove_acl(const struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name);
+			      const struct path *path, const char *acl_name,
+			      struct file *file);
 void security_inode_post_remove_acl(const struct mnt_idmap *idmap,
 				    struct dentry *dentry,
 				    const char *acl_name);
@@ -1028,9 +1030,10 @@ static inline int security_inode_setxattr(const struct mnt_idmap *idmap,
 }
 
 static inline int security_inode_set_acl(const struct mnt_idmap *idmap,
-					 struct dentry *dentry,
+					 const struct path *path,
 					 const char *acl_name,
-					 struct posix_acl *kacl)
+					 struct posix_acl *kacl,
+					 struct file *file)
 {
 	return 0;
 }
@@ -1041,15 +1044,17 @@ static inline void security_inode_post_set_acl(struct dentry *dentry,
 { }
 
 static inline int security_inode_get_acl(const struct mnt_idmap *idmap,
-					 struct dentry *dentry,
-					 const char *acl_name)
+					 const struct path *path,
+					 const char *acl_name,
+					 struct file *file)
 {
 	return 0;
 }
 
 static inline int security_inode_remove_acl(const struct mnt_idmap *idmap,
-					    struct dentry *dentry,
-					    const char *acl_name)
+					    const struct path *path,
+					    const char *acl_name,
+					    struct file *file)
 {
 	return 0;
 }
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 2e1978cbe369..5c24111a9ba7 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -690,10 +690,11 @@ static inline int evm_inode_set_acl_change(const struct mnt_idmap *idmap,
 
 /**
  * evm_inode_set_acl - protect the EVM extended attribute from posix acls
- * @idmap: idmap of the idmapped mount
- * @dentry: pointer to the affected dentry
+ * @idmap: idmap of the mount
+ * @path: pointer to the affected object
  * @acl_name: name of the posix acl
  * @kacl: pointer to the posix acls
+ * @file: file through which the posix acls are set, if any
  *
  * Prevent modifying posix acls causing the EVM HMAC to be re-calculated
  * and 'security.evm' xattr updated, unless the existing 'security.evm' is
@@ -701,10 +702,13 @@ static inline int evm_inode_set_acl_change(const struct mnt_idmap *idmap,
  *
  * Return: zero on success, -EPERM on failure.
  */
-static int evm_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
-			     const char *acl_name, struct posix_acl *kacl)
+static int evm_inode_set_acl(const struct mnt_idmap *idmap,
+			     const struct path *path,
+			     const char *acl_name, struct posix_acl *kacl,
+			     struct file *file)
 {
 	enum integrity_status evm_status;
+	struct dentry *dentry = path->dentry;
 
 	/* Policy permits modification of the protected xattrs even though
 	 * there's no HMAC key loaded
@@ -744,8 +748,9 @@ static int evm_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentr
 /**
  * evm_inode_remove_acl - Protect the EVM extended attribute from posix acls
  * @idmap: idmap of the mount
- * @dentry: pointer to the affected dentry
+ * @path: pointer to the affected object
  * @acl_name: name of the posix acl
+ * @file: file through which the posix acls are removed, if any
  *
  * Prevent removing posix acls causing the EVM HMAC to be re-calculated
  * and 'security.evm' xattr updated, unless the existing 'security.evm' is
@@ -753,10 +758,11 @@ static int evm_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentr
  *
  * Return: zero on success, -EPERM on failure.
  */
-static int evm_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
-				const char *acl_name)
+static int evm_inode_remove_acl(const struct mnt_idmap *idmap,
+				const struct path *path,
+				const char *acl_name, struct file *file)
 {
-	return evm_inode_set_acl(idmap, dentry, acl_name, NULL);
+	return evm_inode_set_acl(idmap, path, acl_name, NULL, file);
 }
 
 static void evm_reset_status(struct inode *inode)
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index ebf050195cf0..a231f1f419f7 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -795,11 +795,13 @@ static int ima_inode_setxattr(const struct mnt_idmap *idmap,
 	return result;
 }
 
-static int ima_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
-			     const char *acl_name, struct posix_acl *kacl)
+static int ima_inode_set_acl(const struct mnt_idmap *idmap,
+			     const struct path *path,
+			     const char *acl_name, struct posix_acl *kacl,
+			     struct file *file)
 {
 	if (evm_revalidate_status(acl_name))
-		ima_reset_appraise_flags(d_backing_inode(dentry), -1);
+		ima_reset_appraise_flags(d_backing_inode(path->dentry), -1);
 
 	return 0;
 }
@@ -821,10 +823,11 @@ static int ima_inode_removexattr(const struct mnt_idmap *idmap,
 	return result;
 }
 
-static int ima_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
-				const char *acl_name)
+static int ima_inode_remove_acl(const struct mnt_idmap *idmap,
+				 const struct path *path,
+				 const char *acl_name, struct file *file)
 {
-	return ima_inode_set_acl(idmap, dentry, acl_name, NULL);
+	return ima_inode_set_acl(idmap, path, acl_name, NULL, file);
 }
 
 static struct security_hook_list ima_appraise_hooks[] __ro_after_init = {
diff --git a/security/security.c b/security/security.c
index 83132d40cc9c..af1777f2aa5e 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1970,9 +1970,10 @@ int security_inode_setxattr(const struct mnt_idmap *idmap,
 /**
  * security_inode_set_acl() - Check if setting posix acls is allowed
  * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @acl_name: acl name
  * @kacl: acl struct
+ * @file: file through which the posix acls are set, if any
  *
  * Check permission before setting posix acls, the posix acls in @kacl are
  * identified by @acl_name.
@@ -1980,12 +1981,13 @@ int security_inode_setxattr(const struct mnt_idmap *idmap,
  * Return: Returns 0 if permission is granted.
  */
 int security_inode_set_acl(const struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name,
-			   struct posix_acl *kacl)
+			   const struct path *path, const char *acl_name,
+			   struct posix_acl *kacl, struct file *file)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_set_acl, idmap, dentry, acl_name, kacl);
+	return call_int_hook(inode_set_acl, idmap, path, acl_name, kacl,
+			       file);
 }
 
 /**
@@ -2008,8 +2010,9 @@ void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
 /**
  * security_inode_get_acl() - Check if reading posix acls is allowed
  * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @acl_name: acl name
+ * @file: file through which the posix acls are retrieved, if any
  *
  * Check permission before getting osix acls, the posix acls are identified by
  * @acl_name.
@@ -2017,18 +2020,20 @@ void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
  * Return: Returns 0 if permission is granted.
  */
 int security_inode_get_acl(const struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name)
+			   const struct path *path, const char *acl_name,
+			   struct file *file)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_get_acl, idmap, dentry, acl_name);
+	return call_int_hook(inode_get_acl, idmap, path, acl_name, file);
 }
 
 /**
  * security_inode_remove_acl() - Check if removing a posix acl is allowed
  * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @acl_name: acl name
+ * @file: file through which the posix acls are removed, if any
  *
  * Check permission before removing posix acls, the posix acls are identified
  * by @acl_name.
@@ -2036,11 +2041,12 @@ int security_inode_get_acl(const struct mnt_idmap *idmap,
  * Return: Returns 0 if permission is granted.
  */
 int security_inode_remove_acl(const struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name)
+			      const struct path *path, const char *acl_name,
+			      struct file *file)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_remove_acl, idmap, dentry, acl_name);
+	return call_int_hook(inode_remove_acl, idmap, path, acl_name, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 4a88bcdbd99a..2b5efe041bc1 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3477,22 +3477,25 @@ static int selinux_inode_setxattr(const struct mnt_idmap *idmap,
 }
 
 static int selinux_inode_set_acl(const struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name,
-				 struct posix_acl *kacl)
+				 const struct path *path,
+				 const char *acl_name, struct posix_acl *kacl,
+				 struct file *file)
 {
-	return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
+	return dentry_has_perm(current_cred(), path->dentry, FILE__SETATTR);
 }
 
 static int selinux_inode_get_acl(const struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name)
+				 const struct path *path,
+				 const char *acl_name, struct file *file)
 {
-	return dentry_has_perm(current_cred(), dentry, FILE__GETATTR);
+	return dentry_has_perm(current_cred(), path->dentry, FILE__GETATTR);
 }
 
 static int selinux_inode_remove_acl(const struct mnt_idmap *idmap,
-				    struct dentry *dentry, const char *acl_name)
+				    const struct path *path,
+				    const char *acl_name, struct file *file)
 {
-	return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
+	return dentry_has_perm(current_cred(), path->dentry, FILE__SETATTR);
 }
 
 static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name,
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index f739756acb3e..e3445a4f6a45 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1551,68 +1551,74 @@ static int smack_inode_removexattr(const struct mnt_idmap *idmap,
 /**
  * smack_inode_set_acl - Smack check for setting posix acls
  * @idmap: idmap of the mnt this request came from
- * @dentry: the object
+ * @path: the object
  * @acl_name: name of the posix acl
  * @kacl: the posix acls
+ * @file: file through which the posix acls are set, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
 static int smack_inode_set_acl(const struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *acl_name,
-			       struct posix_acl *kacl)
+			       const struct path *path,
+			       const char *acl_name, struct posix_acl *kacl,
+			       struct file *file)
 {
 	struct smk_audit_info ad;
 	int rc;
 
 	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
-	smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
+	smk_ad_setfield_u_fs_path_dentry(&ad, path->dentry);
 
-	rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad);
-	rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc);
+	rc = smk_curacc(smk_of_inode(d_backing_inode(path->dentry)), MAY_WRITE, &ad);
+	rc = smk_bu_inode(d_backing_inode(path->dentry), MAY_WRITE, rc);
 	return rc;
 }
 
 /**
  * smack_inode_get_acl - Smack check for getting posix acls
  * @idmap: idmap of the mnt this request came from
- * @dentry: the object
+ * @path: the object
  * @acl_name: name of the posix acl
+ * @file: file through which the posix acls are retrieved, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
 static int smack_inode_get_acl(const struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *acl_name)
+			       const struct path *path,
+			       const char *acl_name, struct file *file)
 {
 	struct smk_audit_info ad;
 	int rc;
 
 	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
-	smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
+	smk_ad_setfield_u_fs_path_dentry(&ad, path->dentry);
 
-	rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_READ, &ad);
-	rc = smk_bu_inode(d_backing_inode(dentry), MAY_READ, rc);
+	rc = smk_curacc(smk_of_inode(d_backing_inode(path->dentry)), MAY_READ, &ad);
+	rc = smk_bu_inode(d_backing_inode(path->dentry), MAY_READ, rc);
 	return rc;
 }
 
 /**
  * smack_inode_remove_acl - Smack check for getting posix acls
  * @idmap: idmap of the mnt this request came from
- * @dentry: the object
+ * @path: the object
  * @acl_name: name of the posix acl
+ * @file: file through which the posix acls are removed, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
 static int smack_inode_remove_acl(const struct mnt_idmap *idmap,
-				  struct dentry *dentry, const char *acl_name)
+				  const struct path *path,
+				  const char *acl_name, struct file *file)
 {
 	struct smk_audit_info ad;
 	int rc;
 
 	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
-	smk_ad_setfield_u_fs_path_dentry(&ad, dentry);
+	smk_ad_setfield_u_fs_path_dentry(&ad, path->dentry);
 
-	rc = smk_curacc(smk_of_inode(d_backing_inode(dentry)), MAY_WRITE, &ad);
-	rc = smk_bu_inode(d_backing_inode(dentry), MAY_WRITE, rc);
+	rc = smk_curacc(smk_of_inode(d_backing_inode(path->dentry)), MAY_WRITE, &ad);
+	rc = smk_bu_inode(d_backing_inode(path->dentry), MAY_WRITE, rc);
 	return rc;
 }
 
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 08/18] LSM: pass struct path and struct file to the inode fileattr hooks
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (6 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 07/18] LSM: pass struct path and struct file to the inode posix acl hooks Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 13:45   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 09/18] LSM: rename inode_getattr to path_getattr and pass struct file Cai Xinchen
                   ` (9 subsequent siblings)
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_file_getattr and inode_file_setattr hooks are called from
vfs_fileattr_get() and vfs_fileattr_set(), whose callers -- the
FS_IOC_GETFLAGS, FS_IOC_SETFLAGS, FS_IOC_FSGETXATTR and
FS_IOC_FSSETXATTR ioctls and the file_getattr(2) and file_setattr(2)
syscalls -- all operate on a struct file or a struct path and had to
derive the dentry from it just for the fileattr helpers and the
hooks.  Switch the helpers and the hooks, along with their SELinux
implementations, to take a const struct path, keeping the idmap as a
separate argument.

Also pass the struct file through which a fileattr operation is
requested, when there is one, so that LSMs can later authorize
file descriptor-based operations (the FS_IOC_GETFLAGS,
FS_IOC_SETFLAGS, FS_IOC_FSGETXATTR and FS_IOC_FSSETXATTR ioctls,
and the file_getattr(2) and file_setattr(2) syscalls with
AT_EMPTY_PATH) with the rights available at open(2) time.  The file
is threaded through vfs_fileattr_get(), vfs_fileattr_set() and
do_vfs_fileattr_set(), and set to NULL for path-based operations
and for callers that only hold a path.  No implementation uses the
file yet, so this part is a purely mechanical change with no
behavior change either.

vfs_fileattr_get() and vfs_fileattr_set() are also called to forward
fileattr operations from stacking filesystems, which are performed
with kernel-side credentials (e.g. overlayfs uses its superblock
creator credentials) and the user-facing operation was already
checked by the hooks, so they must not invoke the security
hooks a second time.  Add __vfs_fileattr_get() and __vfs_fileattr_set()
counterparts without the security checks, following the
__vfs_setxattr_noperm() precedent, and use them for the internal
forwards of:

- ecryptfs, which forwards fileattr operations to lower inodes;
- overlayfs, which forwards them to the real (upper or lower) inodes.
  This also removes the ovl_security_fileattr() hack that opened the
  real file just to call security_file_ioctl() on it: the security
  hooks now run on the user-facing path instead;
- nfsd, which probes casefolding and compression state with
  overridden credentials; this is an internal lookup, not a
  user-facing fileattr operation.

The read of the current file attributes that do_vfs_fileattr_set()
performs to initialize the bits the caller did not set uses the
unchecked helper as well: it is part of a user-facing change, which
is authorized as a whole by the following path_file_setattr check.
A file attribute change therefore no longer requires the file
attribute read permission as a side effect (e.g. FILE__GETATTR for
SELinux); the Landlock patch of this series relies on this for
LANDLOCK_ACCESS_FS_WRITE_METADATA.

ksmbd serves user-facing SMB requests and keeps calling the
hook-checked vfs_fileattr_get() and vfs_fileattr_set(); it passes
the struct file it holds for the SMB opens it serves, and NULL for
its path-only probes.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/ecryptfs/inode.c           |   4 +-
 fs/file_attr.c                | 159 ++++++++++++++++++++++++----------
 fs/nfsd/vfs.c                 |   2 +-
 fs/overlayfs/inode.c          |  47 +---------
 fs/smb/server/smb2pdu.c       |   6 +-
 fs/smb/server/vfs.c           |  14 +--
 fs/smb/server/vfs.h           |   2 +-
 include/linux/fileattr.h      |  10 ++-
 include/linux/lsm_hook_defs.h |   6 +-
 include/linux/security.h      |  18 ++--
 security/security.c           |  18 ++--
 security/selinux/hooks.c      |  14 +--
 12 files changed, 169 insertions(+), 131 deletions(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index fcb5b4de897e..2b8daf21aea8 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -1077,7 +1077,7 @@ static int ecryptfs_removexattr(struct dentry *dentry, struct inode *inode,
 
 static int ecryptfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
 {
-	return vfs_fileattr_get(ecryptfs_dentry_to_lower(dentry), fa);
+	return __vfs_fileattr_get(ecryptfs_dentry_to_lower(dentry), fa);
 }
 
 static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap,
@@ -1086,7 +1086,7 @@ static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap,
 	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
 	int rc;
 
-	rc = vfs_fileattr_set(&nop_mnt_idmap, lower_dentry, fa);
+	rc = __vfs_fileattr_set(&nop_mnt_idmap, lower_dentry, fa);
 	fsstack_copy_attr_all(d_inode(dentry), d_inode(lower_dentry));
 
 	return rc;
diff --git a/fs/file_attr.c b/fs/file_attr.c
index 833e03810b24..08b4cdbef9ff 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -75,27 +75,48 @@ void fileattr_fill_flags(struct file_kattr *fa, u32 flags)
 EXPORT_SYMBOL(fileattr_fill_flags);
 
 /**
- * vfs_fileattr_get - retrieve miscellaneous file attributes
+ * __vfs_fileattr_get - retrieve miscellaneous file attributes
  * @dentry:	the object to retrieve from
  * @fa:		fileattr pointer
  *
- * Call i_op->fileattr_get() callback, if exists.
+ * Call i_op->fileattr_get() callback, if exists.  Unlike
+ * vfs_fileattr_get(), no security hook is called: this is the variant to
+ * use for internal forwards, where the LSM checks were already performed
+ * on the user-facing path (e.g. by a stacking filesystem).
  *
  * Return: 0 on success, or a negative error on failure.
  */
-int vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+int __vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
 {
 	struct inode *inode = d_inode(dentry);
-	int error;
 
 	if (!inode->i_op->fileattr_get)
 		return -ENOIOCTLCMD;
 
-	error = security_inode_file_getattr(dentry, fa);
+	return inode->i_op->fileattr_get(dentry, fa);
+}
+EXPORT_SYMBOL(__vfs_fileattr_get);
+
+/**
+ * vfs_fileattr_get - retrieve miscellaneous file attributes
+ * @path:	the object to retrieve from
+ * @fa:		fileattr pointer
+ * @file:	file through which the attributes are retrieved, if any
+ *
+ * Call i_op->fileattr_get() callback, if exists.
+ *
+ * Return: 0 on success, or a negative error on failure.
+ */
+int vfs_fileattr_get(const struct path *path, struct file_kattr *fa,
+		     struct file *file)
+{
+	int error;
+
+	error = security_inode_file_getattr(path, fa, file);
 	if (error)
 		return error;
 
-	return inode->i_op->fileattr_get(dentry, fa);
+	return __vfs_fileattr_get(path->dentry, fa);
 }
 EXPORT_SYMBOL(vfs_fileattr_get);
 
@@ -254,24 +275,11 @@ static int fileattr_set_prepare(struct inode *inode,
 	return 0;
 }
 
-/**
- * vfs_fileattr_set - change miscellaneous file attributes
- * @idmap:	idmap of the mount
- * @dentry:	the object to change
- * @fa:		fileattr pointer
- *
- * After verifying permissions, call i_op->fileattr_set() callback, if
- * exists.
- *
- * Verifying attributes involves retrieving current attributes with
- * i_op->fileattr_get(), this also allows initializing attributes that have
- * not been set by the caller to current values.  Inode lock is held
- * thoughout to prevent racing with another instance.
- *
- * Return: 0 on success, or a negative error on failure.
- */
-int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
-		     struct file_kattr *fa)
+static int do_vfs_fileattr_set(const struct path *const path,
+			       const struct mnt_idmap *const idmap,
+			       struct dentry *const dentry,
+			       struct file_kattr *const fa,
+			       struct file *const file)
 {
 	struct inode *inode = d_inode(dentry);
 	struct file_kattr old_ma = {};
@@ -284,7 +292,7 @@ int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
 		return -EPERM;
 
 	inode_lock(inode);
-	err = vfs_fileattr_get(dentry, &old_ma);
+	err = __vfs_fileattr_get(dentry, &old_ma);
 	if (!err) {
 		/* initialize missing bits from old_ma */
 		if (fa->flags_valid) {
@@ -300,9 +308,17 @@ int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
 		err = fileattr_set_prepare(inode, &old_ma, fa);
 		if (err)
 			goto out;
-		err = security_inode_file_setattr(dentry, fa);
-		if (err)
-			goto out;
+		/*
+		 * The LSM checks are only performed for user-facing calls
+		 * (i.e. when @path is set): internal forwards, e.g. from a
+		 * stacking filesystem, were already checked on the
+		 * user-facing path.
+		 */
+		if (path) {
+			err = security_inode_file_setattr(path, fa, file);
+			if (err)
+				goto out;
+		}
 		err = inode->i_op->fileattr_set(idmap, dentry, fa);
 		if (err)
 			goto out;
@@ -313,6 +329,50 @@ int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
 	inode_unlock(inode);
 	return err;
 }
+
+/**
+ * __vfs_fileattr_set - change miscellaneous file attributes
+ * @idmap:	idmap of the mount
+ * @dentry:	the object to change
+ * @fa:		fileattr pointer
+ *
+ * After verifying permissions, call i_op->fileattr_set() callback, if
+ * exists.  Unlike vfs_fileattr_set(), no security hook is called: this is
+ * the variant to use for internal forwards, where the LSM checks were
+ * already performed on the user-facing path (e.g. by a stacking
+ * filesystem).
+ *
+ * Verifying attributes involves retrieving current attributes with
+ * i_op->fileattr_get(), this also allows initializing attributes that have
+ * not been set by the caller to current values.  Inode lock is held
+ * thoughout to prevent racing with another instance.
+ *
+ * Return: 0 on success, or a negative error on failure.
+ */
+int __vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
+		       struct file_kattr *fa)
+{
+	return do_vfs_fileattr_set(NULL, idmap, dentry, fa, NULL);
+}
+EXPORT_SYMBOL(__vfs_fileattr_set);
+
+/**
+ * vfs_fileattr_set - change miscellaneous file attributes
+ * @idmap:	idmap of the mount
+ * @path:	the object to change
+ * @fa:		fileattr pointer
+ * @file:	file through which the attributes are set, if any
+ *
+ * After verifying permissions, call i_op->fileattr_set() callback, if
+ * exists.
+ *
+ * Return: 0 on success, or a negative error on failure.
+ */
+int vfs_fileattr_set(const struct mnt_idmap *idmap, const struct path *path,
+		     struct file_kattr *fa, struct file *file)
+{
+	return do_vfs_fileattr_set(path, idmap, path->dentry, fa, file);
+}
 EXPORT_SYMBOL(vfs_fileattr_set);
 
 int ioctl_getflags(struct file *file, unsigned int __user *argp)
@@ -320,7 +380,7 @@ int ioctl_getflags(struct file *file, unsigned int __user *argp)
 	struct file_kattr fa = { .flags_valid = true }; /* hint only */
 	int err;
 
-	err = vfs_fileattr_get(file->f_path.dentry, &fa);
+	err = vfs_fileattr_get(&file->f_path, &fa, file);
 	if (!err)
 		err = put_user(fa.flags, argp);
 	return err;
@@ -329,7 +389,6 @@ int ioctl_getflags(struct file *file, unsigned int __user *argp)
 int ioctl_setflags(struct file *file, unsigned int __user *argp)
 {
 	const struct mnt_idmap *idmap = file_mnt_idmap(file);
-	struct dentry *dentry = file->f_path.dentry;
 	struct file_kattr fa = {};
 	unsigned int flags;
 	int err;
@@ -339,7 +398,7 @@ int ioctl_setflags(struct file *file, unsigned int __user *argp)
 		err = mnt_want_write_file(file);
 		if (!err) {
 			fileattr_fill_flags(&fa, flags);
-			err = vfs_fileattr_set(idmap, dentry, &fa);
+			err = vfs_fileattr_set(idmap, &file->f_path, &fa, file);
 			mnt_drop_write_file(file);
 		}
 	}
@@ -351,7 +410,7 @@ int ioctl_fsgetxattr(struct file *file, void __user *argp)
 	struct file_kattr fa = { .fsx_valid = true }; /* hint only */
 	int err;
 
-	err = vfs_fileattr_get(file->f_path.dentry, &fa);
+	err = vfs_fileattr_get(&file->f_path, &fa, file);
 	if (!err)
 		err = copy_fsxattr_to_user(&fa, argp);
 
@@ -361,7 +420,6 @@ int ioctl_fsgetxattr(struct file *file, void __user *argp)
 int ioctl_fssetxattr(struct file *file, void __user *argp)
 {
 	const struct mnt_idmap *idmap = file_mnt_idmap(file);
-	struct dentry *dentry = file->f_path.dentry;
 	struct file_kattr fa = {};
 	int err;
 
@@ -369,7 +427,7 @@ int ioctl_fssetxattr(struct file *file, void __user *argp)
 	if (!err) {
 		err = mnt_want_write_file(file);
 		if (!err) {
-			err = vfs_fileattr_set(idmap, dentry, &fa);
+			err = vfs_fileattr_set(idmap, &file->f_path, &fa, file);
 			mnt_drop_write_file(file);
 		}
 	}
@@ -407,16 +465,16 @@ SYSCALL_DEFINE5(file_getattr, int, dfd, const char __user *, filename,
 		if (fd_empty(f))
 			return -EBADF;
 
-		filepath = fd_file(f)->f_path;
-		path_get(&filepath);
+		error = vfs_fileattr_get(&fd_file(f)->f_path, &fa,
+					 fd_file(f));
 	} else {
 		error = filename_lookup(dfd, name, lookup_flags, &filepath,
 					NULL);
 		if (error)
 			return error;
-	}
 
-	error = vfs_fileattr_get(filepath.dentry, &fa);
+		error = vfs_fileattr_get(&filepath, &fa, NULL);
+	}
 	if (error == -ENOIOCTLCMD || error == -ENOTTY)
 		error = -EOPNOTSUPP;
 	if (error)
@@ -469,23 +527,28 @@ SYSCALL_DEFINE5(file_setattr, int, dfd, const char __user *, filename,
 		if (fd_empty(f))
 			return -EBADF;
 
-		filepath = fd_file(f)->f_path;
-		path_get(&filepath);
+		error = mnt_want_write_file(fd_file(f));
+		if (!error) {
+			error = vfs_fileattr_set(file_mnt_idmap(fd_file(f)),
+						 &fd_file(f)->f_path, &fa,
+						 fd_file(f));
+			mnt_drop_write_file(fd_file(f));
+		}
 	} else {
 		error = filename_lookup(dfd, name, lookup_flags, &filepath,
 					NULL);
 		if (error)
 			return error;
-	}
 
-	error = mnt_want_write(filepath.mnt);
-	if (!error) {
-		error = vfs_fileattr_set(mnt_idmap(filepath.mnt),
-					 filepath.dentry, &fa);
-		if (error == -ENOIOCTLCMD || error == -ENOTTY)
-			error = -EOPNOTSUPP;
-		mnt_drop_write(filepath.mnt);
+		error = mnt_want_write(filepath.mnt);
+		if (!error) {
+			error = vfs_fileattr_set(mnt_idmap(filepath.mnt),
+						 &filepath, &fa, NULL);
+			mnt_drop_write(filepath.mnt);
+		}
 	}
+	if (error == -ENOIOCTLCMD || error == -ENOTTY)
+		error = -EOPNOTSUPP;
 
 	return error;
 }
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 7050ec0992a1..870756842882 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -2984,7 +2984,7 @@ nfsd_get_case_info(struct dentry *dentry, bool *case_insensitive,
 	}
 	saved = override_creds(probe);
 
-	err = vfs_fileattr_get(cd, &fa);
+	err = __vfs_fileattr_get(cd, &fa);
 
 	put_cred(revert_creds(saved));
 out:
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index eb408284df08..4f1d3077735b 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -608,47 +608,10 @@ static int ovl_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
 		return realinode->i_op->fiemap(realinode, fieinfo, start, len);
 }
 
-/*
- * Work around the fact that security_file_ioctl() takes a file argument.
- * Introducing security_inode_fileattr_get/set() hooks would solve this issue
- * properly.
- */
-static int ovl_security_fileattr(const struct path *realpath, struct file_kattr *fa,
-				 bool set)
-{
-	struct file *file;
-	unsigned int cmd;
-	int err;
-	unsigned int flags;
-
-	flags = O_RDONLY;
-	if (force_o_largefile())
-		flags |= O_LARGEFILE;
-
-	file = dentry_open(realpath, flags, current_cred());
-	if (IS_ERR(file))
-		return PTR_ERR(file);
-
-	if (set)
-		cmd = fa->fsx_valid ? FS_IOC_FSSETXATTR : FS_IOC_SETFLAGS;
-	else
-		cmd = fa->fsx_valid ? FS_IOC_FSGETXATTR : FS_IOC_GETFLAGS;
-
-	err = security_file_ioctl(file, cmd, 0);
-	fput(file);
-
-	return err;
-}
-
 int ovl_real_fileattr_set(const struct path *realpath, struct file_kattr *fa)
 {
-	int err;
-
-	err = ovl_security_fileattr(realpath, fa, true);
-	if (err)
-		return err;
-
-	return vfs_fileattr_set(mnt_idmap(realpath->mnt), realpath->dentry, fa);
+	return __vfs_fileattr_set(mnt_idmap(realpath->mnt), realpath->dentry,
+				  fa);
 }
 
 int ovl_fileattr_set(const struct mnt_idmap *idmap,
@@ -718,11 +681,7 @@ int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa)
 {
 	int err;
 
-	err = ovl_security_fileattr(realpath, fa, false);
-	if (err)
-		return err;
-
-	err = vfs_fileattr_get(realpath->dentry, fa);
+	err = __vfs_fileattr_get(realpath->dentry, fa);
 	if (err == -ENOIOCTLCMD)
 		err = -ENOTTY;
 	return err;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 91e201af66c2..26782bdb5fa4 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3660,7 +3660,7 @@ static bool smb2_parent_compressed(struct ksmbd_tree_connect *tcon,
 	bool compressed = false;
 	int rc;
 
-	rc = vfs_fileattr_get(parent, &fa);
+	rc = vfs_fileattr_get(&parent_path, &fa, NULL);
 	if (!rc && fa.flags & FS_COMPR_FL) {
 		compressed = true;
 		goto out;
@@ -5283,7 +5283,7 @@ int smb2_open(struct ksmbd_work *work)
 		smb2_new_xattrs(tcon, &path, fp);
 	}
 
-	ksmbd_vfs_update_compressed_fattr(path.dentry, &fp->f_ci->m_fattr);
+	ksmbd_vfs_update_compressed_fattr(&path, &fp->f_ci->m_fattr);
 
 	if (created) {
 		if (fp->coption & FILE_NO_COMPRESSION_LE) {
@@ -7956,7 +7956,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
 			FILE_SUPPORTS_SPARSE_FILES |
 			FILE_SUPPORTS_BLOCK_REFCOUNTING;
 
-		err = vfs_fileattr_get(path.dentry, &fa);
+		err = vfs_fileattr_get(&path, &fa, NULL);
 		/*
 		 * -EINVAL, -EOPNOTSUPP: ntfs-3g and other FUSE
 		 * filesystems that lack FS_IOC_FSGETXATTR support.
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 54b5f17995c3..d30f89884c06 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -2350,12 +2350,12 @@ int ksmbd_vfs_inherit_posix_acl(const struct mnt_idmap *idmap,
 	return 0;
 }
 
-void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr)
+void ksmbd_vfs_update_compressed_fattr(struct path *path, __le32 *fattr)
 {
 	int rc;
 	struct file_kattr fa = { .flags_valid = true };
 
-	rc = vfs_fileattr_get(dentry, &fa);
+	rc = vfs_fileattr_get(path, &fa, NULL);
 	if (rc)
 		return;
 
@@ -2368,7 +2368,7 @@ int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt)
 	struct file_kattr fa = { .flags_valid = true };
 	int rc;
 
-	rc = vfs_fileattr_get(fp->filp->f_path.dentry, &fa);
+	rc = vfs_fileattr_get(&fp->filp->f_path, &fa, fp->filp);
 	if (rc == -ENOIOCTLCMD || rc == -ENOTTY || rc == -EINVAL ||
 	    rc == -EOPNOTSUPP) {
 		if (fp->f_ci->m_fattr & FILE_ATTRIBUTE_COMPRESSED_LE)
@@ -2395,9 +2395,8 @@ static int __ksmbd_vfs_set_compression(struct ksmbd_work *work,
 				       bool check_access)
 {
 	const struct cred *saved_cred = NULL;
-	struct file_kattr fa;
-	struct dentry *dentry = fp->filp->f_path.dentry;
 	const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
+	struct file_kattr fa;
 	u32 flags;
 	__le32 old_fattr;
 	int rc;
@@ -2415,7 +2414,7 @@ static int __ksmbd_vfs_set_compression(struct ksmbd_work *work,
 	}
 
 	saved_cred = override_creds(fp->filp->f_cred);
-	rc = vfs_fileattr_get(dentry, &fa);
+	rc = vfs_fileattr_get(&fp->filp->f_path, &fa, fp->filp);
 	if (rc == -ENOIOCTLCMD || rc == -ENOTTY || rc == -EINVAL ||
 	    rc == -EOPNOTSUPP)
 		goto update_fattr;
@@ -2436,7 +2435,8 @@ static int __ksmbd_vfs_set_compression(struct ksmbd_work *work,
 		if (rc)
 			goto out;
 
-		rc = vfs_fileattr_set(idmap, dentry, &fa);
+		rc = vfs_fileattr_set(idmap, &fp->filp->f_path, &fa,
+				      fp->filp);
 		mnt_drop_write_file(fp->filp);
 		if (rc == -ENOIOCTLCMD || rc == -ENOTTY || rc == -EINVAL ||
 		    rc == -EOPNOTSUPP)
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index c1ea94cf153f..8231412182a8 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -182,7 +182,7 @@ int ksmbd_vfs_set_init_posix_acl(const struct mnt_idmap *idmap,
 int ksmbd_vfs_inherit_posix_acl(const struct mnt_idmap *idmap,
 				const struct path *path,
 				struct inode *parent_inode);
-void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr);
+void ksmbd_vfs_update_compressed_fattr(struct path *path, __le32 *fattr);
 int ksmbd_vfs_get_compression(struct ksmbd_file *fp, u16 *fmt);
 int ksmbd_vfs_set_compression(struct ksmbd_work *work, struct ksmbd_file *fp, u16 fmt);
 int ksmbd_vfs_set_compression_create(struct ksmbd_work *work,
diff --git a/include/linux/fileattr.h b/include/linux/fileattr.h
index 09e32b84e02a..66ffd2478d1b 100644
--- a/include/linux/fileattr.h
+++ b/include/linux/fileattr.h
@@ -73,9 +73,13 @@ static inline bool fileattr_has_fsx(const struct file_kattr *fa)
 		 fa->fsx_projid != 0 ||	fa->fsx_cowextsize != 0);
 }
 
-int vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
-int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
-		     struct file_kattr *fa);
+int __vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+int __vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
+		       struct file_kattr *fa);
+int vfs_fileattr_get(const struct path *path, struct file_kattr *fa,
+		     struct file *file);
+int vfs_fileattr_set(const struct mnt_idmap *idmap, const struct path *path,
+		     struct file_kattr *fa, struct file *file);
 int ioctl_getflags(struct file *file, unsigned int __user *argp);
 int ioctl_setflags(struct file *file, unsigned int __user *argp);
 int ioctl_fsgetxattr(struct file *file, void __user *argp);
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8b191df5b169..96ae1b548e8d 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -168,8 +168,10 @@ LSM_HOOK(int, 0, inode_removexattr, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *name, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry,
 	 const char *name)
-LSM_HOOK(int, 0, inode_file_setattr, struct dentry *dentry, struct file_kattr *fa)
-LSM_HOOK(int, 0, inode_file_getattr, struct dentry *dentry, struct file_kattr *fa)
+LSM_HOOK(int, 0, inode_file_setattr, const struct path *path,
+	 struct file_kattr *fa, struct file *file)
+LSM_HOOK(int, 0, inode_file_getattr, const struct path *path,
+	 struct file_kattr *fa, struct file *file)
 LSM_HOOK(int, 0, inode_set_acl, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *acl_name,
 	 struct posix_acl *kacl, struct file *file)
diff --git a/include/linux/security.h b/include/linux/security.h
index 27291542a7e2..67dc03d7cee2 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -464,10 +464,10 @@ int security_inode_removexattr(const struct mnt_idmap *idmap,
 			       const struct path *path, const char *name,
 			       struct file *file);
 void security_inode_post_removexattr(struct dentry *dentry, const char *name);
-int security_inode_file_setattr(struct dentry *dentry,
-			      struct file_kattr *fa);
-int security_inode_file_getattr(struct dentry *dentry,
-			      struct file_kattr *fa);
+int security_inode_file_setattr(const struct path *path,
+				struct file_kattr *fa, struct file *file);
+int security_inode_file_getattr(const struct path *path,
+				struct file_kattr *fa, struct file *file);
 int security_inode_need_killpriv(struct dentry *dentry);
 int security_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry);
 int security_inode_getsecurity(const struct mnt_idmap *idmap,
@@ -1092,14 +1092,16 @@ static inline void security_inode_post_removexattr(struct dentry *dentry,
 						   const char *name)
 { }
 
-static inline int security_inode_file_setattr(struct dentry *dentry,
-					      struct file_kattr *fa)
+static inline int security_inode_file_setattr(const struct path *path,
+					      struct file_kattr *fa,
+					      struct file *file)
 {
 	return 0;
 }
 
-static inline int security_inode_file_getattr(struct dentry *dentry,
-					      struct file_kattr *fa)
+static inline int security_inode_file_getattr(const struct path *path,
+					      struct file_kattr *fa,
+					      struct file *file)
 {
 	return 0;
 }
diff --git a/security/security.c b/security/security.c
index af1777f2aa5e..661b18f08f1b 100644
--- a/security/security.c
+++ b/security/security.c
@@ -2179,32 +2179,38 @@ void security_inode_post_removexattr(struct dentry *dentry, const char *name)
 
 /**
  * security_inode_file_setattr() - check if setting fsxattr is allowed
- * @dentry: file to set filesystem extended attributes on
+ * @path: file to set filesystem extended attributes on (the VFS makes no
+ *        guarantees with regard to @path->mnt)
  * @fa: extended attributes to set on the inode
+ * @file: file through which the file attributes are set, if any
  *
  * Called when file_setattr() syscall or FS_IOC_FSSETXATTR ioctl() is called on
  * inode
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_file_setattr(struct dentry *dentry, struct file_kattr *fa)
+int security_inode_file_setattr(const struct path *path,
+				struct file_kattr *fa, struct file *file)
 {
-	return call_int_hook(inode_file_setattr, dentry, fa);
+	return call_int_hook(inode_file_setattr, path, fa, file);
 }
 
 /**
  * security_inode_file_getattr() - check if retrieving fsxattr is allowed
- * @dentry: file to retrieve filesystem extended attributes from
+ * @path: file to retrieve filesystem extended attributes from (the VFS makes no
+ *        guarantees with regard to @path->mnt)
  * @fa: extended attributes to get
+ * @file: file through which the file attributes are retrieved, if any
  *
  * Called when file_getattr() syscall or FS_IOC_FSGETXATTR ioctl() is called on
  * inode
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_file_getattr(struct dentry *dentry, struct file_kattr *fa)
+int security_inode_file_getattr(const struct path *path,
+				struct file_kattr *fa, struct file *file)
 {
-	return call_int_hook(inode_file_getattr, dentry, fa);
+	return call_int_hook(inode_file_getattr, path, fa, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 2b5efe041bc1..16c8e852f841 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3570,16 +3570,18 @@ static int selinux_inode_removexattr(const struct mnt_idmap *idmap,
 	return -EACCES;
 }
 
-static int selinux_inode_file_setattr(struct dentry *dentry,
-				      struct file_kattr *fa)
+static int selinux_inode_file_setattr(const struct path *path,
+				      struct file_kattr *fa,
+				      struct file *file)
 {
-	return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
+	return dentry_has_perm(current_cred(), path->dentry, FILE__SETATTR);
 }
 
-static int selinux_inode_file_getattr(struct dentry *dentry,
-				      struct file_kattr *fa)
+static int selinux_inode_file_getattr(const struct path *path,
+				      struct file_kattr *fa,
+				      struct file *file)
 {
-	return dentry_has_perm(current_cred(), dentry, FILE__GETATTR);
+	return dentry_has_perm(current_cred(), path->dentry, FILE__GETATTR);
 }
 
 static int selinux_path_notify(const struct path *path, u64 mask,
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 09/18] LSM: rename inode_getattr to path_getattr and pass struct file
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (7 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 08/18] LSM: pass struct path and struct file to the inode fileattr hooks Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 14:01   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 10/18] LSM: rename inode_setattr to path_setattr Cai Xinchen
                   ` (8 subsequent siblings)
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_getattr hook receives a struct path, so rename it to
path_getattr to follow the path_* naming convention used for the
other hooks that take a struct path (path_chmod, path_chown, ...).
Rename the security_inode_getattr() inline wrapper to
security_path_getattr() and the SELinux, Smack, AppArmor and TOMOYO
implementations accordingly.

Also pass the struct file through which the attributes are
requested, when there is one, so that LSMs can later authorize
file descriptor-based operations (fstat(2), statx(2) with an empty
path, and fstatat(2) with AT_EMPTY_PATH) with the rights available
at open(2) time.  vfs_getattr() has many callers and keeps its
signature; it calls the hook with NULL.  The file descriptor-based
vfs_fstat() and vfs_statx_fd() call security_path_getattr()
explicitly with the file they hold and then the unchecked
vfs_getattr_nosec(); vfs_statx_path() takes the file and calls the
hook explicitly, with NULL from the filename-based vfs_statx().  No
implementation uses the file yet, so this part is a purely
mechanical change with no behavior change either.

BPF programs attach to LSM hooks and to the security_*() wrappers by
name ("lsm/inode_getattr", "fentry/security_inode_getattr"), so
update the BPF selftests to the new names, along with the d_path BTF
allowlist in kernel/trace/bpf_trace.c.  Convert the CHECK() in
test_d_path_basic() that this touches to ASSERT_TRUE() to follow the
current selftests style.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/stat.c                                     | 48 ++++++++++++-------
 include/linux/lsm_hook_defs.h                 |  3 +-
 include/linux/security.h                      |  5 +-
 kernel/bpf/bpf_lsm.c                          |  2 +-
 kernel/trace/bpf_trace.c                      |  2 +-
 security/apparmor/lsm.c                       |  5 +-
 security/security.c                           |  9 ++--
 security/selinux/hooks.c                      |  5 +-
 security/smack/smack_lsm.c                    |  8 ++--
 security/tomoyo/tomoyo.c                      |  7 +--
 .../testing/selftests/bpf/prog_tests/d_path.c |  4 +-
 .../testing/selftests/bpf/progs/test_d_path.c |  2 +-
 .../bpf/progs/test_d_path_check_rdonly_mem.c  |  2 +-
 .../bpf/progs/test_d_path_check_types.c       |  2 +-
 .../selftests/bpf/progs/verifier_vfs_accept.c |  2 +-
 15 files changed, 64 insertions(+), 42 deletions(-)

diff --git a/fs/stat.c b/fs/stat.c
index a2e63c6c45b7..f2bb0e2d1ef1 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -172,11 +172,12 @@ EXPORT_SYMBOL_GPL(generic_fill_statx_atomic_writes);
  * @request_mask: STATX_xxx flags indicating what the caller wants
  * @query_flags: Query mode (AT_STATX_SYNC_TYPE)
  *
- * Get attributes without calling security_inode_getattr.
+ * Get attributes without calling security_path_getattr.
  *
- * Currently the only caller other than vfs_getattr is internal to the
- * filehandle lookup code, which uses only the inode number and returns no
- * attributes to any user.  Any other code probably wants vfs_getattr.
+ * Callers must call security_path_getattr() beforehand, unless they only
+ * use the attributes internally: the filehandle lookup code, for
+ * instance, uses only the inode number and returns no attributes to any
+ * user.  Code that merely holds a path probably wants vfs_getattr().
  */
 int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
 		      u32 request_mask, unsigned int query_flags)
@@ -260,7 +261,7 @@ int vfs_getattr(const struct path *path, struct kstat *stat,
 {
 	int retval;
 
-	retval = security_inode_getattr(path);
+	retval = security_path_getattr(path, NULL);
 	if (unlikely(retval))
 		return retval;
 	return vfs_getattr_nosec(path, stat, request_mask, query_flags);
@@ -272,17 +273,26 @@ EXPORT_SYMBOL(vfs_getattr);
  * @fd: The file descriptor referring to the file of interest
  * @stat: The result structure to fill in.
  *
- * This function is a wrapper around vfs_getattr().  The main difference is
- * that it uses a file descriptor to determine the file location.
+ * This function is a wrapper around vfs_getattr_nosec().  The main
+ * difference is that it uses a file descriptor to determine the file
+ * location, and passes that file to security_path_getattr(), so that
+ * LSMs can later authorize the operation with the rights available at
+ * open(2) time.
  *
  * 0 will be returned on success, and a -ve error code if unsuccessful.
  */
 int vfs_fstat(int fd, struct kstat *stat)
 {
+	int error;
 	CLASS(fd_raw, f)(fd);
+
 	if (fd_empty(f))
 		return -EBADF;
-	return vfs_getattr(&fd_file(f)->f_path, stat, STATX_BASIC_STATS, 0);
+	error = security_path_getattr(&fd_file(f)->f_path, fd_file(f));
+	if (error)
+		return error;
+	return vfs_getattr_nosec(&fd_file(f)->f_path, stat,
+				  STATX_BASIC_STATS, 0);
 }
 
 static int statx_lookup_flags(int flags)
@@ -298,9 +308,14 @@ static int statx_lookup_flags(int flags)
 }
 
 static int vfs_statx_path(const struct path *path, int flags, struct kstat *stat,
-			  u32 request_mask)
+			  u32 request_mask, struct file *file)
 {
-	int error = vfs_getattr(path, stat, request_mask, flags);
+	int error = security_path_getattr(path, file);
+
+	if (error)
+		return error;
+
+	error = vfs_getattr_nosec(path, stat, request_mask, flags);
 	if (error)
 		return error;
 
@@ -324,7 +339,8 @@ static int vfs_statx_fd(int fd, int flags, struct kstat *stat,
 	CLASS(fd_raw, f)(fd);
 	if (fd_empty(f))
 		return -EBADF;
-	return vfs_statx_path(&fd_file(f)->f_path, flags, stat, request_mask);
+	return vfs_statx_path(&fd_file(f)->f_path, flags, stat, request_mask,
+			      fd_file(f));
 }
 
 /**
@@ -335,10 +351,10 @@ static int vfs_statx_fd(int fd, int flags, struct kstat *stat,
  * @stat: The result structure to fill in.
  * @request_mask: STATX_xxx flags indicating what the caller wants
  *
- * This function is a wrapper around vfs_getattr().  The main difference is
- * that it uses a filename and base directory to determine the file location.
- * Additionally, the use of AT_SYMLINK_NOFOLLOW in flags will prevent a symlink
- * at the given name from being referenced.
+ * This function is a wrapper around vfs_statx_path().  The main difference
+ * with vfs_getattr() is that it uses a filename and base directory to
+ * determine the file location.  Additionally, the use of AT_SYMLINK_NOFOLLOW
+ * in flags will prevent a symlink at the given name from being referenced.
  *
  * 0 will be returned on success, and a -ve error code if unsuccessful.
  */
@@ -357,7 +373,7 @@ static int vfs_statx(int dfd, struct filename *filename, int flags,
 	error = filename_lookup(dfd, filename, lookup_flags, &path, NULL);
 	if (error)
 		return error;
-	error = vfs_statx_path(&path, flags, stat, request_mask);
+	error = vfs_statx_path(&path, flags, stat, request_mask, NULL);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 96ae1b548e8d..0f7712f51644 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -153,7 +153,8 @@ LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap,
 	 const struct path *path, struct iattr *attr, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
 	 struct dentry *dentry, int ia_valid)
-LSM_HOOK(int, 0, inode_getattr, const struct path *path)
+LSM_HOOK(int, 0, path_getattr, const struct path *path,
+	 struct file *file)
 LSM_HOOK(int, 0, inode_xattr_skipcap, const char *name)
 LSM_HOOK(int, 0, inode_setxattr, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *name, const void *value,
diff --git a/include/linux/security.h b/include/linux/security.h
index 67dc03d7cee2..1e9382fb16a9 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -435,7 +435,7 @@ int security_inode_setattr(const struct mnt_idmap *idmap,
 			   struct file *file);
 void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
 				 int ia_valid);
-int security_inode_getattr(const struct path *path);
+int security_path_getattr(const struct path *path, struct file *file);
 int security_inode_setxattr(const struct mnt_idmap *idmap,
 			    const struct path *path, const char *name,
 			    const void *value, size_t size, int flags,
@@ -1016,7 +1016,8 @@ security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry
 			    int ia_valid)
 { }
 
-static inline int security_inode_getattr(const struct path *path)
+static inline int security_path_getattr(const struct path *path,
+					struct file *file)
 {
 	return 0;
 }
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index f58dce888ff4..293f849879e2 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -335,7 +335,7 @@ BTF_ID(func, bpf_lsm_file_receive)
 
 BTF_ID(func, bpf_lsm_inode_create)
 BTF_ID(func, bpf_lsm_inode_free_security)
-BTF_ID(func, bpf_lsm_inode_getattr)
+BTF_ID(func, bpf_lsm_path_getattr)
 BTF_ID(func, bpf_lsm_inode_getxattr)
 BTF_ID(func, bpf_lsm_inode_mknod)
 BTF_ID(func, bpf_lsm_inode_need_killpriv)
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 14adfb85fdb2..26ff684102f8 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -961,7 +961,7 @@ BPF_CALL_3(bpf_d_path, const struct path *, path, char *, buf, u32, sz)
 BTF_SET_START(btf_allowlist_d_path)
 #ifdef CONFIG_SECURITY
 BTF_ID(func, security_file_permission)
-BTF_ID(func, security_inode_getattr)
+BTF_ID(func, security_path_getattr)
 BTF_ID(func, security_file_open)
 #endif
 #ifdef CONFIG_SECURITY_PATH
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index c73681d820a0..11d802b246b4 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -456,7 +456,8 @@ static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid)
 	return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN);
 }
 
-static int apparmor_inode_getattr(const struct path *path)
+static int apparmor_path_getattr(const struct path *path,
+				 struct file *file)
 {
 	return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR);
 }
@@ -1785,7 +1786,7 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_chmod, apparmor_path_chmod),
 	LSM_HOOK_INIT(path_chown, apparmor_path_chown),
 	LSM_HOOK_INIT(path_truncate, apparmor_path_truncate),
-	LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr),
+	LSM_HOOK_INIT(path_getattr, apparmor_path_getattr),
 
 	LSM_HOOK_INIT(file_open, apparmor_file_open),
 	LSM_HOOK_INIT(file_receive, apparmor_file_receive),
diff --git a/security/security.c b/security/security.c
index 661b18f08f1b..d91268f88760 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1905,18 +1905,19 @@ void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *d
 }
 
 /**
- * security_inode_getattr() - Check if getting file attributes is allowed
- * @path: file
+ * security_path_getattr() - Check if getting file attributes is allowed
+ * @path: file (the VFS makes no guarantees with regard to @path->mnt)
+ * @file: file through which the attributes are retrieved, if any
  *
  * Check permission before obtaining file attributes.
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_getattr(const struct path *path)
+int security_path_getattr(const struct path *path, struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_getattr, path);
+	return call_int_hook(path_getattr, path, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 16c8e852f841..e07b45fba392 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3347,7 +3347,8 @@ static int selinux_inode_setattr(const struct mnt_idmap *idmap,
 	return dentry_has_perm(cred, dentry, av);
 }
 
-static int selinux_inode_getattr(const struct path *path)
+static int selinux_path_getattr(const struct path *path,
+				struct file *file)
 {
 	struct task_security_struct *tsec;
 
@@ -7734,7 +7735,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_follow_link, selinux_inode_follow_link),
 	LSM_HOOK_INIT(inode_permission, selinux_inode_permission),
 	LSM_HOOK_INIT(inode_setattr, selinux_inode_setattr),
-	LSM_HOOK_INIT(inode_getattr, selinux_inode_getattr),
+	LSM_HOOK_INIT(path_getattr, selinux_path_getattr),
 	LSM_HOOK_INIT(inode_xattr_skipcap, selinux_inode_xattr_skipcap),
 	LSM_HOOK_INIT(inode_setxattr, selinux_inode_setxattr),
 	LSM_HOOK_INIT(inode_post_setxattr, selinux_inode_post_setxattr),
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index e3445a4f6a45..1aa94331c980 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1295,12 +1295,14 @@ static int smack_inode_setattr(const struct mnt_idmap *idmap,
 }
 
 /**
- * smack_inode_getattr - Smack check for getting attributes
+ * smack_path_getattr - Smack check for getting attributes
  * @path: path to extract the info from
+ * @file: file through which the attributes are retrieved, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_getattr(const struct path *path)
+static int smack_path_getattr(const struct path *path,
+			      struct file *file)
 {
 	struct smk_audit_info ad;
 	struct inode *inode = d_backing_inode(path->dentry);
@@ -5197,7 +5199,7 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_rename, smack_inode_rename),
 	LSM_HOOK_INIT(inode_permission, smack_inode_permission),
 	LSM_HOOK_INIT(inode_setattr, smack_inode_setattr),
-	LSM_HOOK_INIT(inode_getattr, smack_inode_getattr),
+	LSM_HOOK_INIT(path_getattr, smack_path_getattr),
 	LSM_HOOK_INIT(inode_xattr_skipcap, smack_inode_xattr_skipcap),
 	LSM_HOOK_INIT(inode_setxattr, smack_inode_setxattr),
 	LSM_HOOK_INIT(inode_post_setxattr, smack_inode_post_setxattr),
diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
index c66e02ed8ee3..c67455e060f7 100644
--- a/security/tomoyo/tomoyo.c
+++ b/security/tomoyo/tomoyo.c
@@ -112,13 +112,14 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
 }
 
 /**
- * tomoyo_inode_getattr - Target for security_inode_getattr().
+ * tomoyo_path_getattr - Target for security_path_getattr().
  *
  * @path: Pointer to "struct path".
  *
  * Returns 0 on success, negative value otherwise.
  */
-static int tomoyo_inode_getattr(const struct path *path)
+static int tomoyo_path_getattr(const struct path *path,
+			       struct file *file)
 {
 	return tomoyo_path_perm(TOMOYO_TYPE_GETATTR, path, NULL);
 }
@@ -570,7 +571,7 @@ static struct security_hook_list tomoyo_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_mknod, tomoyo_path_mknod),
 	LSM_HOOK_INIT(path_link, tomoyo_path_link),
 	LSM_HOOK_INIT(path_rename, tomoyo_path_rename),
-	LSM_HOOK_INIT(inode_getattr, tomoyo_inode_getattr),
+	LSM_HOOK_INIT(path_getattr, tomoyo_path_getattr),
 	LSM_HOOK_INIT(file_ioctl, tomoyo_file_ioctl),
 	LSM_HOOK_INIT(file_ioctl_compat, tomoyo_file_ioctl),
 	LSM_HOOK_INIT(path_chmod, tomoyo_path_chmod),
diff --git a/tools/testing/selftests/bpf/prog_tests/d_path.c b/tools/testing/selftests/bpf/prog_tests/d_path.c
index 1a2a2f1abf03..01a03d65b1cf 100644
--- a/tools/testing/selftests/bpf/prog_tests/d_path.c
+++ b/tools/testing/selftests/bpf/prog_tests/d_path.c
@@ -158,9 +158,7 @@ static void test_d_path_basic(void)
 	if (err < 0)
 		goto cleanup;
 
-	if (CHECK(!bss->called_stat,
-		  "stat",
-		  "trampoline for security_inode_getattr was not called\n"))
+	if (!ASSERT_TRUE(bss->called_stat, "stat"))
 		goto cleanup;
 
 	if (CHECK(!bss->called_close,
diff --git a/tools/testing/selftests/bpf/progs/test_d_path.c b/tools/testing/selftests/bpf/progs/test_d_path.c
index 561b2f861808..b97c203e3992 100644
--- a/tools/testing/selftests/bpf/progs/test_d_path.c
+++ b/tools/testing/selftests/bpf/progs/test_d_path.c
@@ -19,7 +19,7 @@ int called_stat = 0;
 int called_close = 0;
 int path_match_fallocate = 0;
 
-SEC("fentry/security_inode_getattr")
+SEC("fentry/security_path_getattr")
 int BPF_PROG(prog_stat, struct path *path, struct kstat *stat,
 	     __u32 request_mask, unsigned int query_flags)
 {
diff --git a/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c b/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c
index 27c27cff6a3a..5e0e3d4828d7 100644
--- a/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c
+++ b/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c
@@ -7,7 +7,7 @@
 
 extern const int bpf_prog_active __ksym;
 
-SEC("fentry/security_inode_getattr")
+SEC("fentry/security_path_getattr")
 int BPF_PROG(d_path_check_rdonly_mem, struct path *path, struct kstat *stat,
 	     __u32 request_mask, unsigned int query_flags)
 {
diff --git a/tools/testing/selftests/bpf/progs/test_d_path_check_types.c b/tools/testing/selftests/bpf/progs/test_d_path_check_types.c
index 7e02b7361307..fba3d527b4df 100644
--- a/tools/testing/selftests/bpf/progs/test_d_path_check_types.c
+++ b/tools/testing/selftests/bpf/progs/test_d_path_check_types.c
@@ -11,7 +11,7 @@ struct {
 	__uint(max_entries, 1 << 12);
 } ringbuf SEC(".maps");
 
-SEC("fentry/security_inode_getattr")
+SEC("fentry/security_path_getattr")
 int BPF_PROG(d_path_check_rdonly_mem, struct path *path, struct kstat *stat,
 	     __u32 request_mask, unsigned int query_flags)
 {
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
index 25f37a3dd7cf..81142d217655 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
@@ -71,7 +71,7 @@ int BPF_PROG(get_task_exe_file_and_put_kfunc_from_argument,
 	return 0;
 }
 
-SEC("lsm.s/inode_getattr")
+SEC("lsm.s/path_getattr")
 __success
 int BPF_PROG(path_d_path_from_path_argument, struct path *path)
 {
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 10/18] LSM: rename inode_setattr to path_setattr
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (8 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 09/18] LSM: rename inode_getattr to path_getattr and pass struct file Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 11/18] LSM: rename the inode xattr hooks to path_xattr hooks Cai Xinchen
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_setattr hook receives a struct path, so rename it to
path_setattr to follow the path_* naming convention used for the
other hooks that take a struct path (path_chmod, path_chown, ...).
Rename the security_inode_setattr() inline wrapper to
security_path_setattr() and the SELinux, Smack and EVM
implementations accordingly.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/attr.c                         | 4 ++--
 fs/bpf_fs_kfuncs.c                | 2 +-
 fs/exfat/file.c                   | 4 ++--
 fs/fat/file.c                     | 4 ++--
 include/linux/lsm_hook_defs.h     | 2 +-
 include/linux/security.h          | 4 ++--
 kernel/bpf/bpf_lsm.c              | 2 +-
 security/integrity/evm/evm_main.c | 6 +++---
 security/security.c               | 8 ++++----
 security/selinux/hooks.c          | 4 ++--
 security/smack/smack_lsm.c        | 6 +++---
 11 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index 67fa5ee6b30e..62f3dc759f76 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -519,7 +519,7 @@ static int do_notify_change(const struct path *const path,
 	 * filesystem, were already checked on the user-facing path.
 	 */
 	if (path) {
-		error = security_inode_setattr(idmap, path, attr, file);
+		error = security_path_setattr(idmap, path, attr, file);
 		if (error)
 			return error;
 	}
@@ -556,7 +556,7 @@ static int do_notify_change(const struct path *const path,
  * @attr:	new attributes
  * @delegated_inode: returns inode, if the inode is delegated
  *
- * Like notify_change(), but no security_inode_setattr() hook is called:
+ * Like notify_change(), but no security_path_setattr() hook is called:
  * this is the variant to use for internal forwards, where the LSM
  * checks were already performed on the user-facing path.
  *
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 35ccd98a4de6..6500d8419bc1 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -568,7 +568,7 @@ BTF_ID(func, bpf_lsm_inode_post_setattr)
 BTF_ID(func, bpf_lsm_inode_post_setxattr)
 BTF_ID(func, bpf_lsm_inode_removexattr)
 BTF_ID(func, bpf_lsm_inode_rmdir)
-BTF_ID(func, bpf_lsm_inode_setattr)
+BTF_ID(func, bpf_lsm_path_setattr)
 BTF_ID(func, bpf_lsm_inode_setxattr)
 BTF_ID(func, bpf_lsm_inode_unlink)
 BTF_SET_END(d_inode_locked_hooks)
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 27b4917e02cb..c108c1ad22a2 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -551,8 +551,8 @@ static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
 	 * out the RO attribute for checking by the security
 	 * module, just because it maps to a file mode.
 	 */
-	err = security_inode_setattr(file_mnt_idmap(file),
-			     &file->f_path, &ia, file);
+	err = security_path_setattr(file_mnt_idmap(file),
+				     &file->f_path, &ia, file);
 	if (err)
 		goto out_unlock_inode;
 
diff --git a/fs/fat/file.c b/fs/fat/file.c
index a044270379e4..dd06c68ebcd4 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -92,8 +92,8 @@ static int fat_ioctl_set_attributes(struct file *file, u32 __user *user_attr)
 	 * out the RO attribute for checking by the security
 	 * module, just because it maps to a file mode.
 	 */
-	err = security_inode_setattr(file_mnt_idmap(file),
-			     &file->f_path, &ia, file);
+	err = security_path_setattr(file_mnt_idmap(file),
+				     &file->f_path, &ia, file);
 	if (err)
 		goto out_unlock_inode;
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 0f7712f51644..f64a8a0f94a4 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -149,7 +149,7 @@ LSM_HOOK(int, 0, inode_permission, const struct mnt_idmap *idmap,
  * are subject to change.  Implementations must not rely on its
  * properties.
  */
-LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap,
+LSM_HOOK(int, 0, path_setattr, const struct mnt_idmap *idmap,
 	 const struct path *path, struct iattr *attr, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
 	 struct dentry *dentry, int ia_valid)
diff --git a/include/linux/security.h b/include/linux/security.h
index 1e9382fb16a9..17a46b413ed3 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -430,7 +430,7 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode,
 			       bool rcu);
 int security_inode_permission(const struct mnt_idmap *idmap,
 			      struct inode *inode, int mask);
-int security_inode_setattr(const struct mnt_idmap *idmap,
+int security_path_setattr(const struct mnt_idmap *idmap,
 			   const struct path *path, struct iattr *attr,
 			   struct file *file);
 void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
@@ -1003,7 +1003,7 @@ static inline int security_inode_permission(const struct mnt_idmap *idmap,
 	return 0;
 }
 
-static inline int security_inode_setattr(const struct mnt_idmap *idmap,
+static inline int security_path_setattr(const struct mnt_idmap *idmap,
 					 const struct path *path,
 					 struct iattr *attr,
 					 struct file *file)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 293f849879e2..32f84356933e 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -345,7 +345,7 @@ BTF_ID(func, bpf_lsm_inode_readlink)
 BTF_ID(func, bpf_lsm_inode_removexattr)
 BTF_ID(func, bpf_lsm_inode_rename)
 BTF_ID(func, bpf_lsm_inode_rmdir)
-BTF_ID(func, bpf_lsm_inode_setattr)
+BTF_ID(func, bpf_lsm_path_setattr)
 BTF_ID(func, bpf_lsm_inode_setxattr)
 BTF_ID(func, bpf_lsm_inode_symlink)
 BTF_ID(func, bpf_lsm_inode_unlink)
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 5c24111a9ba7..962b31a97ec9 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -962,7 +962,7 @@ static int evm_attr_change(const struct mnt_idmap *idmap,
 }
 
 /**
- * evm_inode_setattr - prevent updating an invalid EVM extended attribute
+ * evm_path_setattr - prevent updating an invalid EVM extended attribute
  * @idmap: idmap of the mount
  * @path: pointer to the affected object
  * @attr: iattr structure containing the new file attributes
@@ -971,7 +971,7 @@ static int evm_attr_change(const struct mnt_idmap *idmap,
  * Permit update of file attributes when files have a valid EVM signature,
  * except in the case of them having an immutable portable signature.
  */
-static int evm_inode_setattr(const struct mnt_idmap *idmap,
+static int evm_path_setattr(const struct mnt_idmap *idmap,
 			     const struct path *path,
 			     struct iattr *attr, struct file *file)
 {
@@ -1212,7 +1212,7 @@ static int __init init_evm(void)
 }
 
 static struct security_hook_list evm_hooks[] __ro_after_init = {
-	LSM_HOOK_INIT(inode_setattr, evm_inode_setattr),
+	LSM_HOOK_INIT(path_setattr, evm_path_setattr),
 	LSM_HOOK_INIT(inode_post_setattr, evm_inode_post_setattr),
 	LSM_HOOK_INIT(inode_copy_up_xattr, evm_inode_copy_up_xattr),
 	LSM_HOOK_INIT(inode_setxattr, evm_inode_setxattr),
diff --git a/security/security.c b/security/security.c
index d91268f88760..01d4a463c6b1 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1865,7 +1865,7 @@ int security_inode_permission(const struct mnt_idmap *idmap,
 }
 
 /**
- * security_inode_setattr() - Check if setting file attributes is allowed
+ * security_path_setattr() - Check if setting file attributes is allowed
  * @idmap: idmap of the mount
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @attr: new attributes
@@ -1878,15 +1878,15 @@ int security_inode_permission(const struct mnt_idmap *idmap,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_setattr(const struct mnt_idmap *idmap,
+int security_path_setattr(const struct mnt_idmap *idmap,
 			   const struct path *path, struct iattr *attr,
 			   struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_setattr, idmap, path, attr, file);
+	return call_int_hook(path_setattr, idmap, path, attr, file);
 }
-EXPORT_SYMBOL_GPL(security_inode_setattr);
+EXPORT_SYMBOL_GPL(security_path_setattr);
 
 /**
  * security_inode_post_setattr() - Update the inode after a setattr operation
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e07b45fba392..4751e300bde6 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3316,7 +3316,7 @@ static int selinux_inode_permission(const struct mnt_idmap *idmap,
 	return rc;
 }
 
-static int selinux_inode_setattr(const struct mnt_idmap *idmap,
+static int selinux_path_setattr(const struct mnt_idmap *idmap,
 				 const struct path *path,
 				 struct iattr *iattr, struct file *file)
 {
@@ -7734,7 +7734,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_readlink, selinux_inode_readlink),
 	LSM_HOOK_INIT(inode_follow_link, selinux_inode_follow_link),
 	LSM_HOOK_INIT(inode_permission, selinux_inode_permission),
-	LSM_HOOK_INIT(inode_setattr, selinux_inode_setattr),
+	LSM_HOOK_INIT(path_setattr, selinux_path_setattr),
 	LSM_HOOK_INIT(path_getattr, selinux_path_getattr),
 	LSM_HOOK_INIT(inode_xattr_skipcap, selinux_inode_xattr_skipcap),
 	LSM_HOOK_INIT(inode_setxattr, selinux_inode_setxattr),
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 1aa94331c980..1cbcba3fa62b 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1265,7 +1265,7 @@ static int smack_inode_permission(const struct mnt_idmap *idmap, struct inode *i
 }
 
 /**
- * smack_inode_setattr - Smack check for setting attributes
+ * smack_path_setattr - Smack check for setting attributes
  * @idmap: idmap of the mount
  * @path: the object
  * @iattr: for the force flag
@@ -1273,7 +1273,7 @@ static int smack_inode_permission(const struct mnt_idmap *idmap, struct inode *i
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_setattr(const struct mnt_idmap *idmap,
+static int smack_path_setattr(const struct mnt_idmap *idmap,
 			       const struct path *path,
 			       struct iattr *iattr, struct file *file)
 {
@@ -5198,7 +5198,7 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_rmdir, smack_inode_rmdir),
 	LSM_HOOK_INIT(inode_rename, smack_inode_rename),
 	LSM_HOOK_INIT(inode_permission, smack_inode_permission),
-	LSM_HOOK_INIT(inode_setattr, smack_inode_setattr),
+	LSM_HOOK_INIT(path_setattr, smack_path_setattr),
 	LSM_HOOK_INIT(path_getattr, smack_path_getattr),
 	LSM_HOOK_INIT(inode_xattr_skipcap, smack_inode_xattr_skipcap),
 	LSM_HOOK_INIT(inode_setxattr, smack_inode_setxattr),
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 11/18] LSM: rename the inode xattr hooks to path_xattr hooks
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (9 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 10/18] LSM: rename inode_setattr to path_setattr Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 12/18] LSM: rename the inode posix acl hooks to path_*_acl hooks Cai Xinchen
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_setxattr, inode_getxattr, inode_listxattr and
inode_removexattr hooks receive a struct path, so rename them to
path_setxattr, path_getxattr, path_listxattr and path_removexattr to
follow the path_* naming convention used for the other hooks that
take a struct path (path_chmod, path_chown, ...).  Rename the
security_inode_*xattr() inline wrappers to security_path_*xattr()
and the SELinux, Smack, EVM and IMA implementations
accordingly.  The inode_post_setxattr and inode_post_removexattr
notification hooks still take a dentry and keep their names.

BPF programs attach to LSM hooks by name ("lsm/inode_getxattr",
"lsm.s/inode_setxattr", ...), so update the BPF selftests to the new
names.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 Documentation/bpf/fs_kfuncs.rst               |  2 +-
 fs/bpf_fs_kfuncs.c                            |  4 +-
 fs/overlayfs/inode.c                          |  2 +-
 fs/xattr.c                                    | 10 ++---
 include/linux/lsm_hook_defs.h                 |  8 ++--
 include/linux/security.h                      | 32 ++++++++--------
 kernel/bpf/bpf_lsm.c                          |  6 +--
 security/integrity/evm/evm_main.c             | 28 +++++++-------
 security/integrity/ima/ima_appraise.c         | 20 +++++-----
 security/security.c                           | 37 ++++++++++---------
 security/selinux/hooks.c                      | 27 +++++++-------
 security/smack/smack_lsm.c                    | 26 ++++++-------
 .../selftests/bpf/prog_tests/fs_kfuncs.c      | 10 ++---
 .../selftests/bpf/progs/test_get_xattr.c      |  4 +-
 .../bpf/progs/test_set_remove_xattr.c         |  8 ++--
 .../bpf/progs/verifier_lsm_init_xattr.c       |  2 +-
 .../selftests/bpf/progs/verifier_vfs_reject.c |  2 +-
 17 files changed, 115 insertions(+), 113 deletions(-)

diff --git a/Documentation/bpf/fs_kfuncs.rst b/Documentation/bpf/fs_kfuncs.rst
index 8762c3233a3d..9586dbbbf177 100644
--- a/Documentation/bpf/fs_kfuncs.rst
+++ b/Documentation/bpf/fs_kfuncs.rst
@@ -18,4 +18,4 @@ To avoid recursions, these kfuncs follow the following rules:
 1. These kfuncs are only permitted from BPF LSM function.
 2. These kfuncs should not call into other LSM hooks, i.e. security_*(). For
    example, ``bpf_get_file_xattr()`` does not use ``vfs_getxattr()``, because
-   the latter calls LSM hook ``security_inode_getxattr``.
+   the latter calls LSM hook ``security_path_getxattr``.
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 6500d8419bc1..ab25344e6205 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -566,10 +566,10 @@ BTF_SET_START(d_inode_locked_hooks)
 BTF_ID(func, bpf_lsm_inode_post_removexattr)
 BTF_ID(func, bpf_lsm_inode_post_setattr)
 BTF_ID(func, bpf_lsm_inode_post_setxattr)
-BTF_ID(func, bpf_lsm_inode_removexattr)
+BTF_ID(func, bpf_lsm_path_removexattr)
 BTF_ID(func, bpf_lsm_inode_rmdir)
 BTF_ID(func, bpf_lsm_path_setattr)
-BTF_ID(func, bpf_lsm_inode_setxattr)
+BTF_ID(func, bpf_lsm_path_setxattr)
 BTF_ID(func, bpf_lsm_inode_unlink)
 BTF_SET_END(d_inode_locked_hooks)
 
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 4f1d3077735b..9e6fab3a42d3 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -392,7 +392,7 @@ static void ovl_idmap_posix_acl(const struct inode *realinode,
  * > and following it on overlayfs.
  * >
  * > Applying the same logic to acl: we do need to call the
- * > security_inode_getxattr() on the underlying fs, even if just want to
+ * > security_path_getxattr() on the underlying fs, even if just want to
  * > check permissions on overlay. This is currently not done, which is an
  * > inconsistency.
  * >
diff --git a/fs/xattr.c b/fs/xattr.c
index 7ef3d28e5979..c97fb768ac61 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -309,8 +309,8 @@ __vfs_setxattr_locked(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		return error;
 
-	error = security_inode_setxattr(idmap, path, name, value, size,
-					flags, file);
+	error = security_path_setxattr(idmap, path, name, value, size,
+				       flags, file);
 	if (error)
 		goto out;
 
@@ -464,7 +464,7 @@ vfs_getxattr(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		return error;
 
-	error = security_inode_getxattr(path, name, file);
+	error = security_path_getxattr(path, name, file);
 	if (error)
 		return error;
 
@@ -517,7 +517,7 @@ vfs_listxattr(const struct path *path, char *list, size_t size,
 	struct inode *inode = d_inode(dentry);
 	ssize_t error;
 
-	error = security_inode_listxattr(path, file);
+	error = security_path_listxattr(path, file);
 	if (error)
 		return error;
 
@@ -580,7 +580,7 @@ __vfs_removexattr_locked(const struct mnt_idmap *idmap,
 	if (error)
 		return error;
 
-	error = security_inode_removexattr(idmap, path, name, file);
+	error = security_path_removexattr(idmap, path, name, file);
 	if (error)
 		goto out;
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index f64a8a0f94a4..4f3d19bf4644 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -156,16 +156,16 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
 LSM_HOOK(int, 0, path_getattr, const struct path *path,
 	 struct file *file)
 LSM_HOOK(int, 0, inode_xattr_skipcap, const char *name)
-LSM_HOOK(int, 0, inode_setxattr, const struct mnt_idmap *idmap,
+LSM_HOOK(int, 0, path_setxattr, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *name, const void *value,
 	 size_t size, int flags, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_setxattr, struct dentry *dentry,
 	 const char *name, const void *value, size_t size, int flags)
-LSM_HOOK(int, 0, inode_getxattr, const struct path *path, const char *name,
+LSM_HOOK(int, 0, path_getxattr, const struct path *path, const char *name,
 	 struct file *file)
-LSM_HOOK(int, 0, inode_listxattr, const struct path *path,
+LSM_HOOK(int, 0, path_listxattr, const struct path *path,
 	 struct file *file)
-LSM_HOOK(int, 0, inode_removexattr, const struct mnt_idmap *idmap,
+LSM_HOOK(int, 0, path_removexattr, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *name, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry,
 	 const char *name)
diff --git a/include/linux/security.h b/include/linux/security.h
index 17a46b413ed3..afbc12c6e614 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -436,10 +436,10 @@ int security_path_setattr(const struct mnt_idmap *idmap,
 void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
 				 int ia_valid);
 int security_path_getattr(const struct path *path, struct file *file);
-int security_inode_setxattr(const struct mnt_idmap *idmap,
-			    const struct path *path, const char *name,
-			    const void *value, size_t size, int flags,
-			    struct file *file);
+int security_path_setxattr(const struct mnt_idmap *idmap,
+			   const struct path *path, const char *name,
+			   const void *value, size_t size, int flags,
+			   struct file *file);
 int security_inode_set_acl(const struct mnt_idmap *idmap,
 			   const struct path *path, const char *acl_name,
 			   struct posix_acl *kacl, struct file *file);
@@ -456,13 +456,13 @@ void security_inode_post_remove_acl(const struct mnt_idmap *idmap,
 				    const char *acl_name);
 void security_inode_post_setxattr(struct dentry *dentry, const char *name,
 				  const void *value, size_t size, int flags);
-int security_inode_getxattr(const struct path *path,
-			    const char *name, struct file *file);
-int security_inode_listxattr(const struct path *path,
+int security_path_getxattr(const struct path *path,
+			   const char *name, struct file *file);
+int security_path_listxattr(const struct path *path,
+			    struct file *file);
+int security_path_removexattr(const struct mnt_idmap *idmap,
+			      const struct path *path, const char *name,
 			      struct file *file);
-int security_inode_removexattr(const struct mnt_idmap *idmap,
-			       const struct path *path, const char *name,
-			       struct file *file);
 void security_inode_post_removexattr(struct dentry *dentry, const char *name);
 int security_inode_file_setattr(const struct path *path,
 				struct file_kattr *fa, struct file *file);
@@ -1022,7 +1022,7 @@ static inline int security_path_getattr(const struct path *path,
 	return 0;
 }
 
-static inline int security_inode_setxattr(const struct mnt_idmap *idmap,
+static inline int security_path_setxattr(const struct mnt_idmap *idmap,
 		const struct path *path, const char *name,
 		const void *value, size_t size, int flags,
 		struct file *file)
@@ -1069,19 +1069,19 @@ static inline void security_inode_post_setxattr(struct dentry *dentry,
 		const char *name, const void *value, size_t size, int flags)
 { }
 
-static inline int security_inode_getxattr(const struct path *path,
-			const char *name, struct file *file)
+static inline int security_path_getxattr(const struct path *path,
+					 const char *name, struct file *file)
 {
 	return 0;
 }
 
-static inline int security_inode_listxattr(const struct path *path,
-					   struct file *file)
+static inline int security_path_listxattr(const struct path *path,
+					  struct file *file)
 {
 	return 0;
 }
 
-static inline int security_inode_removexattr(const struct mnt_idmap *idmap,
+static inline int security_path_removexattr(const struct mnt_idmap *idmap,
 					     const struct path *path,
 					     const char *name,
 					     struct file *file)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 32f84356933e..9d7771417812 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -336,17 +336,17 @@ BTF_ID(func, bpf_lsm_file_receive)
 BTF_ID(func, bpf_lsm_inode_create)
 BTF_ID(func, bpf_lsm_inode_free_security)
 BTF_ID(func, bpf_lsm_path_getattr)
-BTF_ID(func, bpf_lsm_inode_getxattr)
+BTF_ID(func, bpf_lsm_path_getxattr)
 BTF_ID(func, bpf_lsm_inode_mknod)
 BTF_ID(func, bpf_lsm_inode_need_killpriv)
 BTF_ID(func, bpf_lsm_inode_post_setxattr)
 BTF_ID(func, bpf_lsm_inode_post_removexattr)
 BTF_ID(func, bpf_lsm_inode_readlink)
-BTF_ID(func, bpf_lsm_inode_removexattr)
+BTF_ID(func, bpf_lsm_path_removexattr)
 BTF_ID(func, bpf_lsm_inode_rename)
 BTF_ID(func, bpf_lsm_inode_rmdir)
 BTF_ID(func, bpf_lsm_path_setattr)
-BTF_ID(func, bpf_lsm_inode_setxattr)
+BTF_ID(func, bpf_lsm_path_setxattr)
 BTF_ID(func, bpf_lsm_inode_symlink)
 BTF_ID(func, bpf_lsm_inode_unlink)
 BTF_ID(func, bpf_lsm_kernel_module_request)
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 962b31a97ec9..025606fe5fe8 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -7,8 +7,8 @@
  * Kylene Hall <kjhall@us.ibm.com>
  *
  * File: evm_main.c
- *	implements evm_inode_setxattr, evm_inode_post_setxattr,
- *	evm_inode_removexattr, evm_verifyxattr, and evm_inode_set_acl.
+ *	implements evm_path_setxattr, evm_inode_post_setxattr,
+ *	evm_path_removexattr, evm_verifyxattr, and evm_inode_set_acl.
  */
 
 #define pr_fmt(fmt) "EVM: "fmt
@@ -593,7 +593,7 @@ static int evm_protect_xattr(const struct mnt_idmap *idmap,
 }
 
 /**
- * evm_inode_setxattr - protect the EVM extended attribute
+ * evm_path_setxattr - protect the EVM extended attribute
  * @idmap: idmap of the mount
  * @path: pointer to the affected object
  * @xattr_name: pointer to the affected extended attribute name
@@ -608,11 +608,11 @@ static int evm_protect_xattr(const struct mnt_idmap *idmap,
  * userspace from writing HMAC value.  Writing 'security.evm' requires
  * requires CAP_SYS_ADMIN privileges.
  */
-static int evm_inode_setxattr(const struct mnt_idmap *idmap,
-			      const struct path *path,
-			      const char *xattr_name, const void *xattr_value,
-			      size_t xattr_value_len, int flags,
-			      struct file *file)
+static int evm_path_setxattr(const struct mnt_idmap *idmap,
+			     const struct path *path,
+			     const char *xattr_name, const void *xattr_value,
+			     size_t xattr_value_len, int flags,
+			     struct file *file)
 {
 	const struct evm_ima_xattr_data *xattr_data = xattr_value;
 	struct dentry *dentry = path->dentry;
@@ -635,7 +635,7 @@ static int evm_inode_setxattr(const struct mnt_idmap *idmap,
 }
 
 /**
- * evm_inode_removexattr - protect the EVM extended attribute
+ * evm_path_removexattr - protect the EVM extended attribute
  * @idmap: idmap of the mount
  * @path: pointer to the affected object
  * @xattr_name: pointer to the affected extended attribute name
@@ -644,9 +644,9 @@ static int evm_inode_setxattr(const struct mnt_idmap *idmap,
  * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that
  * the current value is valid.
  */
-static int evm_inode_removexattr(const struct mnt_idmap *idmap,
-				 const struct path *path,
-				 const char *xattr_name, struct file *file)
+static int evm_path_removexattr(const struct mnt_idmap *idmap,
+				const struct path *path,
+				const char *xattr_name, struct file *file)
 {
 	struct dentry *dentry = path->dentry;
 
@@ -1215,13 +1215,13 @@ static struct security_hook_list evm_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_setattr, evm_path_setattr),
 	LSM_HOOK_INIT(inode_post_setattr, evm_inode_post_setattr),
 	LSM_HOOK_INIT(inode_copy_up_xattr, evm_inode_copy_up_xattr),
-	LSM_HOOK_INIT(inode_setxattr, evm_inode_setxattr),
+	LSM_HOOK_INIT(path_setxattr, evm_path_setxattr),
 	LSM_HOOK_INIT(inode_post_setxattr, evm_inode_post_setxattr),
 	LSM_HOOK_INIT(inode_set_acl, evm_inode_set_acl),
 	LSM_HOOK_INIT(inode_post_set_acl, evm_inode_post_set_acl),
 	LSM_HOOK_INIT(inode_remove_acl, evm_inode_remove_acl),
 	LSM_HOOK_INIT(inode_post_remove_acl, evm_inode_post_remove_acl),
-	LSM_HOOK_INIT(inode_removexattr, evm_inode_removexattr),
+	LSM_HOOK_INIT(path_removexattr, evm_path_removexattr),
 	LSM_HOOK_INIT(inode_post_removexattr, evm_inode_post_removexattr),
 	LSM_HOOK_INIT(inode_init_security, evm_inode_init_security),
 	LSM_HOOK_INIT(inode_alloc_security, evm_inode_alloc_security),
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index a231f1f419f7..c701dce1e837 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -759,11 +759,11 @@ static int validate_hash_algo(struct dentry *dentry,
 	return -EACCES;
 }
 
-static int ima_inode_setxattr(const struct mnt_idmap *idmap,
-			      const struct path *path,
-			      const char *xattr_name, const void *xattr_value,
-			      size_t xattr_value_len, int flags,
-			      struct file *file)
+static int ima_path_setxattr(const struct mnt_idmap *idmap,
+			   const struct path *path,
+			   const char *xattr_name, const void *xattr_value,
+			   size_t xattr_value_len, int flags,
+			   struct file *file)
 {
 	const struct evm_ima_xattr_data *xvalue = xattr_value;
 	struct dentry *dentry = path->dentry;
@@ -806,9 +806,9 @@ static int ima_inode_set_acl(const struct mnt_idmap *idmap,
 	return 0;
 }
 
-static int ima_inode_removexattr(const struct mnt_idmap *idmap,
-				 const struct path *path,
-				 const char *xattr_name, struct file *file)
+static int ima_path_removexattr(const struct mnt_idmap *idmap,
+				const struct path *path,
+				const char *xattr_name, struct file *file)
 {
 	int result, digsig = -1;
 
@@ -832,9 +832,9 @@ static int ima_inode_remove_acl(const struct mnt_idmap *idmap,
 
 static struct security_hook_list ima_appraise_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_post_setattr, ima_inode_post_setattr),
-	LSM_HOOK_INIT(inode_setxattr, ima_inode_setxattr),
+	LSM_HOOK_INIT(path_setxattr, ima_path_setxattr),
 	LSM_HOOK_INIT(inode_set_acl, ima_inode_set_acl),
-	LSM_HOOK_INIT(inode_removexattr, ima_inode_removexattr),
+	LSM_HOOK_INIT(path_removexattr, ima_path_removexattr),
 	LSM_HOOK_INIT(inode_remove_acl, ima_inode_remove_acl),
 };
 
diff --git a/security/security.c b/security/security.c
index 01d4a463c6b1..bca46f5444bd 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1921,7 +1921,7 @@ int security_path_getattr(const struct path *path, struct file *file)
 }
 
 /**
- * security_inode_setxattr() - Check if setting file xattrs is allowed
+ * security_path_setxattr() - Check if setting file xattrs is allowed
  * @idmap: idmap of the mount
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @name: xattr name
@@ -1947,10 +1947,10 @@ int security_path_getattr(const struct path *path, struct file *file)
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_setxattr(const struct mnt_idmap *idmap,
-			    const struct path *path, const char *name,
-			    const void *value, size_t size, int flags,
-			    struct file *file)
+int security_path_setxattr(const struct mnt_idmap *idmap,
+			   const struct path *path, const char *name,
+			   const void *value, size_t size, int flags,
+			   struct file *file)
 {
 	int rc;
 
@@ -1964,7 +1964,7 @@ int security_inode_setxattr(const struct mnt_idmap *idmap,
 			return rc;
 	}
 
-	return call_int_hook(inode_setxattr, idmap, path, name, value,
+	return call_int_hook(path_setxattr, idmap, path, name, value,
 			     size, flags, file);
 }
 
@@ -2086,7 +2086,7 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name,
 }
 
 /**
- * security_inode_getxattr() - Check if xattr access is allowed
+ * security_path_getxattr() - Check if xattr access is allowed
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @name: xattr name
  * @file: file through which the xattr access is requested, if any
@@ -2096,16 +2096,16 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_getxattr(const struct path *path,
-			    const char *name, struct file *file)
+int security_path_getxattr(const struct path *path,
+			   const char *name, struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_getxattr, path, name, file);
+	return call_int_hook(path_getxattr, path, name, file);
 }
 
 /**
- * security_inode_listxattr() - Check if listing xattrs is allowed
+ * security_path_listxattr() - Check if listing xattrs is allowed
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @file: file through which the xattr names are listed, if any
  *
@@ -2114,15 +2114,16 @@ int security_inode_getxattr(const struct path *path,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_listxattr(const struct path *path, struct file *file)
+int security_path_listxattr(const struct path *path,
+			    struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_listxattr, path, file);
+	return call_int_hook(path_listxattr, path, file);
 }
 
 /**
- * security_inode_removexattr() - Check if removing an xattr is allowed
+ * security_path_removexattr() - Check if removing an xattr is allowed
  * @idmap: idmap of the mount
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @name: xattr name
@@ -2145,9 +2146,9 @@ int security_inode_listxattr(const struct path *path, struct file *file)
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_removexattr(const struct mnt_idmap *idmap,
-			       const struct path *path, const char *name,
-			       struct file *file)
+int security_path_removexattr(const struct mnt_idmap *idmap,
+			      const struct path *path, const char *name,
+			      struct file *file)
 {
 	int rc;
 
@@ -2161,7 +2162,7 @@ int security_inode_removexattr(const struct mnt_idmap *idmap,
 			return rc;
 	}
 
-	return call_int_hook(inode_removexattr, idmap, path, name, file);
+	return call_int_hook(path_removexattr, idmap, path, name, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 4751e300bde6..62053a9f7d9e 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3389,8 +3389,8 @@ static int selinux_inode_xattr_skipcap(const char *name)
 	return !strcmp(name, XATTR_NAME_SELINUX);
 }
 
-static int selinux_inode_setxattr(const struct mnt_idmap *idmap,
-				  const struct path *path,
+static int selinux_path_setxattr(const struct mnt_idmap *idmap,
+				 const struct path *path,
 				  const char *name, const void *value,
 				  size_t size, int flags, struct file *file)
 {
@@ -3539,24 +3539,25 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name,
 	spin_unlock(&isec->lock);
 }
 
-static int selinux_inode_getxattr(const struct path *path,
-				  const char *name, struct file *file)
+static int selinux_path_getxattr(const struct path *path, const char *name,
+				 struct file *file)
 {
 	const struct cred *cred = current_cred();
 
 	return dentry_has_perm(cred, path->dentry, FILE__GETATTR);
 }
 
-static int selinux_inode_listxattr(const struct path *path, struct file *file)
+static int selinux_path_listxattr(const struct path *path,
+				  struct file *file)
 {
 	const struct cred *cred = current_cred();
 
 	return dentry_has_perm(cred, path->dentry, FILE__GETATTR);
 }
 
-static int selinux_inode_removexattr(const struct mnt_idmap *idmap,
-				     const struct path *path,
-				     const char *name, struct file *file)
+static int selinux_path_removexattr(const struct mnt_idmap *idmap,
+				    const struct path *path,
+				    const char *name, struct file *file)
 {
 	/* if not a selinux xattr, only check the ordinary setattr perm */
 	if (strcmp(name, XATTR_NAME_SELINUX))
@@ -3636,7 +3637,7 @@ static int selinux_path_notify(const struct path *path, u64 mask,
 /*
  * Copy the inode security context value to the user.
  *
- * Permission check is handled by selinux_inode_getxattr hook.
+ * Permission check is handled by selinux_path_getxattr hook.
  */
 static int selinux_inode_getsecurity(const struct mnt_idmap *idmap,
 				     struct inode *inode, const char *name,
@@ -7737,11 +7738,11 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_setattr, selinux_path_setattr),
 	LSM_HOOK_INIT(path_getattr, selinux_path_getattr),
 	LSM_HOOK_INIT(inode_xattr_skipcap, selinux_inode_xattr_skipcap),
-	LSM_HOOK_INIT(inode_setxattr, selinux_inode_setxattr),
+	LSM_HOOK_INIT(path_setxattr, selinux_path_setxattr),
 	LSM_HOOK_INIT(inode_post_setxattr, selinux_inode_post_setxattr),
-	LSM_HOOK_INIT(inode_getxattr, selinux_inode_getxattr),
-	LSM_HOOK_INIT(inode_listxattr, selinux_inode_listxattr),
-	LSM_HOOK_INIT(inode_removexattr, selinux_inode_removexattr),
+	LSM_HOOK_INIT(path_getxattr, selinux_path_getxattr),
+	LSM_HOOK_INIT(path_listxattr, selinux_path_listxattr),
+	LSM_HOOK_INIT(path_removexattr, selinux_path_removexattr),
 	LSM_HOOK_INIT(inode_file_getattr, selinux_inode_file_getattr),
 	LSM_HOOK_INIT(inode_file_setattr, selinux_inode_file_setattr),
 	LSM_HOOK_INIT(inode_set_acl, selinux_inode_set_acl),
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 1cbcba3fa62b..eed06cf7af99 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1343,7 +1343,7 @@ static int smack_inode_xattr_skipcap(const char *name)
 }
 
 /**
- * smack_inode_setxattr - Smack check for setting xattrs
+ * smack_path_setxattr - Smack check for setting xattrs
  * @idmap: idmap of the mount
  * @path: the object
  * @name: name of the attribute
@@ -1356,8 +1356,8 @@ static int smack_inode_xattr_skipcap(const char *name)
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_setxattr(const struct mnt_idmap *idmap,
-				const struct path *path,
+static int smack_path_setxattr(const struct mnt_idmap *idmap,
+			       const struct path *path,
 				const char *name, const void *value,
 				size_t size, int flags, struct file *file)
 {
@@ -1463,15 +1463,15 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name,
 }
 
 /**
- * smack_inode_getxattr - Smack check on getxattr
+ * smack_path_getxattr - Smack check on getxattr
  * @path: the object
  * @name: unused
  * @file: file through which the xattr access is requested, if any
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_getxattr(const struct path *path,
-			const char *name, struct file *file)
+static int smack_path_getxattr(const struct path *path, const char *name,
+			       struct file *file)
 {
 	struct smk_audit_info ad;
 	struct dentry *dentry = path->dentry;
@@ -1486,7 +1486,7 @@ static int smack_inode_getxattr(const struct path *path,
 }
 
 /**
- * smack_inode_removexattr - Smack check on removexattr
+ * smack_path_removexattr - Smack check on removexattr
  * @idmap: idmap of the mount
  * @path: the object
  * @name: name of the attribute
@@ -1496,9 +1496,9 @@ static int smack_inode_getxattr(const struct path *path,
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_removexattr(const struct mnt_idmap *idmap,
-				   const struct path *path,
-				   const char *name, struct file *file)
+static int smack_path_removexattr(const struct mnt_idmap *idmap,
+				  const struct path *path,
+				  const char *name, struct file *file)
 {
 	struct inode_smack *isp;
 	struct smk_audit_info ad;
@@ -5201,10 +5201,10 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_setattr, smack_path_setattr),
 	LSM_HOOK_INIT(path_getattr, smack_path_getattr),
 	LSM_HOOK_INIT(inode_xattr_skipcap, smack_inode_xattr_skipcap),
-	LSM_HOOK_INIT(inode_setxattr, smack_inode_setxattr),
+	LSM_HOOK_INIT(path_setxattr, smack_path_setxattr),
 	LSM_HOOK_INIT(inode_post_setxattr, smack_inode_post_setxattr),
-	LSM_HOOK_INIT(inode_getxattr, smack_inode_getxattr),
-	LSM_HOOK_INIT(inode_removexattr, smack_inode_removexattr),
+	LSM_HOOK_INIT(path_getxattr, smack_path_getxattr),
+	LSM_HOOK_INIT(path_removexattr, smack_path_removexattr),
 	LSM_HOOK_INIT(inode_set_acl, smack_inode_set_acl),
 	LSM_HOOK_INIT(inode_get_acl, smack_inode_get_acl),
 	LSM_HOOK_INIT(inode_remove_acl, smack_inode_remove_acl),
diff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
index 43a26ec69a8e..5145b1f03cac 100644
--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
@@ -53,7 +53,7 @@ static void test_get_xattr(const char *name, const char *value, bool allow_acces
 	if (!ASSERT_GE(fd, 0, "open_file"))
 		goto out;
 
-	/* Trigger security_inode_getxattr */
+	/* Trigger security_path_getxattr */
 	err = getxattr(testfile, name, v, sizeof(v));
 
 	if (allow_access) {
@@ -148,13 +148,13 @@ static void test_set_remove_xattr(void)
 
 	/* First, test not _locked version of the kfuncs with getxattr. */
 
-	/* Read security.bpf.foo and trigger test_inode_getxattr. This
+	/* Read security.bpf.foo and trigger test_path_getxattr. This
 	 * bpf program will set security.bpf.bar to "world".
 	 */
 	read_and_validate_foo(skel);
 	validate_bar_match(skel);
 
-	/* Read security.bpf.foo and trigger test_inode_getxattr again.
+	/* Read security.bpf.foo and trigger test_path_getxattr again.
 	 * This will remove xattr security.bpf.bar.
 	 */
 	read_and_validate_foo(skel);
@@ -167,13 +167,13 @@ static void test_set_remove_xattr(void)
 
 	/* Second, test _locked version of the kfuncs, with setxattr */
 
-	/* Set security.bpf.foo and trigger test_inode_setxattr. This
+	/* Set security.bpf.foo and trigger test_path_setxattr. This
 	 * bpf program will set security.bpf.bar to "world".
 	 */
 	set_foo(skel);
 	validate_bar_match(skel);
 
-	/* Set security.bpf.foo and trigger test_inode_setxattr again.
+	/* Set security.bpf.foo and trigger test_path_setxattr again.
 	 * This will remove xattr security.bpf.bar.
 	 */
 	set_foo(skel);
diff --git a/tools/testing/selftests/bpf/progs/test_get_xattr.c b/tools/testing/selftests/bpf/progs/test_get_xattr.c
index 97361ff10e9f..c6bde355b58f 100644
--- a/tools/testing/selftests/bpf/progs/test_get_xattr.c
+++ b/tools/testing/selftests/bpf/progs/test_get_xattr.c
@@ -55,8 +55,8 @@ int BPF_PROG(test_file_open, struct file *f)
 	return 0;
 }
 
-SEC("lsm.s/inode_getxattr")
-int BPF_PROG(test_inode_getxattr, const struct path *path, char *name,
+SEC("lsm.s/path_getxattr")
+int BPF_PROG(test_path_getxattr, const struct path *path, char *name,
 	     struct file *file)
 {
 	struct bpf_dynptr value_ptr;
diff --git a/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c b/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
index 06656ac9a777..d096b00cf3bf 100644
--- a/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
+++ b/tools/testing/selftests/bpf/progs/test_set_remove_xattr.c
@@ -32,8 +32,8 @@ static inline bool name_match_foo(const char *name)
 }
 
 /* Test bpf_set_dentry_xattr and bpf_remove_dentry_xattr */
-SEC("lsm.s/inode_getxattr")
-int BPF_PROG(test_inode_getxattr, const struct path *path, char *name,
+SEC("lsm.s/path_getxattr")
+int BPF_PROG(test_path_getxattr, const struct path *path, char *name,
 	     struct file *file)
 {
 	struct bpf_dynptr value_ptr;
@@ -87,8 +87,8 @@ bool locked_remove_security_selinux_fail;
  * not-_locked version in the BPF program. The verifier will fix them up
  * properly.
  */
-SEC("lsm.s/inode_setxattr")
-int BPF_PROG(test_inode_setxattr, const struct mnt_idmap *idmap,
+SEC("lsm.s/path_setxattr")
+int BPF_PROG(test_path_setxattr, const struct mnt_idmap *idmap,
 	     const struct path *path,
 	     const char *name, const void *value, size_t size, int flags,
 	     struct file *file)
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
index 9b7a15c1862f..bc728fdd0b0f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
@@ -232,7 +232,7 @@ int BPF_PROG(reject_lsm_cgroup, struct inode *inode, struct inode *dir,
 	return 0;
 }
 
-SEC("lsm/inode_setxattr")
+SEC("lsm/path_setxattr")
 __failure __msg("calling kernel function bpf_inode_init_xattr is not allowed")
 int BPF_PROG(reject_wrong_hook, const struct mnt_idmap *idmap,
 	     const struct path *path,
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index ed2bd2faebf3..f41750c3fc70 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -46,7 +46,7 @@ int BPF_PROG(get_task_exe_file_kfunc_null)
 	return 0;
 }
 
-SEC("lsm.s/inode_getxattr")
+SEC("lsm.s/path_getxattr")
 __failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(get_task_exe_file_kfunc_fp)
 {
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 12/18] LSM: rename the inode posix acl hooks to path_*_acl hooks
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (10 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 11/18] LSM: rename the inode xattr hooks to path_xattr hooks Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 13/18] LSM: rename the inode fileattr hooks to path_file_* hooks Cai Xinchen
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_set_acl, inode_get_acl and inode_remove_acl hooks receive a
struct path, so rename them to path_set_acl, path_get_acl and
path_remove_acl to follow the path_* naming convention used for the
other hooks that take a struct path (path_chmod, path_chown, ...).
Rename the security_inode_*_acl() inline wrappers to
security_path_*_acl() and the SELinux, Smack, EVM and IMA
implementations accordingly.  The inode_post_set_acl and
inode_post_remove_acl notification hooks still take a dentry and keep
their names.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/posix_acl.c                        |  6 ++--
 include/linux/lsm_hook_defs.h         |  6 ++--
 include/linux/security.h              | 44 +++++++++++++--------------
 security/integrity/evm/evm_main.c     | 26 ++++++++--------
 security/integrity/ima/ima_appraise.c | 20 ++++++------
 security/security.c                   | 32 +++++++++----------
 security/selinux/hooks.c              | 26 ++++++++--------
 security/smack/smack_lsm.c            | 32 +++++++++----------
 8 files changed, 96 insertions(+), 96 deletions(-)

diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 487e17e91e2b..5feeb651fa85 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -1130,7 +1130,7 @@ int vfs_set_acl(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		goto out_inode_unlock;
 
-	error = security_inode_set_acl(idmap, path, acl_name, kacl, file);
+	error = security_path_set_acl(idmap, path, acl_name, kacl, file);
 	if (error)
 		goto out_inode_unlock;
 
@@ -1189,7 +1189,7 @@ struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap,
 	 * The VFS has no restrictions on reading POSIX ACLs so calling
 	 * something like xattr_permission() isn't needed. Only LSMs get a say.
 	 */
-	error = security_inode_get_acl(idmap, path, acl_name, file);
+	error = security_path_get_acl(idmap, path, acl_name, file);
 	if (error)
 		return ERR_PTR(error);
 
@@ -1243,7 +1243,7 @@ int vfs_remove_acl(const struct mnt_idmap *idmap, const struct path *path,
 	if (error)
 		goto out_inode_unlock;
 
-	error = security_inode_remove_acl(idmap, path, acl_name, file);
+	error = security_path_remove_acl(idmap, path, acl_name, file);
 	if (error)
 		goto out_inode_unlock;
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 4f3d19bf4644..7602a3074375 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -173,15 +173,15 @@ LSM_HOOK(int, 0, inode_file_setattr, const struct path *path,
 	 struct file_kattr *fa, struct file *file)
 LSM_HOOK(int, 0, inode_file_getattr, const struct path *path,
 	 struct file_kattr *fa, struct file *file)
-LSM_HOOK(int, 0, inode_set_acl, const struct mnt_idmap *idmap,
+LSM_HOOK(int, 0, path_set_acl, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *acl_name,
 	 struct posix_acl *kacl, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_set_acl, struct dentry *dentry,
 	 const char *acl_name, struct posix_acl *kacl)
-LSM_HOOK(int, 0, inode_get_acl, const struct mnt_idmap *idmap,
+LSM_HOOK(int, 0, path_get_acl, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *acl_name,
 	 struct file *file)
-LSM_HOOK(int, 0, inode_remove_acl, const struct mnt_idmap *idmap,
+LSM_HOOK(int, 0, path_remove_acl, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *acl_name,
 	 struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_remove_acl, const struct mnt_idmap *idmap,
diff --git a/include/linux/security.h b/include/linux/security.h
index afbc12c6e614..305b92c0494c 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -440,17 +440,17 @@ int security_path_setxattr(const struct mnt_idmap *idmap,
 			   const struct path *path, const char *name,
 			   const void *value, size_t size, int flags,
 			   struct file *file);
-int security_inode_set_acl(const struct mnt_idmap *idmap,
-			   const struct path *path, const char *acl_name,
-			   struct posix_acl *kacl, struct file *file);
+int security_path_set_acl(const struct mnt_idmap *idmap,
+			  const struct path *path, const char *acl_name,
+			  struct posix_acl *kacl, struct file *file);
 void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
 				 struct posix_acl *kacl);
-int security_inode_get_acl(const struct mnt_idmap *idmap,
-			   const struct path *path, const char *acl_name,
-			   struct file *file);
-int security_inode_remove_acl(const struct mnt_idmap *idmap,
-			      const struct path *path, const char *acl_name,
-			      struct file *file);
+int security_path_get_acl(const struct mnt_idmap *idmap,
+			  const struct path *path, const char *acl_name,
+			  struct file *file);
+int security_path_remove_acl(const struct mnt_idmap *idmap,
+			     const struct path *path, const char *acl_name,
+			     struct file *file);
 void security_inode_post_remove_acl(const struct mnt_idmap *idmap,
 				    struct dentry *dentry,
 				    const char *acl_name);
@@ -1030,11 +1030,11 @@ static inline int security_path_setxattr(const struct mnt_idmap *idmap,
 	return cap_inode_setxattr(path, name, value, size, flags, file);
 }
 
-static inline int security_inode_set_acl(const struct mnt_idmap *idmap,
-					 const struct path *path,
-					 const char *acl_name,
-					 struct posix_acl *kacl,
-					 struct file *file)
+static inline int security_path_set_acl(const struct mnt_idmap *idmap,
+				       const struct path *path,
+				       const char *acl_name,
+				       struct posix_acl *kacl,
+				       struct file *file)
 {
 	return 0;
 }
@@ -1044,18 +1044,18 @@ static inline void security_inode_post_set_acl(struct dentry *dentry,
 					       struct posix_acl *kacl)
 { }
 
-static inline int security_inode_get_acl(const struct mnt_idmap *idmap,
-					 const struct path *path,
-					 const char *acl_name,
-					 struct file *file)
+static inline int security_path_get_acl(const struct mnt_idmap *idmap,
+				       const struct path *path,
+				       const char *acl_name,
+				       struct file *file)
 {
 	return 0;
 }
 
-static inline int security_inode_remove_acl(const struct mnt_idmap *idmap,
-					    const struct path *path,
-					    const char *acl_name,
-					    struct file *file)
+static inline int security_path_remove_acl(const struct mnt_idmap *idmap,
+					  const struct path *path,
+					  const char *acl_name,
+					  struct file *file)
 {
 	return 0;
 }
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 025606fe5fe8..a0702eeba02b 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -8,7 +8,7 @@
  *
  * File: evm_main.c
  *	implements evm_path_setxattr, evm_inode_post_setxattr,
- *	evm_path_removexattr, evm_verifyxattr, and evm_inode_set_acl.
+ *	evm_path_removexattr, evm_verifyxattr, and evm_path_set_acl.
  */
 
 #define pr_fmt(fmt) "EVM: "fmt
@@ -689,7 +689,7 @@ static inline int evm_inode_set_acl_change(const struct mnt_idmap *idmap,
 #endif
 
 /**
- * evm_inode_set_acl - protect the EVM extended attribute from posix acls
+ * evm_path_set_acl - protect the EVM extended attribute from posix acls
  * @idmap: idmap of the mount
  * @path: pointer to the affected object
  * @acl_name: name of the posix acl
@@ -702,10 +702,10 @@ static inline int evm_inode_set_acl_change(const struct mnt_idmap *idmap,
  *
  * Return: zero on success, -EPERM on failure.
  */
-static int evm_inode_set_acl(const struct mnt_idmap *idmap,
-			     const struct path *path,
-			     const char *acl_name, struct posix_acl *kacl,
-			     struct file *file)
+static int evm_path_set_acl(const struct mnt_idmap *idmap,
+			    const struct path *path,
+			    const char *acl_name, struct posix_acl *kacl,
+			    struct file *file)
 {
 	enum integrity_status evm_status;
 	struct dentry *dentry = path->dentry;
@@ -746,7 +746,7 @@ static int evm_inode_set_acl(const struct mnt_idmap *idmap,
 }
 
 /**
- * evm_inode_remove_acl - Protect the EVM extended attribute from posix acls
+ * evm_path_remove_acl - Protect the EVM extended attribute from posix acls
  * @idmap: idmap of the mount
  * @path: pointer to the affected object
  * @acl_name: name of the posix acl
@@ -758,11 +758,11 @@ static int evm_inode_set_acl(const struct mnt_idmap *idmap,
  *
  * Return: zero on success, -EPERM on failure.
  */
-static int evm_inode_remove_acl(const struct mnt_idmap *idmap,
-				const struct path *path,
-				const char *acl_name, struct file *file)
+static int evm_path_remove_acl(const struct mnt_idmap *idmap,
+			       const struct path *path,
+			       const char *acl_name, struct file *file)
 {
-	return evm_inode_set_acl(idmap, path, acl_name, NULL, file);
+	return evm_path_set_acl(idmap, path, acl_name, NULL, file);
 }
 
 static void evm_reset_status(struct inode *inode)
@@ -1217,9 +1217,9 @@ static struct security_hook_list evm_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_copy_up_xattr, evm_inode_copy_up_xattr),
 	LSM_HOOK_INIT(path_setxattr, evm_path_setxattr),
 	LSM_HOOK_INIT(inode_post_setxattr, evm_inode_post_setxattr),
-	LSM_HOOK_INIT(inode_set_acl, evm_inode_set_acl),
+	LSM_HOOK_INIT(path_set_acl, evm_path_set_acl),
 	LSM_HOOK_INIT(inode_post_set_acl, evm_inode_post_set_acl),
-	LSM_HOOK_INIT(inode_remove_acl, evm_inode_remove_acl),
+	LSM_HOOK_INIT(path_remove_acl, evm_path_remove_acl),
 	LSM_HOOK_INIT(inode_post_remove_acl, evm_inode_post_remove_acl),
 	LSM_HOOK_INIT(path_removexattr, evm_path_removexattr),
 	LSM_HOOK_INIT(inode_post_removexattr, evm_inode_post_removexattr),
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index c701dce1e837..8b12f73afa62 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -795,10 +795,10 @@ static int ima_path_setxattr(const struct mnt_idmap *idmap,
 	return result;
 }
 
-static int ima_inode_set_acl(const struct mnt_idmap *idmap,
-			     const struct path *path,
-			     const char *acl_name, struct posix_acl *kacl,
-			     struct file *file)
+static int ima_path_set_acl(const struct mnt_idmap *idmap,
+			   const struct path *path,
+			   const char *acl_name, struct posix_acl *kacl,
+			   struct file *file)
 {
 	if (evm_revalidate_status(acl_name))
 		ima_reset_appraise_flags(d_backing_inode(path->dentry), -1);
@@ -823,19 +823,19 @@ static int ima_path_removexattr(const struct mnt_idmap *idmap,
 	return result;
 }
 
-static int ima_inode_remove_acl(const struct mnt_idmap *idmap,
-				 const struct path *path,
-				 const char *acl_name, struct file *file)
+static int ima_path_remove_acl(const struct mnt_idmap *idmap,
+				const struct path *path,
+				const char *acl_name, struct file *file)
 {
-	return ima_inode_set_acl(idmap, path, acl_name, NULL, file);
+	return ima_path_set_acl(idmap, path, acl_name, NULL, file);
 }
 
 static struct security_hook_list ima_appraise_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_post_setattr, ima_inode_post_setattr),
 	LSM_HOOK_INIT(path_setxattr, ima_path_setxattr),
-	LSM_HOOK_INIT(inode_set_acl, ima_inode_set_acl),
+	LSM_HOOK_INIT(path_set_acl, ima_path_set_acl),
 	LSM_HOOK_INIT(path_removexattr, ima_path_removexattr),
-	LSM_HOOK_INIT(inode_remove_acl, ima_inode_remove_acl),
+	LSM_HOOK_INIT(path_remove_acl, ima_path_remove_acl),
 };
 
 void __init init_ima_appraise_lsm(const struct lsm_id *lsmid)
diff --git a/security/security.c b/security/security.c
index bca46f5444bd..13778c83fd3a 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1969,7 +1969,7 @@ int security_path_setxattr(const struct mnt_idmap *idmap,
 }
 
 /**
- * security_inode_set_acl() - Check if setting posix acls is allowed
+ * security_path_set_acl() - Check if setting posix acls is allowed
  * @idmap: idmap of the mount
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @acl_name: acl name
@@ -1981,14 +1981,14 @@ int security_path_setxattr(const struct mnt_idmap *idmap,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_set_acl(const struct mnt_idmap *idmap,
-			   const struct path *path, const char *acl_name,
-			   struct posix_acl *kacl, struct file *file)
+int security_path_set_acl(const struct mnt_idmap *idmap,
+			  const struct path *path, const char *acl_name,
+			  struct posix_acl *kacl, struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_set_acl, idmap, path, acl_name, kacl,
-			       file);
+	return call_int_hook(path_set_acl, idmap, path, acl_name, kacl,
+			     file);
 }
 
 /**
@@ -2009,7 +2009,7 @@ void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
 }
 
 /**
- * security_inode_get_acl() - Check if reading posix acls is allowed
+ * security_path_get_acl() - Check if reading posix acls is allowed
  * @idmap: idmap of the mount
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @acl_name: acl name
@@ -2020,17 +2020,17 @@ void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_get_acl(const struct mnt_idmap *idmap,
-			   const struct path *path, const char *acl_name,
-			   struct file *file)
+int security_path_get_acl(const struct mnt_idmap *idmap,
+			  const struct path *path, const char *acl_name,
+			  struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_get_acl, idmap, path, acl_name, file);
+	return call_int_hook(path_get_acl, idmap, path, acl_name, file);
 }
 
 /**
- * security_inode_remove_acl() - Check if removing a posix acl is allowed
+ * security_path_remove_acl() - Check if removing a posix acl is allowed
  * @idmap: idmap of the mount
  * @path: file (the VFS makes no guarantees with regard to @path->mnt)
  * @acl_name: acl name
@@ -2041,13 +2041,13 @@ int security_inode_get_acl(const struct mnt_idmap *idmap,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_remove_acl(const struct mnt_idmap *idmap,
-			      const struct path *path, const char *acl_name,
-			      struct file *file)
+int security_path_remove_acl(const struct mnt_idmap *idmap,
+			     const struct path *path, const char *acl_name,
+			     struct file *file)
 {
 	if (unlikely(IS_PRIVATE(d_backing_inode(path->dentry))))
 		return 0;
-	return call_int_hook(inode_remove_acl, idmap, path, acl_name, file);
+	return call_int_hook(path_remove_acl, idmap, path, acl_name, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 62053a9f7d9e..e0aaaee537d1 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3477,24 +3477,24 @@ static int selinux_path_setxattr(const struct mnt_idmap *idmap,
 			    &ad);
 }
 
-static int selinux_inode_set_acl(const struct mnt_idmap *idmap,
-				 const struct path *path,
-				 const char *acl_name, struct posix_acl *kacl,
-				 struct file *file)
+static int selinux_path_set_acl(const struct mnt_idmap *idmap,
+				const struct path *path,
+				const char *acl_name, struct posix_acl *kacl,
+				struct file *file)
 {
 	return dentry_has_perm(current_cred(), path->dentry, FILE__SETATTR);
 }
 
-static int selinux_inode_get_acl(const struct mnt_idmap *idmap,
-				 const struct path *path,
-				 const char *acl_name, struct file *file)
+static int selinux_path_get_acl(const struct mnt_idmap *idmap,
+				const struct path *path,
+				const char *acl_name, struct file *file)
 {
 	return dentry_has_perm(current_cred(), path->dentry, FILE__GETATTR);
 }
 
-static int selinux_inode_remove_acl(const struct mnt_idmap *idmap,
-				    const struct path *path,
-				    const char *acl_name, struct file *file)
+static int selinux_path_remove_acl(const struct mnt_idmap *idmap,
+				   const struct path *path,
+				   const char *acl_name, struct file *file)
 {
 	return dentry_has_perm(current_cred(), path->dentry, FILE__SETATTR);
 }
@@ -7745,9 +7745,9 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_removexattr, selinux_path_removexattr),
 	LSM_HOOK_INIT(inode_file_getattr, selinux_inode_file_getattr),
 	LSM_HOOK_INIT(inode_file_setattr, selinux_inode_file_setattr),
-	LSM_HOOK_INIT(inode_set_acl, selinux_inode_set_acl),
-	LSM_HOOK_INIT(inode_get_acl, selinux_inode_get_acl),
-	LSM_HOOK_INIT(inode_remove_acl, selinux_inode_remove_acl),
+	LSM_HOOK_INIT(path_set_acl, selinux_path_set_acl),
+	LSM_HOOK_INIT(path_get_acl, selinux_path_get_acl),
+	LSM_HOOK_INIT(path_remove_acl, selinux_path_remove_acl),
 	LSM_HOOK_INIT(inode_getsecurity, selinux_inode_getsecurity),
 	LSM_HOOK_INIT(inode_setsecurity, selinux_inode_setsecurity),
 	LSM_HOOK_INIT(inode_listsecurity, selinux_inode_listsecurity),
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index eed06cf7af99..95a048bcf3a6 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1551,7 +1551,7 @@ static int smack_path_removexattr(const struct mnt_idmap *idmap,
 }
 
 /**
- * smack_inode_set_acl - Smack check for setting posix acls
+ * smack_path_set_acl - Smack check for setting posix acls
  * @idmap: idmap of the mnt this request came from
  * @path: the object
  * @acl_name: name of the posix acl
@@ -1560,10 +1560,10 @@ static int smack_path_removexattr(const struct mnt_idmap *idmap,
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_set_acl(const struct mnt_idmap *idmap,
-			       const struct path *path,
-			       const char *acl_name, struct posix_acl *kacl,
-			       struct file *file)
+static int smack_path_set_acl(const struct mnt_idmap *idmap,
+			      const struct path *path,
+			      const char *acl_name, struct posix_acl *kacl,
+			      struct file *file)
 {
 	struct smk_audit_info ad;
 	int rc;
@@ -1577,7 +1577,7 @@ static int smack_inode_set_acl(const struct mnt_idmap *idmap,
 }
 
 /**
- * smack_inode_get_acl - Smack check for getting posix acls
+ * smack_path_get_acl - Smack check for getting posix acls
  * @idmap: idmap of the mnt this request came from
  * @path: the object
  * @acl_name: name of the posix acl
@@ -1585,9 +1585,9 @@ static int smack_inode_set_acl(const struct mnt_idmap *idmap,
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_get_acl(const struct mnt_idmap *idmap,
-			       const struct path *path,
-			       const char *acl_name, struct file *file)
+static int smack_path_get_acl(const struct mnt_idmap *idmap,
+			      const struct path *path,
+			      const char *acl_name, struct file *file)
 {
 	struct smk_audit_info ad;
 	int rc;
@@ -1601,7 +1601,7 @@ static int smack_inode_get_acl(const struct mnt_idmap *idmap,
 }
 
 /**
- * smack_inode_remove_acl - Smack check for getting posix acls
+ * smack_path_remove_acl - Smack check for removing posix acls
  * @idmap: idmap of the mnt this request came from
  * @path: the object
  * @acl_name: name of the posix acl
@@ -1609,9 +1609,9 @@ static int smack_inode_get_acl(const struct mnt_idmap *idmap,
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_remove_acl(const struct mnt_idmap *idmap,
-				  const struct path *path,
-				  const char *acl_name, struct file *file)
+static int smack_path_remove_acl(const struct mnt_idmap *idmap,
+				 const struct path *path,
+				 const char *acl_name, struct file *file)
 {
 	struct smk_audit_info ad;
 	int rc;
@@ -5205,9 +5205,9 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(inode_post_setxattr, smack_inode_post_setxattr),
 	LSM_HOOK_INIT(path_getxattr, smack_path_getxattr),
 	LSM_HOOK_INIT(path_removexattr, smack_path_removexattr),
-	LSM_HOOK_INIT(inode_set_acl, smack_inode_set_acl),
-	LSM_HOOK_INIT(inode_get_acl, smack_inode_get_acl),
-	LSM_HOOK_INIT(inode_remove_acl, smack_inode_remove_acl),
+	LSM_HOOK_INIT(path_set_acl, smack_path_set_acl),
+	LSM_HOOK_INIT(path_get_acl, smack_path_get_acl),
+	LSM_HOOK_INIT(path_remove_acl, smack_path_remove_acl),
 	LSM_HOOK_INIT(inode_getsecurity, smack_inode_getsecurity),
 	LSM_HOOK_INIT(inode_setsecurity, smack_inode_setsecurity),
 	LSM_HOOK_INIT(inode_listsecurity, smack_inode_listsecurity),
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 13/18] LSM: rename the inode fileattr hooks to path_file_* hooks
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (11 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 12/18] LSM: rename the inode posix acl hooks to path_*_acl hooks Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 14/18] LSM: rename inode_setsecctx to path_setsecctx Cai Xinchen
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_file_getattr and inode_file_setattr hooks receive a struct
path, so rename them to path_file_getattr and path_file_setattr to
follow the path_* naming convention used for the other hooks that
take a struct path (path_chmod, path_chown, ...).  Rename the
security_inode_file_getattr() and security_inode_file_setattr()
inline wrappers and the SELinux implementations
accordingly.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/file_attr.c                |  4 ++--
 include/linux/lsm_hook_defs.h |  4 ++--
 include/linux/security.h      | 20 ++++++++++----------
 security/security.c           | 16 ++++++++--------
 security/selinux/hooks.c      | 16 ++++++++--------
 5 files changed, 30 insertions(+), 30 deletions(-)

diff --git a/fs/file_attr.c b/fs/file_attr.c
index 08b4cdbef9ff..093cb05ae3c4 100644
--- a/fs/file_attr.c
+++ b/fs/file_attr.c
@@ -112,7 +112,7 @@ int vfs_fileattr_get(const struct path *path, struct file_kattr *fa,
 {
 	int error;
 
-	error = security_inode_file_getattr(path, fa, file);
+	error = security_path_file_getattr(path, fa, file);
 	if (error)
 		return error;
 
@@ -315,7 +315,7 @@ static int do_vfs_fileattr_set(const struct path *const path,
 		 * user-facing path.
 		 */
 		if (path) {
-			err = security_inode_file_setattr(path, fa, file);
+			err = security_path_file_setattr(path, fa, file);
 			if (err)
 				goto out;
 		}
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 7602a3074375..683c4d7a8024 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -169,9 +169,9 @@ LSM_HOOK(int, 0, path_removexattr, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *name, struct file *file)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry,
 	 const char *name)
-LSM_HOOK(int, 0, inode_file_setattr, const struct path *path,
+LSM_HOOK(int, 0, path_file_setattr, const struct path *path,
 	 struct file_kattr *fa, struct file *file)
-LSM_HOOK(int, 0, inode_file_getattr, const struct path *path,
+LSM_HOOK(int, 0, path_file_getattr, const struct path *path,
 	 struct file_kattr *fa, struct file *file)
 LSM_HOOK(int, 0, path_set_acl, const struct mnt_idmap *idmap,
 	 const struct path *path, const char *acl_name,
diff --git a/include/linux/security.h b/include/linux/security.h
index 305b92c0494c..edba134db8f6 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -464,10 +464,10 @@ int security_path_removexattr(const struct mnt_idmap *idmap,
 			      const struct path *path, const char *name,
 			      struct file *file);
 void security_inode_post_removexattr(struct dentry *dentry, const char *name);
-int security_inode_file_setattr(const struct path *path,
-				struct file_kattr *fa, struct file *file);
-int security_inode_file_getattr(const struct path *path,
-				struct file_kattr *fa, struct file *file);
+int security_path_file_setattr(const struct path *path,
+			       struct file_kattr *fa, struct file *file);
+int security_path_file_getattr(const struct path *path,
+			       struct file_kattr *fa, struct file *file);
 int security_inode_need_killpriv(struct dentry *dentry);
 int security_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry);
 int security_inode_getsecurity(const struct mnt_idmap *idmap,
@@ -1093,16 +1093,16 @@ static inline void security_inode_post_removexattr(struct dentry *dentry,
 						   const char *name)
 { }
 
-static inline int security_inode_file_setattr(const struct path *path,
-					      struct file_kattr *fa,
-					      struct file *file)
+static inline int security_path_file_setattr(const struct path *path,
+					     struct file_kattr *fa,
+					     struct file *file)
 {
 	return 0;
 }
 
-static inline int security_inode_file_getattr(const struct path *path,
-					      struct file_kattr *fa,
-					      struct file *file)
+static inline int security_path_file_getattr(const struct path *path,
+					     struct file_kattr *fa,
+					     struct file *file)
 {
 	return 0;
 }
diff --git a/security/security.c b/security/security.c
index 13778c83fd3a..919763c61994 100644
--- a/security/security.c
+++ b/security/security.c
@@ -2180,7 +2180,7 @@ void security_inode_post_removexattr(struct dentry *dentry, const char *name)
 }
 
 /**
- * security_inode_file_setattr() - check if setting fsxattr is allowed
+ * security_path_file_setattr() - check if setting fsxattr is allowed
  * @path: file to set filesystem extended attributes on (the VFS makes no
  *        guarantees with regard to @path->mnt)
  * @fa: extended attributes to set on the inode
@@ -2191,14 +2191,14 @@ void security_inode_post_removexattr(struct dentry *dentry, const char *name)
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_file_setattr(const struct path *path,
-				struct file_kattr *fa, struct file *file)
+int security_path_file_setattr(const struct path *path,
+			       struct file_kattr *fa, struct file *file)
 {
-	return call_int_hook(inode_file_setattr, path, fa, file);
+	return call_int_hook(path_file_setattr, path, fa, file);
 }
 
 /**
- * security_inode_file_getattr() - check if retrieving fsxattr is allowed
+ * security_path_file_getattr() - check if retrieving fsxattr is allowed
  * @path: file to retrieve filesystem extended attributes from (the VFS makes no
  *        guarantees with regard to @path->mnt)
  * @fa: extended attributes to get
@@ -2209,10 +2209,10 @@ int security_inode_file_setattr(const struct path *path,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_file_getattr(const struct path *path,
-				struct file_kattr *fa, struct file *file)
+int security_path_file_getattr(const struct path *path,
+			       struct file_kattr *fa, struct file *file)
 {
-	return call_int_hook(inode_file_getattr, path, fa, file);
+	return call_int_hook(path_file_getattr, path, fa, file);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e0aaaee537d1..8bafa530806e 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3572,16 +3572,16 @@ static int selinux_path_removexattr(const struct mnt_idmap *idmap,
 	return -EACCES;
 }
 
-static int selinux_inode_file_setattr(const struct path *path,
-				      struct file_kattr *fa,
-				      struct file *file)
+static int selinux_path_file_setattr(const struct path *path,
+				     struct file_kattr *fa,
+				     struct file *file)
 {
 	return dentry_has_perm(current_cred(), path->dentry, FILE__SETATTR);
 }
 
-static int selinux_inode_file_getattr(const struct path *path,
-				      struct file_kattr *fa,
-				      struct file *file)
+static int selinux_path_file_getattr(const struct path *path,
+				     struct file_kattr *fa,
+				     struct file *file)
 {
 	return dentry_has_perm(current_cred(), path->dentry, FILE__GETATTR);
 }
@@ -7743,8 +7743,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_getxattr, selinux_path_getxattr),
 	LSM_HOOK_INIT(path_listxattr, selinux_path_listxattr),
 	LSM_HOOK_INIT(path_removexattr, selinux_path_removexattr),
-	LSM_HOOK_INIT(inode_file_getattr, selinux_inode_file_getattr),
-	LSM_HOOK_INIT(inode_file_setattr, selinux_inode_file_setattr),
+	LSM_HOOK_INIT(path_file_getattr, selinux_path_file_getattr),
+	LSM_HOOK_INIT(path_file_setattr, selinux_path_file_setattr),
 	LSM_HOOK_INIT(path_set_acl, selinux_path_set_acl),
 	LSM_HOOK_INIT(path_get_acl, selinux_path_get_acl),
 	LSM_HOOK_INIT(path_remove_acl, selinux_path_remove_acl),
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 14/18] LSM: rename inode_setsecctx to path_setsecctx
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (12 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 13/18] LSM: rename the inode fileattr hooks to path_file_* hooks Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 15/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

The inode_setsecctx hook receives a struct path, so rename it to
path_setsecctx to follow the path_* naming convention used for the
other hooks that take a struct path (path_chmod, path_chown, ...).
Rename the security_inode_setsecctx() inline wrapper to
security_path_setsecctx() and the SELinux and Smack implementations
accordingly.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/nfsd/vfs.c                 | 2 +-
 include/linux/lsm_hook_defs.h | 2 +-
 include/linux/security.h      | 4 ++--
 security/security.c           | 8 ++++----
 security/selinux/hooks.c      | 4 ++--
 security/smack/smack_lsm.c    | 4 ++--
 6 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 870756842882..3362ac3e1a41 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -628,7 +628,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
 			break;
 	}
 	if (attr->na_seclabel && attr->na_seclabel->len)
-		attr->na_labelerr = security_inode_setsecctx(&path,
+		attr->na_labelerr = security_path_setsecctx(&path,
 			attr->na_seclabel->data, attr->na_seclabel->len);
 	if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && attr->na_dpacl) {
 		if (!S_ISDIR(inode->i_mode))
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 683c4d7a8024..5b25077d17e5 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -334,7 +334,7 @@ LSM_HOOK(int, 0, inode_notifysecctx, struct inode *inode, void *ctx, u32 ctxlen)
  * taking a struct path; the VFS makes no guarantees with regard to
  * @path->mnt.
  */
-LSM_HOOK(int, 0, inode_setsecctx, const struct path *path, void *ctx, u32 ctxlen)
+LSM_HOOK(int, 0, path_setsecctx, const struct path *path, void *ctx, u32 ctxlen)
 LSM_HOOK(int, -EOPNOTSUPP, inode_getsecctx, struct inode *inode,
 	 struct lsm_context *cp)
 
diff --git a/include/linux/security.h b/include/linux/security.h
index edba134db8f6..cf683b89737b 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -597,7 +597,7 @@ int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid);
 void security_release_secctx(struct lsm_context *cp);
 void security_inode_invalidate_secctx(struct inode *inode);
 int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
-int security_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen);
+int security_path_setsecctx(const struct path *path, void *ctx, u32 ctxlen);
 int security_inode_getsecctx(struct inode *inode, struct lsm_context *cp);
 int security_locked_down(enum lockdown_reason what);
 int lsm_fill_user_ctx(struct lsm_ctx __user *uctx, u32 *uctx_len,
@@ -1649,7 +1649,7 @@ static inline int security_inode_notifysecctx(struct inode *inode, void *ctx, u3
 {
 	return -EOPNOTSUPP;
 }
-static inline int security_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
+static inline int security_path_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/security/security.c b/security/security.c
index 919763c61994..fa14bf5dd036 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4131,7 +4131,7 @@ int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen)
 EXPORT_SYMBOL(security_inode_notifysecctx);
 
 /**
- * security_inode_setsecctx() - Change the security label of an inode
+ * security_path_setsecctx() - Change the security label of an inode
  * @path: inode (the VFS makes no guarantees with regard to @path->mnt)
  * @ctx: secctx
  * @ctxlen: length of secctx
@@ -4146,11 +4146,11 @@ EXPORT_SYMBOL(security_inode_notifysecctx);
  *
  * Return: Returns 0 on success, error on failure.
  */
-int security_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
+int security_path_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
-	return call_int_hook(inode_setsecctx, path, ctx, ctxlen);
+	return call_int_hook(path_setsecctx, path, ctx, ctxlen);
 }
-EXPORT_SYMBOL(security_inode_setsecctx);
+EXPORT_SYMBOL(security_path_setsecctx);
 
 /**
  * security_inode_getsecctx() - Get the security label of an inode
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 8bafa530806e..cd04b09a8a7d 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7157,7 +7157,7 @@ static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen
 /*
  *	called with inode->i_mutex locked
  */
-static int selinux_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
+static int selinux_path_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
 	return __vfs_setxattr_locked(&nop_mnt_idmap, path, XATTR_NAME_SELINUX,
 				     ctx, ctxlen, 0, NULL, NULL);
@@ -7831,7 +7831,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(release_secctx, selinux_release_secctx),
 	LSM_HOOK_INIT(inode_invalidate_secctx, selinux_inode_invalidate_secctx),
 	LSM_HOOK_INIT(inode_notifysecctx, selinux_inode_notifysecctx),
-	LSM_HOOK_INIT(inode_setsecctx, selinux_inode_setsecctx),
+	LSM_HOOK_INIT(path_setsecctx, selinux_path_setsecctx),
 
 	LSM_HOOK_INIT(unix_stream_connect, selinux_socket_unix_stream_connect),
 	LSM_HOOK_INIT(unix_may_send, selinux_socket_unix_may_send),
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 95a048bcf3a6..b3475253e879 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -4998,7 +4998,7 @@ static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen)
 				       ctxlen, 0);
 }
 
-static int smack_inode_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
+static int smack_path_setsecctx(const struct path *path, void *ctx, u32 ctxlen)
 {
 	return __vfs_setxattr_locked(&nop_mnt_idmap, path, XATTR_NAME_SMACK,
 				     ctx, ctxlen, 0, NULL, NULL);
@@ -5323,7 +5323,7 @@ static struct security_hook_list smack_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(lsmprop_to_secctx, smack_lsmprop_to_secctx),
 	LSM_HOOK_INIT(secctx_to_secid, smack_secctx_to_secid),
 	LSM_HOOK_INIT(inode_notifysecctx, smack_inode_notifysecctx),
-	LSM_HOOK_INIT(inode_setsecctx, smack_inode_setsecctx),
+	LSM_HOOK_INIT(path_setsecctx, smack_path_setsecctx),
 	LSM_HOOK_INIT(inode_getsecctx, smack_inode_getsecctx),
 	LSM_HOOK_INIT(inode_copy_up, smack_inode_copy_up),
 	LSM_HOOK_INIT(inode_copy_up_xattr, smack_inode_copy_up_xattr),
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 15/18] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (13 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 14/18] LSM: rename inode_setsecctx to path_setsecctx Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 14:49   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 16/18] selftests/landlock: Add tests for metadata " Cai Xinchen
                   ` (2 subsequent siblings)
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

Add two new filesystem access rights to control file metadata access:

- LANDLOCK_ACCESS_FS_READ_METADATA: Read file or directory metadata
  (e.g. inode attributes, extended attributes and POSIX ACLs) through
  stat(2), fstat(2), lstat(2), newfstatat(2), getxattr(2) and
  friends, listxattr(2) and friends, and the POSIX ACL read
  operations.
- LANDLOCK_ACCESS_FS_WRITE_METADATA: Change file or directory
  metadata through chmod(2) and friends, chown(2) and friends,
  utimensat(2) and friends, setxattr(2) and friends, removexattr(2)
  and friends, and the POSIX ACL set and remove operations.

This follows the coarse-grained grouping approach discussed in the
mailing list [1], where file metadata operations are grouped by
security relevance rather than by individual syscall.

Implement the two rights by hooking the path_getattr,
path_file_getattr, path_setattr, path_file_setattr, path_setxattr,
path_getxattr, path_listxattr, path_removexattr, path_set_acl,
path_get_acl and path_remove_acl LSM hooks, which now receive a
struct path thanks to the preceding VFS and LSM refactoring.  All of
them receive the struct file through which the operation is
requested, when there is one.  The following system
calls are now controlled:

- stat(2), fstat(2), lstat(2), newfstatat(2), getxattr(2) and
  friends, listxattr(2) and friends, POSIX ACL reads, and the
  chattr(1)/xfs_io style reads: ioctl(FS_IOC_GETFLAGS),
  ioctl(FS_IOC_FSGETXATTR) and file_getattr(2) via path_getattr,
  path_getxattr, path_listxattr, path_get_acl and path_file_getattr
  (READ_METADATA)
- chmod(2), fchmod(2), fchmodat(2), fchmodat2(2), chown(2), fchown(2),
  lchown(2), fchownat(2), chgrp(2), utimensat(2) and friends,
  utime(2), setxattr(2) and friends, removexattr(2) and friends,
  POSIX ACL set and remove, and the chattr(1)/xfs_io style writes:
  ioctl(FS_IOC_SETFLAGS), ioctl(FS_IOC_FSSETXATTR) and
  file_setattr(2) via path_setattr, path_file_setattr, path_setxattr,
  path_removexattr, path_set_acl and path_remove_acl
  (WRITE_METADATA)

For operations on already opened files (e.g. fchmod(2), fchown(2),
utimensat(2) with an empty path, fgetxattr(2), fstat(2), or the
FS_IOC_* ioctls), it is the access rights at the time of open(2)
which decide whether the operation is permitted, not the rights of
the current thread, as for LANDLOCK_ACCESS_FS_TRUNCATE.  To that
end, READ_METADATA and WRITE_METADATA are included in
_LANDLOCK_ACCESS_FS_OPTIONAL, deny_masks_t is widened from u8 to u16
to keep tracking one nibble per optional access right for the audit
logs, and both rights are recorded in the allowed_access of opened
files in hook_file_open().  The metadata hooks consume this record
when they are called with a file, and fall back to the current
domain's rights for path-based operations.  The hook_file_truncate()
check is factored out into the shared check_access_file() helper for
that purpose.

As for truncate, file descriptors therefore carry the rights of the
process which opened them: a file descriptor opened before sandboxing
keeps working, whereas a file descriptor opened with a restricted set
of rights keeps denying metadata accesses even when passed to another
process.  This also closes the metadata leak through file descriptors
opened without READ_METADATA, e.g. via fstat(2) or the
FS_IOC_GETFLAGS ioctl.  O_PATH file descriptors are an exception:
they are not opened through the open permission checks (cf.
do_dentry_open()), so they do not carry access rights stashed at open
time; metadata accesses through them are checked against the current
domain, like path-based accesses.

hook_path_setattr only restricts explicit metadata changes, i.e. it
checks WRITE_METADATA only when the ia_valid mask contains
ATTR_MODE, ATTR_UID, ATTR_GID, ATTR_TIMES_SET or ATTR_TOUCH.
Metadata changes that the kernel performs implicitly, such as
timestamp updates on write(2) or size changes on truncate(2), are
therefore not restricted.  The setuid/setgid bit clearing that the
kernel performs after write(2) or truncate(2) comes through
notify_change() with ATTR_FORCE set, and the ATTR_MODE that
do_notify_change() synthesizes from ATTR_KILL_S[UG]ID is therefore
ignored; an explicit chown(2), including chown(2) with (-1, -1) on
a privileged file, does not set ATTR_FORCE and is still checked,
matching the behavior of the SELinux path_setattr hook.  A chown(2)
that changes nothing (e.g. with (-1, -1) on a file without privilege
bits) sets no checkable attribute and is not restricted either.

Stacking filesystems forward fileattr operations to underlying inodes
without calling the LSM hooks again (cf. the patch passing struct path
and struct file to the inode fileattr hooks), so a fileattr operation
on an overlayfs file is checked once on the user-facing merged path
and is not spuriously denied because of the kernel-side creator
credentials used for the forward.

Kernel-internal accesses performed with override_creds() (e.g.
overlayfs and cachefiles) are not affected because Landlock domains
are attached to credentials, and kernel threads without a Landlock
domain (e.g. nfsd and ksmbd) are not restricted either.

Both new rights are added to ACCESS_FILE as they apply to both files
and directories.  Add the names of the new rights to
_LANDLOCK_ACCESS_FS_NAMES in include/linux/landlock.h, which is
shared by audit records and trace events, so that denied accesses can
be reported with a human-readable name and the static assertion on
fs_access_strings in security/landlock/audit.c stays valid.

Remove stat(2), chmod(2), chown(2), setxattr(2) and utime(2) from the
list of file-related actions that cannot be restricted yet, and
increment the Landlock ABI version from 12 to 13.

[1]
https://lore.kernel.org/all/abc960a1-e66e-792e-6869-cfd201c29dbe@digikod.net/

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 include/linux/landlock.h      |   4 +-
 include/uapi/linux/landlock.h |  25 +++-
 security/landlock/access.h    |  13 +-
 security/landlock/fs.c        | 232 ++++++++++++++++++++++++++++++----
 security/landlock/limits.h    |   2 +-
 security/landlock/syscalls.c  |   2 +-
 6 files changed, 241 insertions(+), 37 deletions(-)

diff --git a/include/linux/landlock.h b/include/linux/landlock.h
index 0ee4880988ba..ee1234fbc9f0 100644
--- a/include/linux/landlock.h
+++ b/include/linux/landlock.h
@@ -41,7 +41,9 @@
 	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REFER, "refer"), \
 	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_TRUNCATE, "truncate"), \
 	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_IOCTL_DEV, "ioctl_dev"), \
-	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_RESOLVE_UNIX, "resolve_unix")
+	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_RESOLVE_UNIX, "resolve_unix"), \
+	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_READ_METADATA, "read_metadata"), \
+	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_WRITE_METADATA, "write_metadata")
 
 #define _LANDLOCK_ACCESS_NET_NAMES \
 	_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_TCP, "bind_tcp"), \
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 24510e5d8479..36dfb499e48e 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -429,6 +429,25 @@ struct landlock_capability_attr {
  *
  *   The rationale for this design is described in
  *   :ref:`Documentation/security/landlock.rst <scoped-flags-interaction>`.
+ * - %LANDLOCK_ACCESS_FS_READ_METADATA: Read file or directory metadata (e.g.
+ *   inode attributes, extended attributes, and POSIX ACLs) through
+ *   :manpage:`stat(2)`, :manpage:`fstat(2)`, :manpage:`lstat(2)`,
+ *   :manpage:`newfstatat(2)`, :manpage:`getxattr(2)`, and
+ *   :manpage:`listxattr(2)`.
+ *
+ *   This access right is available since the thirteenth version of the
+ *   Landlock ABI.
+ * - %LANDLOCK_ACCESS_FS_WRITE_METADATA: Change file or directory metadata
+ *   (e.g. inode attributes, extended attributes, and POSIX ACLs) through
+ *   :manpage:`chmod(2)`, :manpage:`chown(2)`, :manpage:`utimensat(2)`,
+ *   :manpage:`setxattr(2)`, and :manpage:`removexattr(2)`, as well as related
+ *   system calls.  Implicit metadata changes performed by the kernel (e.g.
+ *   timestamp updates with :manpage:`write(2)`, or set-user-ID and
+ *   set-group-ID bit clearing with :manpage:`write(2)` or
+ *   :manpage:`truncate(2)`) are not restricted by this access right.
+ *
+ *   This access right is available since the thirteenth version of the
+ *   Landlock ABI.
  *
  * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used
  * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
@@ -493,9 +512,7 @@ struct landlock_capability_attr {
  *
  *   It is currently not possible to restrict some file-related actions
  *   accessible through these syscall families: :manpage:`chdir(2)`,
- *   :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`,
- *   :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`,
- *   :manpage:`fcntl(2)`, :manpage:`access(2)`.
+ *   :manpage:`flock(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`.
  *   Future Landlock evolutions will enable to restrict them.
  */
 /* clang-format off */
@@ -516,6 +533,8 @@ struct landlock_capability_attr {
 #define LANDLOCK_ACCESS_FS_TRUNCATE			(1ULL << 14)
 #define LANDLOCK_ACCESS_FS_IOCTL_DEV			(1ULL << 15)
 #define LANDLOCK_ACCESS_FS_RESOLVE_UNIX			(1ULL << 16)
+#define LANDLOCK_ACCESS_FS_READ_METADATA		(1ULL << 17)
+#define LANDLOCK_ACCESS_FS_WRITE_METADATA		(1ULL << 18)
 /* clang-format on */
 
 /**
diff --git a/security/landlock/access.h b/security/landlock/access.h
index f9eaa2f53686..a719760cdeff 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -31,7 +31,9 @@
 /* clang-format off */
 #define _LANDLOCK_ACCESS_FS_OPTIONAL ( \
 	LANDLOCK_ACCESS_FS_TRUNCATE | \
-	LANDLOCK_ACCESS_FS_IOCTL_DEV)
+	LANDLOCK_ACCESS_FS_IOCTL_DEV | \
+	LANDLOCK_ACCESS_FS_READ_METADATA | \
+	LANDLOCK_ACCESS_FS_WRITE_METADATA)
 /* clang-format on */
 
 typedef u32 access_mask_t;
@@ -156,11 +158,12 @@ struct layer_masks {
  * required by update_request().
  *
  * Each nibble represents the layer index of the newest layer which denied a
- * certain access right.  For file system access rights, the upper four bits are
- * the index of the layer which denies LANDLOCK_ACCESS_FS_IOCTL_DEV and the
- * lower nibble represents LANDLOCK_ACCESS_FS_TRUNCATE.
+ * certain access right.  For file system access rights, the nibbles from the
+ * least to the most significant one represent the layers which deny
+ * LANDLOCK_ACCESS_FS_TRUNCATE, LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ * LANDLOCK_ACCESS_FS_READ_METADATA and LANDLOCK_ACCESS_FS_WRITE_METADATA.
  */
-typedef u8 deny_masks_t;
+typedef u16 deny_masks_t;
 
 /*
  * Makes sure all optional access rights can be tied to a layer index (cf.
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 2ab67bcad198..73c6353ecbf8 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -318,6 +318,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
 	LANDLOCK_ACCESS_FS_EXECUTE | \
 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
 	LANDLOCK_ACCESS_FS_READ_FILE | \
+	LANDLOCK_ACCESS_FS_READ_METADATA | \
+	LANDLOCK_ACCESS_FS_WRITE_METADATA | \
 	LANDLOCK_ACCESS_FS_TRUNCATE | \
 	LANDLOCK_ACCESS_FS_IOCTL_DEV | \
 	LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
@@ -1676,6 +1678,192 @@ static int hook_path_truncate(const struct path *const path)
 	return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
 }
 
+static int check_access_file(const struct file *const file,
+			     const access_mask_t access)
+{
+	/*
+	 * O_PATH file descriptors do not go through the open permission
+	 * checks (cf. do_dentry_open()) and therefore do not carry the
+	 * access rights stashed at open time: they are checked against the
+	 * current domain, like path-based accesses.
+	 */
+	if (unlikely(file->f_mode & FMODE_PATH))
+		return current_check_access_path(&file->f_path, access);
+
+	/*
+	 * Allows accesses if the requested access right was available at the
+	 * time of opening the file, to get a consistent access check as for
+	 * read, write and execute operations.
+	 *
+	 * Note: For checks done based on the file's Landlock allowed access,
+	 * we enforce them independently of whether the current thread is in
+	 * a Landlock domain, so that open files passed between independent
+	 * processes retain their behaviour.
+	 */
+	if (landlock_file(file)->allowed_access & access)
+		return 0;
+
+	landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) {
+		.type = LANDLOCK_REQUEST_FS_ACCESS,
+		.audit = {
+			.type = LSM_AUDIT_DATA_FILE,
+			.u.file = file,
+		},
+		.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
+		.access = access,
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+		.deny_masks = landlock_file(file)->deny_masks,
+		.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+	});
+	return -EACCES;
+}
+
+static int check_access_path_or_file(const struct path *const path,
+				     const struct file *const file,
+				     const access_mask_t access)
+{
+	if (file)
+		return check_access_file(file, access);
+
+	return current_check_access_path(path, access);
+}
+
+static int hook_path_getattr(const struct path *const path,
+			     struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_path_file_getattr(const struct path *const path,
+				  struct file_kattr *const fa,
+				  struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_path_setattr(const struct mnt_idmap *const idmap,
+			     const struct path *const path,
+			     struct iattr *const attr,
+			     struct file *const file)
+{
+	unsigned int ia_valid = attr->ia_valid;
+
+	/*
+	 * Only explicit metadata changes are checked: implicit
+	 * timestamp updates never go through notify_change(), and size
+	 * changes (ATTR_SIZE) are governed by the truncate hooks
+	 * instead.  See the "File metadata" section in the user space
+	 * documentation for the full semantics.
+	 *
+	 * The setuid/setgid bit clearing that the kernel performs
+	 * after write(2) or truncate(2) is an implicit change as
+	 * well: it comes through __remove_privs() and do_truncate()
+	 * with ATTR_FORCE set, and do_notify_change() then adds a
+	 * synthesized ATTR_MODE (with these bits removed) before
+	 * calling this hook.  As do_notify_change() makes it a bug for
+	 * callers to pass ATTR_MODE together with a kill flag, an
+	 * ATTR_MODE seen along with ATTR_FORCE is always
+	 * kernel-synthesized and must not be checked.  An explicit
+	 * chown(2) does not set ATTR_FORCE, so the same clearing
+	 * requested through chown(2), including chown(2) with
+	 * (-1, -1) on a privileged file, is still checked through the
+	 * synthesized ATTR_MODE, matching the SELinux behavior.  A
+	 * chown(2) that changes nothing (e.g. with (-1, -1) on a file
+	 * without privilege bits) sets neither a changed attribute
+	 * nor a synthesized ATTR_MODE, and is not checked.
+	 */
+	if (ia_valid & ATTR_FORCE)
+		ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_MODE);
+
+	if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
+			  ATTR_TIMES_SET | ATTR_TOUCH)))
+		return 0;
+
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_path_file_setattr(const struct path *const path,
+				  struct file_kattr *const fa,
+				  struct file *const file)
+{
+	/*
+	 * The chattr(2) style flags (e.g. FS_IMMUTABLE_FL) and fsxattr
+	 * fields are metadata: writing them requires WRITE_METADATA.  The
+	 * user-facing path is checked here; stacking filesystems skip this
+	 * hook on their internal forwards because they were already
+	 * checked on the user-facing path.
+	 */
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_path_setxattr(const struct mnt_idmap *const idmap,
+			       const struct path *const path,
+			       const char *const name,
+			       const void *const value, const size_t size,
+			       const int flags,
+			       struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_path_getxattr(const struct path *const path,
+			      const char *const name,
+			      struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_path_listxattr(const struct path *const path,
+			       struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_path_removexattr(const struct mnt_idmap *const idmap,
+				  const struct path *const path,
+				  const char *const name,
+				  struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_path_set_acl(const struct mnt_idmap *const idmap,
+			      const struct path *const path,
+			      const char *const acl_name,
+			      struct posix_acl *const kacl,
+			      struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_path_get_acl(const struct mnt_idmap *const idmap,
+			      const struct path *const path,
+			      const char *const acl_name,
+			      struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_path_remove_acl(const struct mnt_idmap *const idmap,
+				const struct path *const path,
+				const char *const acl_name,
+				struct file *const file)
+{
+	return check_access_path_or_file(path, file,
+					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
 /**
  * unmask_scoped_access - Remove access right bits in @masks in all layers
  *                        where @client and @server have the same domain
@@ -1876,7 +2064,9 @@ static int hook_file_open(struct file *const file)
 	 * We look up more access than what we immediately need for open(), so
 	 * that we can later authorize operations on opened files.
 	 */
-	optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
+	optional_access = LANDLOCK_ACCESS_FS_TRUNCATE |
+			  LANDLOCK_ACCESS_FS_READ_METADATA |
+			  LANDLOCK_ACCESS_FS_WRITE_METADATA;
 	if (is_device(file))
 		optional_access |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
 
@@ -1928,32 +2118,11 @@ static int hook_file_open(struct file *const file)
 static int hook_file_truncate(struct file *const file)
 {
 	/*
-	 * Allows truncation if the truncate right was available at the time of
-	 * opening the file, to get a consistent access check as for read, write
-	 * and execute operations.
-	 *
-	 * Note: For checks done based on the file's Landlock allowed access, we
-	 * enforce them independently of whether the current thread is in a
-	 * Landlock domain, so that open files passed between independent
-	 * processes retain their behaviour.
+	 * Allows truncation if the truncate right was available at the time
+	 * of opening the file, to get a consistent access check as for read,
+	 * write and execute operations.
 	 */
-	if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE)
-		return 0;
-
-	landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) {
-		.type = LANDLOCK_REQUEST_FS_ACCESS,
-		.audit = {
-			.type = LSM_AUDIT_DATA_FILE,
-			.u.file = file,
-		},
-		.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
-		.access = LANDLOCK_ACCESS_FS_TRUNCATE,
-#ifdef CONFIG_SECURITY_LANDLOCK_LOG
-		.deny_masks = landlock_file(file)->deny_masks,
-		.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
-#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
-	});
-	return -EACCES;
+	return check_access_file(file, LANDLOCK_ACCESS_FS_TRUNCATE);
 }
 
 static int hook_file_ioctl_common(const struct file *const file,
@@ -2100,6 +2269,17 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(path_unlink, hook_path_unlink),
 	LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
 	LSM_HOOK_INIT(path_truncate, hook_path_truncate),
+	LSM_HOOK_INIT(path_getattr, hook_path_getattr),
+	LSM_HOOK_INIT(path_file_getattr, hook_path_file_getattr),
+	LSM_HOOK_INIT(path_setattr, hook_path_setattr),
+	LSM_HOOK_INIT(path_file_setattr, hook_path_file_setattr),
+	LSM_HOOK_INIT(path_setxattr, hook_path_setxattr),
+	LSM_HOOK_INIT(path_getxattr, hook_path_getxattr),
+	LSM_HOOK_INIT(path_listxattr, hook_path_listxattr),
+	LSM_HOOK_INIT(path_removexattr, hook_path_removexattr),
+	LSM_HOOK_INIT(path_set_acl, hook_path_set_acl),
+	LSM_HOOK_INIT(path_get_acl, hook_path_get_acl),
+	LSM_HOOK_INIT(path_remove_acl, hook_path_remove_acl),
 	LSM_HOOK_INIT(unix_find, hook_unix_find),
 
 	LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index ddd300e10689..ed606ee3102f 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -21,7 +21,7 @@
 #define LANDLOCK_MAX_NUM_LAYERS		16
 #define LANDLOCK_MAX_NUM_RULES		U32_MAX
 
-#define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_RESOLVE_UNIX
+#define LANDLOCK_LAST_ACCESS_FS		LANDLOCK_ACCESS_FS_WRITE_METADATA
 #define LANDLOCK_MASK_ACCESS_FS		((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
 #define LANDLOCK_NUM_ACCESS_FS		__const_hweight64(LANDLOCK_MASK_ACCESS_FS)
 
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index fb5af318b69b..11d3a2abcec1 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -191,7 +191,7 @@ static const struct file_operations ruleset_fops = {
  * If the change involves a fix that requires userspace awareness, also update
  * the errata documentation in Documentation/userspace-api/landlock.rst .
  */
-const int landlock_abi_version = 12;
+const int landlock_abi_version = 13;
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 16/18] selftests/landlock: Add tests for metadata access rights
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (14 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 15/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 15:11   ` sashiko-bot
  2026-10-27 12:11 ` [PATCH v3 -next 17/18] samples/landlock: Add metadata rights to sandboxer Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 18/18] Documentation: Update landlock doc for metadata rights Cai Xinchen
  17 siblings, 1 reply; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

Add layout1 tests for the LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights:

- unhandled_metadata: metadata access is allowed when the new rights
  are not handled by the ruleset;
- write_metadata: chmod(2), fchmod(2), chown(2), fchown(2), lchown(2),
  utimensat(2) (both with explicit times and with UTIME_NOW),
  setxattr(2), and removexattr(2) are denied with EACCES without
  WRITE_METADATA, and allowed with it.  Writing to or truncating a
  set-user-ID file clears the set-user-ID bit implicitly, which
  stays allowed without WRITE_METADATA, while an explicit chown(2)
  with (-1, -1) requesting the same clearing is denied without
  WRITE_METADATA and allowed with it;
- read_metadata: stat(2), fstat(2), getxattr(2), and listxattr(2) are
  denied with EACCES without READ_METADATA, and allowed with it.

Add layout1 and layout2_overlay tests for the fileattr interfaces
controlled by the same rights:

- fileattr: ioctl(FS_IOC_GETFLAGS), ioctl(FS_IOC_SETFLAGS),
  file_getattr(2) and file_setattr(2) (both with a path and with
  AT_EMPTY_PATH) are denied with EACCES without the corresponding
  right, and allowed with it;
- fileattr_write_only: with only WRITE_METADATA handled, metadata
  writes (ioctl(FS_IOC_SETFLAGS)) and reads (ioctl(FS_IOC_GETFLAGS))
  are both allowed, as READ_METADATA is not handled; when
  READ_METADATA is handled as well but not granted, metadata reads
  through a file descriptor opened from then on are denied with
  EACCES while writes are still allowed, because the kernel-side
  read of the old flags performed when setting new ones does not go
  through the READ_METADATA check.  Reading metadata through a file
  descriptor opened while READ_METADATA was not handled yet is still
  allowed;
- layout2_overlay.fileattr: the same fileattr checks on files
  accessed through an overlayfs mount created by the (unrestricted)
  test process;
- overlay_mount_denied: a restricted process cannot bypass the
  metadata rights by mounting a new overlayfs with mount(2), even
  with CAP_SYS_ADMIN: landlock denies mount(2) with EPERM for any
  landlocked process.

The file-based metadata checks follow the
LANDLOCK_ACCESS_FS_TRUNCATE mechanic: it is the access rights at the
time of open(2) which decide whether an operation on an already
opened file is permitted.  Adapt the fchown(2) denial case of the
write_metadata test and the second phase of the fileattr_write_only
test to that semantic by (re)opening the file descriptors after the
ruleset is enforced, and add layout1 tests for the open-time
semantic itself:

- metadata_open_time: a file descriptor opened before the ruleset is
  enforced keeps allowing fchmod(2), fchown(2), futimens(2),
  fstat(2), fgetxattr(2), fsetxattr(2), ioctl(FS_IOC_GETFLAGS) and
  file_getattr(2)/file_setattr(2) with AT_EMPTY_PATH, whereas the
  same operations through a file descriptor opened while the
  ruleset is enforced are denied with EACCES, and the same
  operations through paths are denied as well.  Data accesses
  (write(2)) still work through the restricted file descriptor, and
  an O_PATH file descriptor is checked against the current domain,
  like path-based accesses;
- metadata_transfer: a file descriptor opened by a landlocked
  process without the metadata access rights and passed to a
  non-landlocked process through a unix socket still denies metadata
  accesses, while data accesses work: the file descriptor carries
  the rights of the process which opened it, whatever the rights of
  the process using it.

The fallback syscall numbers for file_getattr(2) and file_setattr(2)
used when building against older headers are not universal: alpha
assigns 578 and 579 to them instead of the generic 468 and 469.

Rule enforcement is checked per file and per directory.

Update the ACCESS_FILE and ACCESS_LAST definitions to include the new
rights.

The new rights are appended to _LANDLOCK_ACCESS_FS_NAMES, which
extends the absolute access mask expanded by
landlock_append_fs_rule(); update the expected access_rights suffix in
the trace_fs.add_rule_path_beneath_escaped_path_overflow test
accordingly.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 tools/testing/selftests/landlock/base_test.c  |   2 +-
 tools/testing/selftests/landlock/fs_test.c    | 921 +++++++++++++++++-
 .../selftests/landlock/trace_fs_test.c        |   3 +-
 3 files changed, 923 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 89c0d96607de..14078ad203d8 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(12, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(13, landlock_create_ruleset(NULL, 0,
 					      LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index f725882fa4a7..1e819ba952ac 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -12,6 +12,7 @@
 #include <fcntl.h>
 #include <libgen.h>
 #include <linux/fiemap.h>
+#include <linux/fs.h>
 #include <linux/landlock.h>
 #include <linux/magic.h>
 #include <sched.h>
@@ -26,8 +27,10 @@
 #include <sys/sendfile.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/sysmacros.h>
 #include <sys/un.h>
+#include <sys/xattr.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
@@ -571,11 +574,13 @@ TEST_F_FORK(layout1, inval)
 	LANDLOCK_ACCESS_FS_EXECUTE | \
 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
 	LANDLOCK_ACCESS_FS_READ_FILE | \
+	LANDLOCK_ACCESS_FS_READ_METADATA | \
+	LANDLOCK_ACCESS_FS_WRITE_METADATA | \
 	LANDLOCK_ACCESS_FS_TRUNCATE | \
 	LANDLOCK_ACCESS_FS_IOCTL_DEV | \
 	LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
 
-#define ACCESS_LAST LANDLOCK_ACCESS_FS_RESOLVE_UNIX
+#define ACCESS_LAST LANDLOCK_ACCESS_FS_WRITE_METADATA
 
 #define ACCESS_ALL ( \
 	ACCESS_FILE | \
@@ -10916,4 +10921,918 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)
 	free(buf);
 }
 
+/* Invokes chmod(2) and returns its errno or 0. */
+static int test_chmod(const char *const path, mode_t mode)
+{
+	if (chmod(path, mode) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes fchmod(2) and returns its errno or 0. */
+static int test_fchmod(int fd, mode_t mode)
+{
+	if (fchmod(fd, mode) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes chown(2) and returns its errno or 0. */
+static int test_chown(const char *const path, uid_t uid, gid_t gid)
+{
+	if (chown(path, uid, gid) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes fchown(2) and returns its errno or 0. */
+static int test_fchown(int fd, uid_t uid, gid_t gid)
+{
+	if (fchown(fd, uid, gid) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes lchown(2) and returns its errno or 0. */
+static int test_lchown(const char *const path, uid_t uid, gid_t gid)
+{
+	if (lchown(path, uid, gid) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes utimensat(2) with explicit times and returns its errno or 0. */
+static int test_utimensat(const char *const path)
+{
+	const struct timespec times[] = {
+		{
+			.tv_sec = 0,
+			.tv_nsec = UTIME_OMIT,
+		},
+		{
+			.tv_sec = 1,
+			.tv_nsec = 0,
+		},
+	};
+
+	if (utimensat(AT_FDCWD, path, times, 0) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes utimensat(2) with UTIME_NOW and returns its errno or 0. */
+static int test_utimensat_now(const char *const path)
+{
+	if (utimensat(AT_FDCWD, path, NULL, 0) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes futimens(2) and returns its errno or 0. */
+static int test_futimensat(int fd)
+{
+	const struct timespec times[] = {
+		{
+			.tv_sec = 0,
+			.tv_nsec = UTIME_OMIT,
+		},
+		{
+			.tv_sec = 1,
+			.tv_nsec = 0,
+		},
+	};
+
+	if (futimens(fd, times) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes setxattr(2) and returns its errno or 0. */
+static int test_setxattr(const char *const path, const char *const name,
+			 const void *const value, const size_t size)
+{
+	if (setxattr(path, name, value, size, 0) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes getxattr(2) and returns its errno or 0. */
+static int test_getxattr(const char *const path, const char *const name,
+			 void *const value, const size_t size)
+{
+	if (getxattr(path, name, value, size) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes listxattr(2) and returns its errno or 0. */
+static int test_listxattr(const char *const path, void *const list,
+			  const size_t size)
+{
+	if (listxattr(path, list, size) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes removexattr(2) and returns its errno or 0. */
+static int test_removexattr(const char *const path, const char *const name)
+{
+	if (removexattr(path, name) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes fsetxattr(2) and returns its errno or 0. */
+static int test_fsetxattr(int fd, const char *const name,
+			  const void *const value, const size_t size)
+{
+	if (fsetxattr(fd, name, value, size, 0) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes fgetxattr(2) and returns its errno or 0. */
+static int test_fgetxattr(int fd, const char *const name, void *const value,
+			  const size_t size)
+{
+	if (fgetxattr(fd, name, value, size) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes stat(2) and returns its errno or 0. */
+static int test_stat(const char *const path, struct stat *statbuf)
+{
+	if (stat(path, statbuf) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes fstat(2) and returns its errno or 0. */
+static int test_fstat(int fd, struct stat *statbuf)
+{
+	if (fstat(fd, statbuf) < 0)
+		return errno;
+	return 0;
+}
+
+TEST_F_FORK(layout1, unhandled_metadata)
+{
+	int file_fd;
+	int ruleset_fd;
+	const char *const file1 = file1_s1d1;
+	const char *const file2 = file2_s1d1;
+	const char *const dir1 = dir_s1d1;
+	struct stat statbuf;
+	const struct rule rules[] = {
+		{
+			.path = file1,
+			.access = LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{
+			.path = file2,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{
+			.path = dir1,
+			.access = ACCESS_RW,
+		},
+		{},
+	};
+
+	/*
+	 * READ_METADATA and WRITE_METADATA are not handled, so metadata
+	 * access should be allowed.
+	 */
+	ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
+	ASSERT_LE(0, ruleset_fd);
+	file_fd = open(file1, O_WRONLY | O_CLOEXEC);
+	ASSERT_LE(0, file_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	EXPECT_EQ(0, test_chmod(file1, 0644));
+	EXPECT_EQ(0, test_fchmod(file_fd, 0644));
+	EXPECT_EQ(0, test_chmod(file2, 0400));
+	EXPECT_EQ(0, test_chmod(dir1, 0700));
+
+	EXPECT_EQ(0, test_chown(file1, -1, 0));
+	EXPECT_EQ(0, test_fchown(file_fd, -1, 0));
+	EXPECT_EQ(0, test_lchown(file1, -1, 0));
+
+	EXPECT_EQ(0, test_utimensat(file1));
+	EXPECT_EQ(0, test_utimensat_now(file1));
+
+	EXPECT_EQ(0, test_setxattr(file1, "user.test", "a", 1));
+	EXPECT_EQ(0, test_getxattr(file1, "user.test", NULL, 0));
+	EXPECT_EQ(0, test_listxattr(file1, NULL, 0));
+	EXPECT_EQ(0, test_removexattr(file1, "user.test"));
+
+	EXPECT_EQ(0, test_stat(file1, &statbuf));
+	EXPECT_EQ(0, test_fstat(file_fd, &statbuf));
+	EXPECT_EQ(0, test_stat(dir1, &statbuf));
+
+	ASSERT_EQ(0, close(file_fd));
+}
+
+TEST_F_FORK(layout1, write_metadata)
+{
+	int file_fd, file2_fd, trunc_fd, chown_fd;
+	int ruleset_fd;
+	const char *const file1 = file1_s1d1;
+	const char *const file2 = file2_s1d1;
+	const char *const trunc_file = file1_s1d2;
+	const char *const chown_file = file2_s1d2;
+	const char *const dir1 = dir_s1d1;
+	struct stat statbuf;
+	const struct rule rules[] = {
+		{
+			.path = file1,
+			.access = LANDLOCK_ACCESS_FS_WRITE_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		},
+		{
+			.path = file2,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{
+			.path = chown_file,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{
+			.path = dir1,
+			.access = ACCESS_RW,
+		},
+		{},
+	};
+
+	ruleset_fd = create_ruleset(_metadata, ACCESS_RW |
+				    LANDLOCK_ACCESS_FS_WRITE_METADATA, rules);
+	ASSERT_LE(0, ruleset_fd);
+	file_fd = open(file1, O_WRONLY | O_CLOEXEC);
+	ASSERT_LE(0, file_fd);
+	file2_fd = open(file2, O_RDWR | O_CLOEXEC);
+	ASSERT_LE(0, file2_fd);
+	trunc_fd = open(trunc_file, O_RDWR | O_CLOEXEC);
+	ASSERT_LE(0, trunc_fd);
+	chown_fd = open(chown_file, O_RDWR | O_CLOEXEC);
+	ASSERT_LE(0, chown_fd);
+
+	/*
+	 * Arm the setuid bit for the setuid-clearing tests below:
+	 * writing or truncating a setuid file clears that bit
+	 * implicitly, which must not require WRITE_METADATA, whereas
+	 * an explicit chown(2) with (-1, -1) requests the same
+	 * clearing and must require it.  The clearing happens when
+	 * the caller has no CAP_FSETID, which is the case here
+	 * because tests run without capabilities (see disable_caps()
+	 * in prepare_layout()).
+	 */
+	ASSERT_EQ(0, fchmod(file_fd, 04666));
+	ASSERT_EQ(0, fchmod(file2_fd, 04666));
+	ASSERT_EQ(0, fchmod(trunc_fd, 04666));
+	ASSERT_EQ(0, fchmod(chown_fd, 04666));
+	/* Closes the file descriptor opened before sandboxing. */
+	ASSERT_EQ(0, close(chown_fd));
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * Reopens the file while the ruleset is enforced: the fchown(2)
+	 * below is denied because the WRITE_METADATA right is missing from
+	 * the rights stashed at open time.
+	 */
+	chown_fd = open(chown_file, O_RDWR | O_CLOEXEC);
+	ASSERT_LE(0, chown_fd);
+
+	/*
+	 * An explicit chown(2) with (-1, -1) on a setuid file is an
+	 * explicit request to clear the setuid bit: it requires
+	 * WRITE_METADATA, which file1 has, and clears the bit.
+	 */
+	EXPECT_EQ(0, test_fchown(file_fd, -1, -1));
+	ASSERT_EQ(0, fstat(file_fd, &statbuf));
+	EXPECT_EQ(0, statbuf.st_mode & S_ISUID);
+
+	/* file1 has WRITE_METADATA: allowed */
+	EXPECT_EQ(0, test_chmod(file1, 0644));
+	EXPECT_EQ(0, test_fchmod(file_fd, 0644));
+	EXPECT_EQ(0, test_chown(file1, -1, 0));
+	EXPECT_EQ(0, test_fchown(file_fd, -1, 0));
+	EXPECT_EQ(0, test_lchown(file1, -1, 0));
+	EXPECT_EQ(0, test_utimensat(file1));
+	EXPECT_EQ(0, test_utimensat_now(file1));
+	EXPECT_EQ(0, test_setxattr(file1, "user.test", "a", 1));
+	EXPECT_EQ(0, test_removexattr(file1, "user.test"));
+
+	/*
+	 * Writing is not an explicit metadata change: implicit timestamp
+	 * updates and the implicit setuid-bit clearing are allowed
+	 * without WRITE_METADATA.
+	 */
+	EXPECT_EQ(1, write(file2_fd, "a", 1));
+	ASSERT_EQ(0, fstat(file2_fd, &statbuf));
+	EXPECT_EQ(0, statbuf.st_mode & S_ISUID);
+
+	/* file2 does not have WRITE_METADATA: denied */
+	EXPECT_EQ(EACCES, test_chmod(file2, 0400));
+	EXPECT_EQ(EACCES, test_chown(file2, -1, 0));
+	EXPECT_EQ(EACCES, test_lchown(file2, -1, 0));
+	EXPECT_EQ(EACCES, test_utimensat(file2));
+	EXPECT_EQ(EACCES, test_utimensat_now(file2));
+	EXPECT_EQ(EACCES, test_setxattr(file2, "user.test", "a", 1));
+	EXPECT_EQ(EACCES, test_removexattr(file2, "user.test"));
+
+	/*
+	 * Without the WRITE_METADATA right (i.e. without the right stashed
+	 * at open time), the same explicit chown(2) with (-1, -1) is
+	 * denied and leaves the setuid bit in place.
+	 */
+	EXPECT_EQ(EACCES, test_fchown(chown_fd, -1, -1));
+	ASSERT_EQ(0, fstat(chown_fd, &statbuf));
+	EXPECT_EQ(S_ISUID, statbuf.st_mode & S_ISUID);
+
+	/* dir1 does not have WRITE_METADATA: denied */
+	EXPECT_EQ(EACCES, test_chmod(dir1, 0700));
+	EXPECT_EQ(EACCES, test_chown(dir1, -1, 0));
+
+	/*
+	 * Truncating a setuid file implicitly clears the setuid bit as
+	 * well: this must not require WRITE_METADATA, which trunc_file
+	 * does not have.
+	 */
+	EXPECT_EQ(0, ftruncate(trunc_fd, 0));
+	ASSERT_EQ(0, fstat(trunc_fd, &statbuf));
+	EXPECT_EQ(0, statbuf.st_mode & S_ISUID);
+
+	ASSERT_EQ(0, close(file_fd));
+	ASSERT_EQ(0, close(file2_fd));
+	ASSERT_EQ(0, close(trunc_fd));
+	ASSERT_EQ(0, close(chown_fd));
+}
+
+TEST_F_FORK(layout1, read_metadata)
+{
+	int file_fd, file2_fd, sandbox_fd;
+	int ruleset_fd;
+	const char *const file1 = file1_s1d1;
+	const char *const file2 = file2_s1d1;
+	const char *const dir1 = dir_s1d1;
+	struct stat statbuf;
+	const struct rule rules[] = {
+		{
+			.path = file1,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_READ_METADATA,
+		},
+		{
+			.path = file2,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{
+			.path = dir1,
+			.access = ACCESS_RW,
+		},
+		{},
+	};
+
+	ruleset_fd = create_ruleset(_metadata, ACCESS_RW |
+				    LANDLOCK_ACCESS_FS_READ_METADATA, rules);
+	ASSERT_LE(0, ruleset_fd);
+
+	ASSERT_EQ(0, setxattr(file1, "user.test", "a", 1, 0));
+	ASSERT_EQ(0, setxattr(file2, "user.test", "a", 1, 0));
+
+	/*
+	 * A file descriptor opened before the ruleset is enforced carries
+	 * all access rights.
+	 */
+	sandbox_fd = open(file1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, sandbox_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/* file1 has READ_METADATA: allowed */
+	EXPECT_EQ(0, test_stat(file1, &statbuf));
+	file_fd = open(file1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, file_fd);
+	EXPECT_EQ(0, test_fstat(file_fd, &statbuf));
+	EXPECT_EQ(0, test_getxattr(file1, "user.test", NULL, 0));
+	EXPECT_EQ(0, test_listxattr(file1, NULL, 0));
+
+	/*
+	 * Reading metadata through the file descriptor opened before
+	 * sandboxing is still allowed.
+	 */
+	EXPECT_EQ(0, test_fstat(sandbox_fd, &statbuf));
+	ASSERT_EQ(0, close(sandbox_fd));
+
+	/*
+	 * file2 does not have READ_METADATA: denied, both through the path
+	 * and through a file descriptor opened while the ruleset is
+	 * enforced.
+	 */
+	EXPECT_EQ(EACCES, test_stat(file2, &statbuf));
+	file2_fd = open(file2, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, file2_fd);
+	EXPECT_EQ(EACCES, test_fstat(file2_fd, &statbuf));
+	EXPECT_EQ(EACCES, test_fgetxattr(file2_fd, "user.test", NULL, 0));
+	ASSERT_EQ(0, close(file2_fd));
+	EXPECT_EQ(EACCES, test_getxattr(file2, "user.test", NULL, 0));
+	EXPECT_EQ(EACCES, test_listxattr(file2, NULL, 0));
+	/* dir1 does not have READ_METADATA: denied */
+	EXPECT_EQ(EACCES, test_stat(dir1, &statbuf));
+
+	ASSERT_EQ(0, close(file_fd));
+}
+
+/* Invokes ioctl(FS_IOC_GETFLAGS) and returns its errno or 0. */
+static int test_getflags(int fd, unsigned int *const flags)
+{
+	if (ioctl(fd, FS_IOC_GETFLAGS, flags) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes ioctl(FS_IOC_SETFLAGS) and returns its errno or 0. */
+static int test_setflags(int fd, const unsigned int flags)
+{
+	unsigned int flags_ = flags;
+
+	if (ioctl(fd, FS_IOC_SETFLAGS, &flags_) < 0)
+		return errno;
+	return 0;
+}
+
+#ifndef __NR_file_getattr
+#if defined(__alpha__)
+#define __NR_file_getattr 578
+#else
+#define __NR_file_getattr 468
+#endif
+#endif
+#ifndef __NR_file_setattr
+#if defined(__alpha__)
+#define __NR_file_setattr 579
+#else
+#define __NR_file_setattr 469
+#endif
+#endif
+
+/* Invokes file_getattr(2) and returns its errno or 0. */
+static int test_file_getattr(int dfd, const char *const path,
+			     struct file_attr *const fattr,
+			     const unsigned int at_flags)
+{
+	if (syscall(__NR_file_getattr, dfd, path, fattr, sizeof(*fattr),
+		    at_flags) < 0)
+		return errno;
+	return 0;
+}
+
+/* Invokes file_setattr(2) and returns its errno or 0. */
+static int test_file_setattr(int dfd, const char *const path,
+			     struct file_attr *const fattr,
+			     const unsigned int at_flags)
+{
+	if (syscall(__NR_file_setattr, dfd, path, fattr, sizeof(*fattr),
+		    at_flags) < 0)
+		return errno;
+	return 0;
+}
+
+TEST_F_FORK(layout1, fileattr)
+{
+	const struct rule rules[] = {
+		{
+			.path = file1_s1d1,
+			.access = LANDLOCK_ACCESS_FS_READ_METADATA |
+				  LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		},
+		{},
+	};
+	struct file_attr fattr;
+	unsigned int flags;
+	int fd, fd2, ruleset_fd;
+
+	ruleset_fd = create_ruleset(
+		_metadata,
+		LANDLOCK_ACCESS_FS_READ_METADATA |
+			LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		rules);
+	ASSERT_LE(0, ruleset_fd);
+
+	fd = open(file1_s1d1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/* Allowed file: FS_IOC_GETFLAGS */
+	flags = 0;
+	EXPECT_EQ(0, test_getflags(fd, &flags));
+	EXPECT_EQ(0, flags & FS_NODUMP_FL);
+
+	/* Allowed file: FS_IOC_SETFLAGS */
+	EXPECT_EQ(0, test_setflags(fd, FS_NODUMP_FL));
+	flags = 0;
+	EXPECT_EQ(0, test_getflags(fd, &flags));
+	EXPECT_EQ(FS_NODUMP_FL, flags & FS_NODUMP_FL);
+	EXPECT_EQ(0, test_setflags(fd, 0));
+
+	/* Allowed file: file_getattr(2), with a path and with AT_EMPTY_PATH */
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(0, test_file_getattr(AT_FDCWD, file1_s1d1, &fattr, 0));
+	EXPECT_EQ(0, fattr.fa_xflags & FS_XFLAG_NODUMP);
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(0, test_file_getattr(fd, NULL, &fattr, AT_EMPTY_PATH));
+
+	/* Allowed file: file_setattr(2), with a path and with AT_EMPTY_PATH */
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = FS_XFLAG_NODUMP;
+	EXPECT_EQ(0, test_file_setattr(AT_FDCWD, file1_s1d1, &fattr, 0));
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(0, test_file_getattr(AT_FDCWD, file1_s1d1, &fattr, 0));
+	EXPECT_EQ(FS_XFLAG_NODUMP, fattr.fa_xflags & FS_XFLAG_NODUMP);
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = 0;
+	EXPECT_EQ(0, test_file_setattr(fd, NULL, &fattr, AT_EMPTY_PATH));
+
+	/* Non-allowed file: FS_IOC_GETFLAGS and FS_IOC_SETFLAGS */
+	fd2 = open(file2_s1d1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, fd2);
+	EXPECT_EQ(EACCES, test_getflags(fd2, &flags));
+	EXPECT_EQ(EACCES, test_setflags(fd2, FS_NODUMP_FL));
+
+	/* Non-allowed file: file_getattr(2) and file_setattr(2) */
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(EACCES, test_file_getattr(AT_FDCWD, file2_s1d1, &fattr, 0));
+	fattr.fa_xflags = FS_XFLAG_NODUMP;
+	EXPECT_EQ(EACCES, test_file_setattr(AT_FDCWD, file2_s1d1, &fattr, 0));
+
+	ASSERT_EQ(0, close(fd));
+	ASSERT_EQ(0, close(fd2));
+}
+
+TEST_F_FORK(layout1, fileattr_write_only)
+{
+	const struct rule rules[] = {
+		{
+			.path = file1_s1d1,
+			.access = LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		},
+		{},
+	};
+	struct file_attr fattr;
+	unsigned int flags;
+	int fd, fd2, ruleset_fd;
+
+	ruleset_fd = create_ruleset(_metadata,
+				    LANDLOCK_ACCESS_FS_WRITE_METADATA, rules);
+	ASSERT_LE(0, ruleset_fd);
+
+	fd = open(file1_s1d1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * WRITE_METADATA alone must be enough to set flags: the kernel-side
+	 * read of the old flags must not require READ_METADATA.
+	 */
+	EXPECT_EQ(0, test_setflags(fd, FS_NODUMP_FL));
+	flags = 0;
+	/* READ_METADATA is not handled: reading is allowed. */
+	EXPECT_EQ(0, test_getflags(fd, &flags));
+	EXPECT_EQ(FS_NODUMP_FL, flags & FS_NODUMP_FL);
+
+	/*
+	 * Handle READ_METADATA as well, without granting it.  Reading is
+	 * now denied for file descriptors opened from now on, but writing
+	 * must still work because the kernel-side read of the old flags
+	 * does not go through the READ_METADATA check.
+	 */
+	ruleset_fd = create_ruleset(
+		_metadata,
+		LANDLOCK_ACCESS_FS_READ_METADATA |
+			LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		rules);
+	ASSERT_LE(0, ruleset_fd);
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	fd2 = open(file1_s1d1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, fd2);
+	EXPECT_EQ(EACCES, test_getflags(fd2, &flags));
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(EACCES,
+		  test_file_getattr(fd2, NULL, &fattr, AT_EMPTY_PATH));
+
+	EXPECT_EQ(0, test_setflags(fd2, 0));
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = FS_XFLAG_NODUMP;
+	EXPECT_EQ(0, test_file_setattr(fd2, NULL, &fattr, AT_EMPTY_PATH));
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = 0;
+	EXPECT_EQ(0, test_file_setattr(fd2, NULL, &fattr, AT_EMPTY_PATH));
+
+	/*
+	 * Reading through the file descriptor opened before READ_METADATA
+	 * was handled is still allowed: it carries the rights of the time
+	 * it was opened with.
+	 */
+	EXPECT_EQ(0, test_getflags(fd, &flags));
+
+	ASSERT_EQ(0, close(fd));
+	ASSERT_EQ(0, close(fd2));
+}
+
+TEST_F_FORK(layout1, metadata_open_time)
+{
+	int file_fd, opath_fd, ruleset_fd;
+	struct stat statbuf;
+	unsigned int flags;
+	struct file_attr fattr;
+	const char *const file1 = file1_s1d1;
+	const struct rule rules[] = {
+		{
+			.path = file1,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{},
+	};
+
+	ruleset_fd = create_ruleset(_metadata, ACCESS_RW |
+				    LANDLOCK_ACCESS_FS_READ_METADATA |
+				    LANDLOCK_ACCESS_FS_WRITE_METADATA,
+				    rules);
+	ASSERT_LE(0, ruleset_fd);
+
+	/*
+	 * A file descriptor opened before the ruleset is enforced carries
+	 * all access rights: metadata accesses through it remain allowed
+	 * even though the current thread cannot access the file's metadata
+	 * anymore.
+	 */
+	file_fd = open(file1, O_RDWR | O_CLOEXEC);
+	ASSERT_LE(0, file_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	EXPECT_EQ(0, test_fchmod(file_fd, 0600));
+	EXPECT_EQ(0, test_fchown(file_fd, -1, 0));
+	EXPECT_EQ(0, test_futimensat(file_fd));
+	EXPECT_EQ(0, test_fstat(file_fd, &statbuf));
+	EXPECT_EQ(0, test_getflags(file_fd, &flags));
+	EXPECT_EQ(0, test_fsetxattr(file_fd, "user.test", "a", 1));
+	EXPECT_EQ(0, test_fgetxattr(file_fd, "user.test", NULL, 0));
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(0,
+		  test_file_getattr(file_fd, NULL, &fattr, AT_EMPTY_PATH));
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = FS_XFLAG_NODUMP;
+	EXPECT_EQ(0,
+		  test_file_setattr(file_fd, NULL, &fattr, AT_EMPTY_PATH));
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = 0;
+	EXPECT_EQ(0,
+		  test_file_setattr(file_fd, NULL, &fattr, AT_EMPTY_PATH));
+
+	/* Path-based accesses use the rights of the current thread. */
+	EXPECT_EQ(EACCES, test_chmod(file1, 0644));
+	EXPECT_EQ(EACCES, test_chown(file1, -1, 0));
+	EXPECT_EQ(EACCES, test_utimensat(file1));
+	EXPECT_EQ(EACCES, test_stat(file1, &statbuf));
+	EXPECT_EQ(EACCES, test_setxattr(file1, "user.test", "a", 1));
+	EXPECT_EQ(EACCES, test_getxattr(file1, "user.test", NULL, 0));
+
+	ASSERT_EQ(0, close(file_fd));
+
+	/*
+	 * A file descriptor opened while the ruleset is enforced only
+	 * carries the rights granted to the file: metadata accesses
+	 * through it are denied, but data accesses still work.
+	 */
+	file_fd = open(file1, O_RDWR | O_CLOEXEC);
+	ASSERT_LE(0, file_fd);
+
+	EXPECT_EQ(EACCES, test_fchmod(file_fd, 0600));
+	EXPECT_EQ(EACCES, test_fchown(file_fd, -1, 0));
+	EXPECT_EQ(EACCES, test_futimensat(file_fd));
+	EXPECT_EQ(EACCES, test_fstat(file_fd, &statbuf));
+	EXPECT_EQ(EACCES, test_getflags(file_fd, &flags));
+	EXPECT_EQ(EACCES, test_fsetxattr(file_fd, "user.test", "a", 1));
+	EXPECT_EQ(EACCES, test_fgetxattr(file_fd, "user.test", NULL, 0));
+	memset(&fattr, 0, sizeof(fattr));
+	EXPECT_EQ(EACCES,
+		  test_file_getattr(file_fd, NULL, &fattr, AT_EMPTY_PATH));
+	memset(&fattr, 0, sizeof(fattr));
+	fattr.fa_xflags = FS_XFLAG_NODUMP;
+	EXPECT_EQ(EACCES,
+		  test_file_setattr(file_fd, NULL, &fattr, AT_EMPTY_PATH));
+
+	EXPECT_EQ(0, lseek(file_fd, 0, SEEK_SET));
+	EXPECT_EQ(1, write(file_fd, "a", 1));
+
+	/*
+	 * An O_PATH file descriptor does not carry open-time rights: it is
+	 * checked against the current domain, like path-based accesses.
+	 */
+	opath_fd = open(file1, O_PATH | O_CLOEXEC);
+	ASSERT_LE(0, opath_fd);
+	EXPECT_EQ(EACCES, test_fstat(opath_fd, &statbuf));
+
+	ASSERT_EQ(0, close(file_fd));
+	ASSERT_EQ(0, close(opath_fd));
+}
+
+TEST_F_FORK(layout1, metadata_transfer)
+{
+	int child, fd, status;
+	int socket_fds[2];
+	struct stat statbuf;
+	unsigned int flags;
+	const char *const file1 = file1_s1d1;
+	const struct rule rules[] = {
+		{
+			.path = file1,
+			.access = LANDLOCK_ACCESS_FS_READ_FILE |
+				  LANDLOCK_ACCESS_FS_WRITE_FILE,
+		},
+		{},
+	};
+
+	ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0,
+				socket_fds));
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (child == 0) {
+		/*
+		 * Enables Landlock in the child process, opens a file
+		 * descriptor without the metadata access rights and sends it
+		 * to the non-landlocked parent process.
+		 */
+		int fd;
+
+		enforce_fs(_metadata, ACCESS_RW |
+					  LANDLOCK_ACCESS_FS_READ_METADATA |
+					  LANDLOCK_ACCESS_FS_WRITE_METADATA,
+			   rules);
+
+		fd = open(file1, O_RDWR | O_CLOEXEC);
+		ASSERT_LE(0, fd);
+		ASSERT_EQ(0, send_fd(socket_fds[0], fd));
+		ASSERT_EQ(0, close(fd));
+		ASSERT_EQ(0, close(socket_fds[0]));
+
+		_exit(_metadata->exit_code);
+		return;
+	}
+
+	/*
+	 * The non-landlocked parent process can use the received file
+	 * descriptor for data accesses, but its metadata accesses are
+	 * still denied with the rights stashed at open time.
+	 */
+	fd = recv_fd(socket_fds[1]);
+	ASSERT_LE(0, fd);
+	EXPECT_EQ(1, write(fd, "a", 1));
+	EXPECT_EQ(EACCES, test_fchmod(fd, 0600));
+	EXPECT_EQ(EACCES, test_fchown(fd, -1, 0));
+	EXPECT_EQ(EACCES, test_futimensat(fd));
+	EXPECT_EQ(EACCES, test_fstat(fd, &statbuf));
+	EXPECT_EQ(EACCES, test_getflags(fd, &flags));
+	ASSERT_EQ(0, close(fd));
+
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_EQ(1, WIFEXITED(status));
+	ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+	ASSERT_EQ(0, close(socket_fds[0]));
+	ASSERT_EQ(0, close(socket_fds[1]));
+}
+
+TEST_F_FORK(layout2_overlay, fileattr)
+{
+	const struct rule rules[] = {
+		{
+			.path = MERGE_DATA,
+			.access = LANDLOCK_ACCESS_FS_READ_METADATA |
+				  LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		},
+		{},
+	};
+	unsigned int flags;
+	int fd, fd2, ruleset_fd;
+
+	if (self->skip_test)
+		SKIP(return, "overlayfs is not supported (test)");
+
+	ruleset_fd = create_ruleset(
+		_metadata,
+		LANDLOCK_ACCESS_FS_READ_METADATA |
+			LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		rules);
+	ASSERT_LE(0, ruleset_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/*
+	 * The overlay mount was created before the ruleset was enforced
+	 * (i.e. as an administrator): the internal forwards to the real
+	 * paths must not interfere with the allowed accesses below.
+	 */
+	fd = open(merge_fl1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, fd);
+
+	flags = 0;
+	EXPECT_EQ(0, test_getflags(fd, &flags));
+	EXPECT_EQ(0, flags & FS_NODUMP_FL);
+	/* Copies the file up and sets the flag on the upper real file. */
+	EXPECT_EQ(0, test_setflags(fd, FS_NODUMP_FL));
+	flags = 0;
+	EXPECT_EQ(0, test_getflags(fd, &flags));
+	EXPECT_EQ(FS_NODUMP_FL, flags & FS_NODUMP_FL);
+	EXPECT_EQ(0, test_setflags(fd, 0));
+
+	ASSERT_EQ(0, close(fd));
+
+	/* Non-allowed path: raw lower tmpfs path, outside of MERGE_DATA. */
+	fd2 = open(lower_fl1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, fd2);
+	EXPECT_EQ(EACCES, test_getflags(fd2, &flags));
+	EXPECT_EQ(EACCES, test_setflags(fd2, FS_NODUMP_FL));
+	ASSERT_EQ(0, close(fd2));
+
+	/*
+	 * Removes the copied-up file to keep the upper tree in the state
+	 * expected by FIXTURE_TEARDOWN_PARENT().
+	 */
+	EXPECT_EQ(0, remove_path(merge_fl1));
+}
+
+/*
+ * A landlocked process cannot change the mount topology with mount(2),
+ * even with CAP_SYS_ADMIN (see hook_sb_mount()).  An overlay filesystem
+ * can therefore not be mounted this way by a restricted process.
+ */
+TEST_F_FORK(layout1, overlay_mount_denied)
+{
+	const char lower_dir[] = TMP_DIR "/ovl-lower";
+	const char upper_dir[] = TMP_DIR "/ovl-upper";
+	const char work_dir[] = TMP_DIR "/ovl-work";
+	const char merge_dir[] = TMP_DIR "/ovl-merge";
+	char mount_data[256];
+	const struct rule rules[] = {
+		{
+			.path = merge_dir,
+			.access = LANDLOCK_ACCESS_FS_READ_METADATA |
+				  LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		},
+		{},
+	};
+	int ruleset_fd;
+
+	ASSERT_EQ(0, mkdir(lower_dir, 0700));
+	ASSERT_EQ(0, mkdir(upper_dir, 0700));
+	ASSERT_EQ(0, mkdir(work_dir, 0700));
+	ASSERT_EQ(0, mkdir(merge_dir, 0700));
+
+	snprintf(mount_data, sizeof(mount_data),
+		 "lowerdir=%s,upperdir=%s,workdir=%s", lower_dir, upper_dir,
+		 work_dir);
+
+	ruleset_fd = create_ruleset(
+		_metadata,
+		LANDLOCK_ACCESS_FS_READ_METADATA |
+			LANDLOCK_ACCESS_FS_WRITE_METADATA,
+		rules);
+	ASSERT_LE(0, ruleset_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	set_cap(_metadata, CAP_SYS_ADMIN);
+	EXPECT_EQ(-1, mount("overlay", merge_dir, "overlay", 0, mount_data));
+	EXPECT_EQ(EPERM, errno);
+	clear_cap(_metadata, CAP_SYS_ADMIN);
+
+	EXPECT_EQ(0, rmdir(lower_dir));
+	EXPECT_EQ(0, rmdir(upper_dir));
+	EXPECT_EQ(0, rmdir(work_dir));
+	EXPECT_EQ(0, rmdir(merge_dir));
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c
index 64014ade3a0e..97506d249c4f 100644
--- a/tools/testing/selftests/landlock/trace_fs_test.c
+++ b/tools/testing/selftests/landlock/trace_fs_test.c
@@ -252,7 +252,8 @@ TEST_F(trace_fs, add_rule_path_beneath)
 TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow)
 {
 	static const char access_prefix[] = "execute|write_file|read_file|";
-	static const char access_suffix[] = "|ioctl_dev|resolve_unix";
+	static const char access_suffix[] =
+		"|ioctl_dev|resolve_unix|read_metadata|write_metadata";
 	struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 17/18] samples/landlock: Add metadata rights to sandboxer
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (15 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 16/18] selftests/landlock: Add tests for metadata " Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  2026-10-27 12:11 ` [PATCH v3 -next 18/18] Documentation: Update landlock doc for metadata rights Cai Xinchen
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

Handle the new LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights in the sandboxer:
READ_METADATA is added to the read-only set, WRITE_METADATA to the
read-write set, and both to ACCESS_FILE.

Update the ABI version fallback switch to remove the metadata rights
for ABI < 13, and bump LANDLOCK_ABI_LAST to 13.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 samples/landlock/sandboxer.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 1362f08ff14b..29965ef90bed 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -137,6 +137,8 @@ static int parse_path(char *env_path, const char ***const path_list)
 	LANDLOCK_ACCESS_FS_EXECUTE | \
 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
 	LANDLOCK_ACCESS_FS_READ_FILE | \
+	LANDLOCK_ACCESS_FS_READ_METADATA | \
+	LANDLOCK_ACCESS_FS_WRITE_METADATA | \
 	LANDLOCK_ACCESS_FS_TRUNCATE | \
 	LANDLOCK_ACCESS_FS_IOCTL_DEV | \
 	LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
@@ -487,7 +489,8 @@ static bool check_ruleset_scope(const char *const env_var,
 #define ACCESS_FS_ROUGHLY_READ ( \
 	LANDLOCK_ACCESS_FS_EXECUTE | \
 	LANDLOCK_ACCESS_FS_READ_FILE | \
-	LANDLOCK_ACCESS_FS_READ_DIR)
+	LANDLOCK_ACCESS_FS_READ_DIR | \
+	LANDLOCK_ACCESS_FS_READ_METADATA)
 
 #define ACCESS_FS_ROUGHLY_WRITE ( \
 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
@@ -503,7 +506,8 @@ static bool check_ruleset_scope(const char *const env_var,
 	LANDLOCK_ACCESS_FS_REFER | \
 	LANDLOCK_ACCESS_FS_TRUNCATE | \
 	LANDLOCK_ACCESS_FS_IOCTL_DEV | \
-	LANDLOCK_ACCESS_FS_RESOLVE_UNIX)
+	LANDLOCK_ACCESS_FS_RESOLVE_UNIX | \
+	LANDLOCK_ACCESS_FS_WRITE_METADATA)
 
 /* clang-format on */
 
@@ -570,7 +574,7 @@ static int add_quiet_access(const char *const env_var,
 	return 0;
 }
 
-#define LANDLOCK_ABI_LAST 12
+#define LANDLOCK_ABI_LAST 13
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -780,6 +784,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		ruleset_attr.handled_permissions &=
 			~(LANDLOCK_PERMISSION_NAMESPACE_USE |
 			  LANDLOCK_PERMISSION_CAPABILITY_USE);
+		__attribute__((fallthrough));
+	case 12:
+		/* Removes metadata rights for ABI < 13 */
+		ruleset_attr.handled_access_fs &=
+			~(LANDLOCK_ACCESS_FS_READ_METADATA |
+			  LANDLOCK_ACCESS_FS_WRITE_METADATA);
 		/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
 		fprintf(stderr,
 			"Hint: You should update the running kernel "
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH v3 -next 18/18] Documentation: Update landlock doc for metadata rights
  2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (16 preceding siblings ...)
  2026-10-27 12:11 ` [PATCH v3 -next 17/18] samples/landlock: Add metadata rights to sandboxer Cai Xinchen
@ 2026-10-27 12:11 ` Cai Xinchen
  17 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-27 12:11 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan, utilityemal77,
	caixinchen1
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, rdunlap, gregkh,
	rafael, dakr, johan, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, hyc.lee, miklos,
	amir73il, senozhatsky, chenxiaosong, dsterba, mason, kpsingh,
	matt, rostedt, mhiramat, mathieu.desnoyers, john.johansen,
	georgia.garcia, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, takedakn,
	penguin-kernel, xiujianfeng, nanx95726, djwong, lujialin4, bpf,
	linux-doc, linux-kernel, linux-security-module, driver-core,
	linux-block, linux-btrfs, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, ntfs, linux-unionfs, linux-cifs, linux-trace-kernel,
	apparmor, linux-integrity, selinux, linux-kselftest

Update the user space documentation for the new
LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights: add them to the
example ruleset attributes, extend the ABI version fallback switch
to remove them for ABI < 13, and document their semantics in a new
"File metadata" section, including the fact that only explicit
metadata changes requested by user space are restricted.

The new section also documents the open-time semantic of both
rights: metadata accesses requested through an already opened file
descriptor are granted according to the rights available at open(2)
time, like for LANDLOCK_ACCESS_FS_TRUNCATE, whereas path-based
accesses and O_PATH file descriptors are always checked against the
rights of the calling thread.  The "Rights associated with
file descriptors" section is updated accordingly.

Finally, the new section warns that most of the time,
LANDLOCK_ACCESS_FS_READ_METADATA should be granted together with
LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_READ_DIR:
programs commonly read metadata alongside file contents and
directory listings (e.g. with stat(2)) and do not expect such calls
to fail, while denying only READ_FILE or READ_DIR does not hide
metadata.

Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 Documentation/userspace-api/landlock.rst | 92 ++++++++++++++++++++++--
 1 file changed, 86 insertions(+), 6 deletions(-)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 3d45b956b640..489306ab0da8 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -87,7 +87,9 @@ to be explicit about the denied-by-default access rights.
             LANDLOCK_ACCESS_FS_REFER |
             LANDLOCK_ACCESS_FS_TRUNCATE |
             LANDLOCK_ACCESS_FS_IOCTL_DEV |
-            LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
+            LANDLOCK_ACCESS_FS_RESOLVE_UNIX |
+            LANDLOCK_ACCESS_FS_READ_METADATA |
+            LANDLOCK_ACCESS_FS_WRITE_METADATA,
         .handled_access_net =
             LANDLOCK_ACCESS_NET_BIND_TCP |
             LANDLOCK_ACCESS_NET_CONNECT_TCP |
@@ -159,6 +161,12 @@ version, and only use the available subset of access rights:
         ruleset_attr.handled_permissions &=
             ~(LANDLOCK_PERMISSION_NAMESPACE_USE |
               LANDLOCK_PERMISSION_CAPABILITY_USE);
+        __attribute__((fallthrough));
+    case 12:
+        /* Removes metadata rights for ABI < 13 */
+        ruleset_attr.handled_access_fs &=
+            ~(LANDLOCK_ACCESS_FS_READ_METADATA |
+              LANDLOCK_ACCESS_FS_WRITE_METADATA);
     }
 
 This enables the creation of an inclusive ruleset that will contain our rules.
@@ -669,14 +677,73 @@ for writing, sidestepping the ``LANDLOCK_ACCESS_FS_TRUNCATE`` right.
 
 The truncate right is associated with the opened file (see below).
 
+File metadata
+-------------
+
+The ``LANDLOCK_ACCESS_FS_READ_METADATA`` right governs reading file and
+directory metadata: :manpage:`stat(2)` and friends,
+:manpage:`getxattr(2)`, :manpage:`listxattr(2)` and friends, and POSIX ACL
+reads.
+
+The ``LANDLOCK_ACCESS_FS_WRITE_METADATA`` right governs explicit changes to
+file and directory metadata: :manpage:`chmod(2)` and friends,
+:manpage:`chown(2)` and friends, :manpage:`utimensat(2)`,
+:manpage:`setxattr(2)`, :manpage:`removexattr(2)` and friends, and POSIX ACL
+changes.  Both rights apply to files and directories alike.
+
+Implicit metadata changes performed by the kernel are not restricted by
+``LANDLOCK_ACCESS_FS_WRITE_METADATA``: reading a file may update its access
+time, writing a file updates its modification and change times and may change
+the file size, and writing to or truncating a file with a set-user-ID or
+set-group-ID bit clears these bits.  File size changes through
+:manpage:`truncate(2)` and friends are governed by
+``LANDLOCK_ACCESS_FS_TRUNCATE`` instead.  An explicit metadata change request
+that changes nothing, e.g. :manpage:`chown(2)` with owner and group ``-1`` on
+a file without a set-user-ID or set-group-ID bit, is not restricted either;
+the same :manpage:`chown(2)` on a file carrying such a bit explicitly
+requests clearing it, and therefore requires
+``LANDLOCK_ACCESS_FS_WRITE_METADATA``.
+
+.. warning::
+
+   Many programs read metadata as part of reading file contents or listing
+   directories (e.g. calling :manpage:`stat(2)` before or after
+   :manpage:`open(2)`), and do not expect such calls to fail with
+   ``EACCES``.  Most of the time, ``LANDLOCK_ACCESS_FS_READ_METADATA``
+   should therefore be granted together with
+   ``LANDLOCK_ACCESS_FS_READ_FILE`` and ``LANDLOCK_ACCESS_FS_READ_DIR``.
+   Conversely, denying only ``LANDLOCK_ACCESS_FS_READ_FILE`` or
+   ``LANDLOCK_ACCESS_FS_READ_DIR`` does not hide metadata: it remains
+   readable through :manpage:`stat(2)` and friends unless
+   ``LANDLOCK_ACCESS_FS_READ_METADATA`` is denied as well.  Tailored
+   programs that do not read metadata at all can still deny this right to
+   further restrict their sandbox.
+
+Both metadata rights are associated with opened files, like
+``LANDLOCK_ACCESS_FS_TRUNCATE`` (see below): metadata accesses and changes
+requested through an already opened file descriptor, e.g. with
+:manpage:`fchmod(2)`, :manpage:`fchown(2)`, :manpage:`utimensat(2)` on an
+empty path, :manpage:`fstat(2)`, :manpage:`fgetxattr(2)`,
+:manpage:`fsetxattr(2)`, :manpage:`ioctl(2)` with ``FS_IOC_GETFLAGS`` or
+``FS_IOC_SETFLAGS``, or ``file_getattr(2)`` and ``file_setattr(2)`` with
+``AT_EMPTY_PATH``, are granted according to the rights available at
+:manpage:`open(2)` time, not according to the rights of the calling thread.
+Metadata accesses requested through a path (e.g. :manpage:`chmod(2)`,
+:manpage:`stat(2)`) are always checked against the rights of the calling
+thread.  ``O_PATH`` file descriptors are not opened through the open
+permission checks: metadata accesses through them are checked against the
+rights of the calling thread as well.
+
 Rights associated with file descriptors
 ---------------------------------------
 
-When opening a file, the availability of the ``LANDLOCK_ACCESS_FS_TRUNCATE`` and
-``LANDLOCK_ACCESS_FS_IOCTL_DEV`` rights is associated with the newly created
-file descriptor and will be used for subsequent truncation and ioctl attempts
-using :manpage:`ftruncate(2)` and :manpage:`ioctl(2)`.  The behavior is similar
-to opening a file for reading or writing, where permissions are checked during
+When opening a file, the availability of the ``LANDLOCK_ACCESS_FS_TRUNCATE``,
+``LANDLOCK_ACCESS_FS_IOCTL_DEV``, ``LANDLOCK_ACCESS_FS_READ_METADATA`` and
+``LANDLOCK_ACCESS_FS_WRITE_METADATA`` rights is associated with the newly
+created file descriptor and will be used for subsequent truncation, ioctl
+and metadata attempts using :manpage:`ftruncate(2)`, :manpage:`ioctl(2)` and
+the file descriptor-based metadata system calls.  The behavior is similar to
+opening a file for reading or writing, where permissions are checked during
 :manpage:`open(2)`, but not during the subsequent :manpage:`read(2)` and
 :manpage:`write(2)` calls.
 
@@ -1051,6 +1118,19 @@ fd-reference acquisition (:manpage:`open_tree(2)`, :manpage:`fsmount(2)`) with
 the new ``LANDLOCK_PERMISSION_NAMESPACE_USE`` permission flag and
 ``LANDLOCK_RULE_NAMESPACE`` rule type.
 
+File metadata (ABI < 13)
+------------------------
+
+Starting with the Landlock ABI version 13, it is possible to restrict
+file metadata access with the new ``LANDLOCK_ACCESS_FS_READ_METADATA``
+and ``LANDLOCK_ACCESS_FS_WRITE_METADATA`` access rights, which control
+reading file and directory metadata (e.g. with :manpage:`stat(2)` and
+:manpage:`getxattr(2)`) and changing it (e.g. with :manpage:`chmod(2)`
+and :manpage:`setxattr(2)`).  As with ``LANDLOCK_ACCESS_FS_TRUNCATE``,
+these rights are tied to opened files: metadata accesses through an
+already opened file descriptor are granted according to the rights
+available at :manpage:`open(2)` time.
+
 .. _kernel_support:
 
 Kernel support
-- 
2.18.0.huawei.25


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 05/18] LSM: pass struct path and struct file to the inode_setattr hook
  2026-10-27 12:11 ` [PATCH v3 -next 05/18] LSM: pass struct path and struct file to the inode_setattr hook Cai Xinchen
@ 2026-10-27 12:48   ` sashiko-bot
  0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 12:48 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] vfs: do_fchmodat(), do_fchownat(), and do_utimes_path() reject O_PATH fds
- [High] lsm: missing __nullable on file parameter of inode_setattr hook
- [High] fs: AT_EMPTY_PATH regressions in do_utimes_path(), do_fchmodat(), and do_fchownat()
- [Medium] lsm: untrusted path->dentry breaks bpf_lsm_inode_setattr() dentry kfuncs
--

commit 5205e882fa7eb32587d3ca77d8a0f86441ec4e69
Author: Cai Xinchen <caixinchen1@huawei.com>

LSM: pass struct path and struct file to the inode_setattr hook

This commit updates the inode_setattr LSM hook and its callers to pass a
struct path and an optional struct file alongside the mnt_idmap. It also
dispatches AT_EMPTY_PATH operations in do_fchmodat(), do_fchownat(), and
do_utimes_path() to the file descriptor-based helpers so the struct file can
be threaded through notify_change().

> diff --git a/fs/open.c b/fs/open.c
> index 294d0ea7488e6..882985d4008bf 100644
> --- a/fs/open.c
> +++ b/fs/open.c
[ ... ]
> @@ -724,12 +729,22 @@ static int do_fchmodat(int dfd, const char __user *filename, umode_t mode,
>  	if (unlikely(flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)))
>  		return -EINVAL;
>  
> +	CLASS(filename_maybe_null, name)(filename, flags);
> +
> +	if (!name && dfd >= 0) {
> +		CLASS(fd, f)(dfd);
> +
> +		if (fd_empty(f))
> +			return -EBADF;
> +
> +		return vfs_fchmod(fd_file(f), mode);
> +	}
[ ... ]
> @@ -844,14 +860,24 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
>  	if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
>  		return -EINVAL;
>  
> +	CLASS(filename_maybe_null, name)(filename, flag);
> +
> +	if (!name && dfd >= 0) {
> +		CLASS(fd, f)(dfd);
> +
> +		if (fd_empty(f))
> +			return -EBADF;
> +
> +		return vfs_fchown(fd_file(f), user, group);
> +	}

[Severity: High]
Does using CLASS(fd, f)(dfd) here and in do_fchmodat() and do_utimes_path()
break AT_EMPTY_PATH on O_PATH file descriptors?

Previously, an empty path with AT_EMPTY_PATH went through
filename_lookup() -> path_lookupat() -> path_init(), which looks up nd->dfd
using CLASS(fd_raw, f)(nd->dfd) (just as vfs_fstat() does for vfs_fstatat()):

fs/namei.c:path_init() {
	...
		CLASS(fd_raw, f)(nd->dfd);
		struct dentry *dentry;

		if (fd_empty(f))
			return ERR_PTR(-EBADF);
	...
}

Supporting O_PATH file descriptors without /proc/self/fd was the reason
AT_EMPTY_PATH was added to fchmodat2(2) in commit 5daeb41a6fc9 ("fchmodat2:
add support for AT_EMPTY_PATH"), and is documented UAPI for fchownat(2) and
utimensat(2), since fchmod(2), fchown(2), and futimens(3) reject O_PATH
descriptors with EBADF.

By switching to CLASS(fd, f)(dfd), which calls fdget(dfd) ->
__fget_light(dfd, FMODE_PATH), fd_empty(f) is true whenever FMODE_PATH is
set, causing fchmodat2(dfd, "", mode, AT_EMPTY_PATH),
fchownat(dfd, "", user, group, AT_EMPTY_PATH), and
utimensat(dfd, "", times, AT_EMPTY_PATH) on an O_PATH fd to fail with -EBADF.

In addition, in do_dentry_open(), O_PATH files return before
security_file_open() is called:

fs/open.c:do_dentry_open() {
	...
	if (unlikely(f->f_flags & O_PATH)) {
		f->f_mode = FMODE_PATH | FMODE_OPENED;
		file_set_fsnotify_mode(f, FMODE_NONOTIFY);
		f->f_op = &empty_fops;
		return 0;
	}
	...
}

Because an O_PATH struct file was never authorized at open(2) time, should
these callers use CLASS(fd_raw, f)(dfd) and ensure that LSMs checking the
struct file in security_inode_setattr() do not treat an FMODE_PATH file as
carrying open-time permissions?

> diff --git a/fs/utimes.c b/fs/utimes.c
> index 5193d504d289f..6021c481e742f 100644
> --- a/fs/utimes.c
> +++ b/fs/utimes.c
[ ... ]
> @@ -88,15 +89,25 @@ static int do_utimes_path(int dfd, const char __user *filename,
>  	if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))
>  		return -EINVAL;
>  
> +	CLASS(filename_maybe_null, name)(filename, flags);
> +
> +	if (!name && dfd >= 0) {
> +		CLASS(fd, f)(dfd);
> +
> +		if (fd_empty(f))
> +			return -EBADF;
> +
> +		return vfs_utimes(&fd_file(f)->f_path, times, fd_file(f));
> +	}

[Severity: High]
Beyond the CLASS(fd, f)(dfd) issue with O_PATH file descriptors (which also
makes the FMODE_PATH check for hook_path_setattr in commit "landlock: Add
READ_METADATA and WRITE_METADATA access rights" unreachable), can switching
do_utimes_path() from CLASS(filename_uflags, name) to
CLASS(filename_maybe_null, name) cause utimensat(AT_FDCWD, NULL, times,
AT_EMPTY_PATH) to modify the current working directory instead of returning
-EFAULT?

In do_utimes(), a NULL filename with dfd != AT_FDCWD is routed to
do_utimes_fd() (which rejects non-zero flags like AT_EMPTY_PATH with -EINVAL),
whereas dfd == AT_FDCWD is routed to do_utimes_path():

fs/utimes.c:do_utimes() {
	if (filename == NULL && dfd != AT_FDCWD)
		return do_utimes_fd(dfd, times, flags);
	return do_utimes_path(dfd, filename, times, flags);
}

When filename == NULL, dfd == AT_FDCWD, and flags == AT_EMPTY_PATH,
CLASS(filename_uflags, name) previously returned ERR_PTR(-EFAULT). With
CLASS(filename_maybe_null, name), name is NULL, the (!name && dfd >= 0) check
is skipped because AT_FDCWD < 0, and filename_lookup(AT_FDCWD, NULL, ...)
treats NULL as an empty path and updates the current working directory.

Also, when (!name && dfd >= 0) is taken in do_utimes_path(), it bypasses
filename_lookup() (which called audit_inode()) and calls vfs_utimes(). Unlike
vfs_fchmod() and vfs_fchown(), vfs_utimes() does not call audit_file().
Should audit_file(fd_file(f)) be called before vfs_utimes() on the fd paths?

> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 539525648b303..ea6e0fbaa3b9c 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -141,8 +141,16 @@ LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode,
>  	 bool rcu)
>  LSM_HOOK(int, 0, inode_permission, const struct mnt_idmap *idmap,
>  	 struct inode *inode, int mask)
> -LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap, struct dentry *dentry,
> -	 struct iattr *attr)
> +/*
> + * For the hooks that pass a struct path: the VFS only sanctions
> + * passing the path to VFS helpers taking a struct path (e.g.,
> + * notify_change(), the *xattr() helpers); it makes no guarantees
> + * with regard to path->mnt, whose layout and implementation details
> + * are subject to change.  Implementations must not rely on its
> + * properties.
> + */
> +LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap,
> +	 const struct path *path, struct iattr *attr, struct file *file)

[Severity: High]
Could omitting a __nullable annotation on the struct file *file parameter of
inode_setattr allow a BPF LSM program to trigger a kernel NULL pointer
dereference when notify_change() is called with file == NULL?

All path-based and internal callers of notify_change() (such as
chmod_common(), chown_common(), vfs_utimes(), do_truncate(), and
__remove_privs()) pass file == NULL, which do_notify_change() forwards to
security_inode_setattr(idmap, path, attr, NULL).

In btf_ctx_access(), arguments of trusted BPF LSM hooks are marked
PTR_TO_BTF_ID | PTR_TRUSTED, and PTR_MAYBE_NULL is only added if the parameter
name ends with __nullable:

kernel/bpf/btf.c:btf_ctx_access() {
	...
	info->reg_type = PTR_TO_BTF_ID;
	if (prog_args_trusted(prog))
		info->reg_type |= PTR_TRUSTED;

	if (btf_param_match_suffix(btf, &args[arg], "__nullable"))
		info->reg_type |= PTR_MAYBE_NULL;
	...
}

Without __nullable (either in lsm_hook_defs.h or via a strong override in
kernel/bpf/bpf_lsm_proto.c like bpf_lsm_mmap_file()), the BPF verifier treats
file as a non-NULL trusted pointer and permits passing it without a NULL
check to kfuncs such as bpf_get_file_xattr() or bpf_real_data_inode(), which
immediately dereference file->f_path.dentry.

[Severity: Medium]
Does replacing struct dentry *dentry with const struct path *path in
inode_setattr also break sleepable BPF LSM programs that pass the dentry to
bpf_get_dentry_xattr(), bpf_set_dentry_xattr(), or bpf_remove_dentry_xattr()?

bpf_lsm_inode_setattr is listed in d_inode_locked_hooks in
fs/bpf_fs_kfuncs.c specifically so sleepable BPF LSM programs attached to
inode_setattr can call those dentry xattr kfuncs:

fs/bpf_fs_kfuncs.c {
	BTF_SET_START(d_inode_locked_hooks)
	...
	BTF_ID(func, bpf_lsm_inode_setattr)
	...
	BTF_SET_END(d_inode_locked_hooks)
}

However, struct path is not listed in BTF_TYPE_SAFE_TRUSTED in
type_is_trusted() in kernel/bpf/verifier.c. When a BPF program walks
path->dentry, check_ptr_to_btf_access() marks the resulting dentry pointer as
PTR_UNTRUSTED, causing the BPF verifier to reject calls to the dentry xattr
kfuncs.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=5

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks
  2026-10-27 12:11 ` [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks Cai Xinchen
@ 2026-10-27 13:02   ` sashiko-bot
  2026-10-27 15:01     ` Alexei Starovoitov
  0 siblings, 1 reply; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 13:02 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] bpf: missing __nullable on file arg in inode xattr LSM hooks allows NULL deref
- [High] bpf: untrusted path->dentry in inode xattr LSM hooks breaks dentry kfuncs
- [Low] security: missing @idmap kernel-doc in cap_inode_removexattr()
--

commit e83206fa3b704333b0de2a23321bc120a419f251
Author: Cai Xinchen <caixinchen1@huawei.com>

LSM: pass struct path and struct file to the inode xattr hooks

This commit updates the inode_setxattr, inode_getxattr, inode_listxattr, and
inode_removexattr LSM hooks and their callers to take a const struct path
instead of a struct dentry, and passes the associated struct file when
available (or NULL for path-based and in-kernel operations). It also updates
the BPF LSM selftests attached to these hooks to pass path->dentry to the
dentry xattr kfuncs.

> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index ea6e0fbaa3b9c..2315961b6d713 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -156,14 +156,16 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
>  LSM_HOOK(int, 0, inode_getattr, const struct path *path)
>  LSM_HOOK(int, 0, inode_xattr_skipcap, const char *name)
>  LSM_HOOK(int, 0, inode_setxattr, const struct mnt_idmap *idmap,
> -	 struct dentry *dentry, const char *name, const void *value,
> -	 size_t size, int flags)
> +	 const struct path *path, const char *name, const void *value,
> +	 size_t size, int flags, struct file *file)
>  LSM_HOOK(void, LSM_RET_VOID, inode_post_setxattr, struct dentry *dentry,
>  	 const char *name, const void *value, size_t size, int flags)
> -LSM_HOOK(int, 0, inode_getxattr, struct dentry *dentry, const char *name)
> -LSM_HOOK(int, 0, inode_listxattr, struct dentry *dentry)
> +LSM_HOOK(int, 0, inode_getxattr, const struct path *path, const char *name,
> +	 struct file *file)
> +LSM_HOOK(int, 0, inode_listxattr, const struct path *path,
> +	 struct file *file)
>  LSM_HOOK(int, 0, inode_removexattr, const struct mnt_idmap *idmap,
> -	 struct dentry *dentry, const char *name)
> +	 const struct path *path, const char *name, struct file *file)

[Severity: Critical]
Since path-based xattr syscalls (filename_setxattr(), filename_getxattr(),
filename_listxattr(), and filename_removexattr() in fs/xattr.c) and in-kernel
callers pass file = NULL to these hooks, can omitting the __nullable suffix
on the new file parameter allow a BPF LSM program to trigger a kernel NULL
pointer dereference?

In kernel/bpf/btf.c:btf_ctx_access(), pointer arguments of trusted BPF LSM
hooks are marked as non-NULL PTR_TO_BTF_ID | PTR_TRUSTED unless the parameter
name ends with __nullable:

kernel/bpf/btf.c:btf_ctx_access() {
	...
	info->reg_type = PTR_TO_BTF_ID;
	if (prog_args_trusted(prog))
		info->reg_type |= PTR_TRUSTED;

	if (btf_param_match_suffix(btf, &args[arg], "__nullable"))
		info->reg_type |= PTR_MAYBE_NULL;
}

Without PTR_MAYBE_NULL on file, bpf_may_fault_on_deref() returns false, so
convert_ctx_accesses() does not rewrite loads from file (such as walking the
trusted field file->f_inode via BTF_TYPE_SAFE_TRUSTED(struct file)) to
BPF_PROBE_MEM. In addition, the verifier allows passing file without a NULL
check directly to kfuncs that require a non-NULL trusted struct file * and
immediately dereference it, such as bpf_get_file_xattr():

fs/bpf_fs_kfuncs.c:bpf_get_file_xattr() {
	struct dentry *dentry;

	dentry = file_dentry(file);
	return bpf_get_dentry_xattr(dentry, name__str, value_p);
}

Should these four hooks either name the parameter file__nullable or provide
strong bpf_lsm_* overrides with file__nullable in kernel/bpf/bpf_lsm_proto.c
(similar to bpf_lsm_mmap_file())?

> diff --git a/security/commoncap.c b/security/commoncap.c
> index 565c893c6d4b4..0a94b9cd231fa 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
[ ... ]
> @@ -1044,25 +1048,21 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name,
>  /**
>   * cap_inode_removexattr - Determine whether an xattr may be removed
>   *
> - * @idmap:	idmap of the mount the inode was found from
> - * @dentry:	The inode/dentry being altered
> + * @path:	The inode/path being altered
>   * @name:	The name of the xattr to be changed
> + * @file:	the file through which the xattr removal is requested, if any
>   *
>   * Determine whether an xattr may be removed from an inode, returning 0 if
>   * permission is granted, -ve if denied.
>   *
> - * If the inode has been found through an idmapped mount the idmap of
> - * the vfsmount must be passed through @idmap. This function will then
> - * take care to map the inode according to @idmap before checking
> - * permissions. On non-idmapped mounts or if permission checking is to be
> - * performed on the raw inode simply pass @nop_mnt_idmap.
> - *
>   * This is used to make sure security xattrs don't get removed by those who
>   * aren't privileged to remove them.
>   */
>  int cap_inode_removexattr(const struct mnt_idmap *idmap,
> -			  struct dentry *dentry, const char *name)
> +			  const struct path *path, const char *name,
> +			  struct file *file)

[Severity: Low]
Was the @idmap kernel-doc description above cap_inode_removexattr() dropped
by mistake? Since const struct mnt_idmap *idmap is still a parameter of
cap_inode_removexattr(), removing @idmap from the comment block will trigger
a scripts/kernel-doc warning.

> diff --git a/tools/testing/selftests/bpf/progs/test_get_xattr.c b/tools/testing/selftests/bpf/progs/test_get_xattr.c
> index 54305f4c9f2dd..97361ff10e9f1 100644
> --- a/tools/testing/selftests/bpf/progs/test_get_xattr.c
> +++ b/tools/testing/selftests/bpf/progs/test_get_xattr.c
> @@ -56,7 +56,8 @@ int BPF_PROG(test_file_open, struct file *f)
>  }
>  
>  SEC("lsm.s/inode_getxattr")
> -int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
> +int BPF_PROG(test_inode_getxattr, const struct path *path, char *name,
> +	     struct file *file)
>  {
>  	struct bpf_dynptr value_ptr;
>  	__u32 pid;
> @@ -69,7 +70,7 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
>  	bpf_dynptr_from_mem(value2, sizeof(value2), 0, &value_ptr);
>  
>  	for (i = 0; i < ARRAY_SIZE(xattr_names); i++) {
> -		ret = bpf_get_dentry_xattr(dentry, xattr_names[i], &value_ptr);
> +		ret = bpf_get_dentry_xattr(path->dentry, xattr_names[i], &value_ptr);
>  		if (ret == sizeof(expected_value))
>  			break;
>  	}

[Severity: High]
Will the BPF verifier reject loading test_inode_getxattr() here (as well as
test_inode_getxattr() and test_inode_setxattr() in
tools/testing/selftests/bpf/progs/test_set_remove_xattr.c) when passing
path->dentry to bpf_get_dentry_xattr(), bpf_set_dentry_xattr(), and
bpf_remove_dentry_xattr()?

Previously, dentry was a direct argument to the inode_getxattr and
inode_setxattr hooks, so btf_ctx_access() marked it as
PTR_TO_BTF_ID | PTR_TRUSTED.

With the hook argument changed to const struct path *path, dereferencing
path->dentry goes through check_ptr_to_btf_access(), which only preserves
PTR_TRUSTED if type_is_trusted() or type_is_trusted_or_null() returns true:

kernel/bpf/verifier.c:check_ptr_to_btf_access() {
	...
	if (flag & PTR_TRUSTED) {
		if (type_is_trusted(env, reg, field_name, btf_id)) {
			flag |= PTR_TRUSTED;
		} else if (type_is_trusted_or_null(env, reg, field_name, btf_id)) {
			flag |= PTR_TRUSTED | PTR_MAYBE_NULL;
		} else if (in_rcu_cs(env) && !type_may_be_null(reg->type)) {
			clear_trusted_flags(&flag);
			flag |= MEM_RCU;
		} else {
			flag = PTR_UNTRUSTED;
		}
	}
}

Because struct path is not listed in BTF_TYPE_SAFE_TRUSTED in
kernel/bpf/verifier.c:type_is_trusted(), walking path->dentry drops
PTR_TRUSTED (marking the register PTR_UNTRUSTED in sleepable lsm.s programs).
Since bpf_get_dentry_xattr(), bpf_set_dentry_xattr(), and
bpf_remove_dentry_xattr() require a trusted struct dentry * argument, the
verifier rejects the call with -EINVAL ("R1 must be referenced or trusted").

Does struct path need to be added to BTF_TYPE_SAFE_TRUSTED in
kernel/bpf/verifier.c so that path->dentry and path->mnt remain trusted when
accessed from LSM hooks?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=6

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 08/18] LSM: pass struct path and struct file to the inode fileattr hooks
  2026-10-27 12:11 ` [PATCH v3 -next 08/18] LSM: pass struct path and struct file to the inode fileattr hooks Cai Xinchen
@ 2026-10-27 13:45   ` sashiko-bot
  0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 13:45 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] ovl: bypass of underlying LSM checks in ovl_real_fileattr_get() and set
- [Medium] vfs: vfs_fileattr_get() runs LSM hook before checking i_op->fileattr_get
--

commit 2d42ade8215536e97143b2105a207c59391b323b
Author: Cai Xinchen <caixinchen1@huawei.com>

LSM: pass struct path and struct file to the inode fileattr hooks

This commit updates vfs_fileattr_get(), vfs_fileattr_set(), and the
inode_file_getattr and inode_file_setattr security hooks to accept a
const struct path and struct file. It also introduces unchecked
__vfs_fileattr_get() and __vfs_fileattr_set() helpers and switches
ecryptfs, overlayfs, and nfsd internal fileattr calls to use them.

> diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
> index fcb5b4de897e4..2b8daf21aea85 100644
> --- a/fs/ecryptfs/inode.c
> +++ b/fs/ecryptfs/inode.c
> @@ -1077,7 +1077,7 @@ static int ecryptfs_removexattr(struct dentry *dentry, struct inode *inode,
>  
>  static int ecryptfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
>  {
> -	return vfs_fileattr_get(ecryptfs_dentry_to_lower(dentry), fa);
> +	return __vfs_fileattr_get(ecryptfs_dentry_to_lower(dentry), fa);
>  }
>  
>  static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap,
> @@ -1086,7 +1086,7 @@ static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap,
>  	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry);
>  	int rc;
>  
> -	rc = vfs_fileattr_set(&nop_mnt_idmap, lower_dentry, fa);
> +	rc = __vfs_fileattr_set(&nop_mnt_idmap, lower_dentry, fa);
>  	fsstack_copy_attr_all(d_inode(dentry), d_inode(lower_dentry));
>  
>  	return rc;
> diff --git a/fs/file_attr.c b/fs/file_attr.c
> index 833e03810b24c..08b4cdbef9ffa 100644
> --- a/fs/file_attr.c
> +++ b/fs/file_attr.c
> @@ -75,27 +75,48 @@ void fileattr_fill_flags(struct file_kattr *fa, u32 flags)
>  EXPORT_SYMBOL(fileattr_fill_flags);
>  
>  /**
> - * vfs_fileattr_get - retrieve miscellaneous file attributes
> + * __vfs_fileattr_get - retrieve miscellaneous file attributes
>   * @dentry:	the object to retrieve from
>   * @fa:		fileattr pointer
>   *
> - * Call i_op->fileattr_get() callback, if exists.
> + * Call i_op->fileattr_get() callback, if exists.  Unlike
> + * vfs_fileattr_get(), no security hook is called: this is the variant to
> + * use for internal forwards, where the LSM checks were already performed
> + * on the user-facing path (e.g. by a stacking filesystem).
>   *
>   * Return: 0 on success, or a negative error on failure.
>   */
> -int vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
> +int __vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
>  {
>  	struct inode *inode = d_inode(dentry);
> -	int error;
>  
>  	if (!inode->i_op->fileattr_get)
>  		return -ENOIOCTLCMD;
>  
> -	error = security_inode_file_getattr(dentry, fa);
> +	return inode->i_op->fileattr_get(dentry, fa);
> +}
> +EXPORT_SYMBOL(__vfs_fileattr_get);
> +
> +/**
> + * vfs_fileattr_get - retrieve miscellaneous file attributes
> + * @path:	the object to retrieve from
> + * @fa:		fileattr pointer
> + * @file:	file through which the attributes are retrieved, if any
> + *
> + * Call i_op->fileattr_get() callback, if exists.
> + *
> + * Return: 0 on success, or a negative error on failure.
> + */
> +int vfs_fileattr_get(const struct path *path, struct file_kattr *fa,
> +		     struct file *file)
> +{
> +	int error;
> +
> +	error = security_inode_file_getattr(path, fa, file);
>  	if (error)
>  		return error;
>  
> -	return inode->i_op->fileattr_get(dentry, fa);
> +	return __vfs_fileattr_get(path->dentry, fa);
>  }

[Severity: Medium]
Should vfs_fileattr_get() check whether inode->i_op->fileattr_get is
implemented before calling security_inode_file_getattr()?

Previously, vfs_fileattr_get() checked !inode->i_op->fileattr_get and
returned -ENOIOCTLCMD before invoking security_inode_file_getattr(), matching
do_vfs_fileattr_set() in fs/file_attr.c:

    if (!inode->i_op->fileattr_set)
        return -ENOIOCTLCMD;

With the check moved into __vfs_fileattr_get(), calling vfs_fileattr_get() on
an inode whose filesystem does not implement i_op->fileattr_get (such as
tmpfs, procfs, sysfs, NFS, FUSE without fileattr_get, or special files) runs
security_inode_file_getattr() first.

If an LSM denies the hook, vfs_fileattr_get() returns -EACCES (and generates
audit denial records) instead of -ENOIOCTLCMD. Callers such as
do_vfs_ioctl() -> ioctl_getflags() / ioctl_fsgetxattr(), file_getattr(), and
ksmbd's smb2_get_info_filesystem(), ksmbd_vfs_get_compression(), and
__ksmbd_vfs_set_compression() rely on -ENOIOCTLCMD to fall back to
vfs_ioctl(), -EOPNOTSUPP, or default attributes:

fs/smb/server/smb2pdu.c:smb2_get_info_filesystem() {
    ...
    err = vfs_fileattr_get(&path, &fa, NULL);
    if (err && err != -ENOIOCTLCMD && err != -ENOTTY &&
        err != -EINVAL && err != -EOPNOTSUPP) {
        path_put(&path);
        return err;
    }
    ...
}

[ ... ]

> diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
> index eb408284df082..4f1d3077735bd 100644
> --- a/fs/overlayfs/inode.c
> +++ b/fs/overlayfs/inode.c
> @@ -608,47 +608,10 @@ static int ovl_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
>  		return realinode->i_op->fiemap(realinode, fieinfo, start, len);
>  }
>  
> -/*
> - * Work around the fact that security_file_ioctl() takes a file argument.
> - * Introducing security_inode_fileattr_get/set() hooks would solve this issue
> - * properly.
> - */
> -static int ovl_security_fileattr(const struct path *realpath, struct file_kattr *fa,
> -				 bool set)
> -{
> -	struct file *file;
> -	unsigned int cmd;
> -	int err;
> -	unsigned int flags;
> -
> -	flags = O_RDONLY;
> -	if (force_o_largefile())
> -		flags |= O_LARGEFILE;
> -
> -	file = dentry_open(realpath, flags, current_cred());
> -	if (IS_ERR(file))
> -		return PTR_ERR(file);
> -
> -	if (set)
> -		cmd = fa->fsx_valid ? FS_IOC_FSSETXATTR : FS_IOC_SETFLAGS;
> -	else
> -		cmd = fa->fsx_valid ? FS_IOC_FSGETXATTR : FS_IOC_GETFLAGS;
> -
> -	err = security_file_ioctl(file, cmd, 0);
> -	fput(file);
> -
> -	return err;
> -}
> -
>  int ovl_real_fileattr_set(const struct path *realpath, struct file_kattr *fa)
>  {
> -	int err;
> -
> -	err = ovl_security_fileattr(realpath, fa, true);
> -	if (err)
> -		return err;
> -
> -	return vfs_fileattr_set(mnt_idmap(realpath->mnt), realpath->dentry, fa);
> +	return __vfs_fileattr_set(mnt_idmap(realpath->mnt), realpath->dentry,
> +				  fa);
>  }
>  
>  int ovl_fileattr_set(const struct mnt_idmap *idmap,
> @@ -718,11 +681,7 @@ int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa)
>  {
>  	int err;
>  
> -	err = ovl_security_fileattr(realpath, fa, false);
> -	if (err)
> -		return err;
> -
> -	err = vfs_fileattr_get(realpath->dentry, fa);
> +	err = __vfs_fileattr_get(realpath->dentry, fa);
>  	if (err == -ENOIOCTLCMD)
>  		err = -ENOTTY;
>  	return err;

[Severity: High]
Won't removing ovl_security_fileattr() while also switching
ovl_real_fileattr_set() and ovl_real_fileattr_get() (as well as
ecryptfs_fileattr_get() and ecryptfs_fileattr_set() in fs/ecryptfs/inode.c)
to the unchecked __vfs_fileattr_set() and __vfs_fileattr_get() helpers bypass
mandatory access control checks on the underlying layer?

As described in Documentation/filesystems/overlayfs.rst ("Permission model"),
overlayfs performs two permission checks on each access:

 a) check if current task is allowed access based on local DAC (owner,
    group, mode and posix acl), as well as MAC checks

 b) check if stashed credentials would be allowed real operation on lower or
    upper layer based on underlying filesystem permissions, again including
    MAC checks

Other overlayfs metadata operations (such as ovl_do_notify_change(),
ovl_xattr_get(), ovl_do_setxattr(), ovl_do_removexattr(), ovl_get_acl_path(),
and ovl_do_set_acl()) invoke the hook-checked VFS helpers on the underlying
realpath under with_ovl_creds() to enforce the second check.

Commit 66dbfabf10d3 ("ovl: stack fileattr ops") added ovl_security_fileattr()
under the mounter's credentials to check the underlying realpath when
vfs_fileattr_get() and vfs_fileattr_set() lacked LSM hooks. Now that
vfs_fileattr_get() and vfs_fileattr_set() invoke the security hooks on a
struct path, using __vfs_fileattr_get() and __vfs_fileattr_set() in
ovl_real_fileattr_get() and ovl_real_fileattr_set() skips the second MAC
check on realpath altogether.

For example, when an unprivileged user namespace mounts overlayfs with a
context= mount option (or with override_creds) and calls path-based
file_getattr() or file_setattr() on an upper file, the first check only
evaluates the overlay mountpoint label, and __vfs_fileattr_get() /
__vfs_fileattr_set() bypass SELinux FILE__GETATTR and FILE__SETATTR checks on
the underlying inode. Similarly, in ecryptfs, the lower filesystem can have
per-file SELinux xattr labels distinct from the ecryptfs genfscon label, and
ecryptfs_setattr(), ecryptfs_setxattr(), and ecryptfs_set_acl() all call the
hook-checked VFS helpers on the lower dentry/path.

Should ovl_real_fileattr_get(), ovl_real_fileattr_set(),
ecryptfs_fileattr_get(), and ecryptfs_fileattr_set() call vfs_fileattr_get()
and vfs_fileattr_set() instead of the unchecked helpers?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=8

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 09/18] LSM: rename inode_getattr to path_getattr and pass struct file
  2026-10-27 12:11 ` [PATCH v3 -next 09/18] LSM: rename inode_getattr to path_getattr and pass struct file Cai Xinchen
@ 2026-10-27 14:01   ` sashiko-bot
  0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 14:01 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] bpf, lsm: missing __nullable on file arg in bpf_lsm_path_getattr()
- [Low] tomoyo: missing @file kernel-doc in tomoyo_path_getattr()
--

commit b5ecf79579b44f0d69e46ecb8c8c91e302299f23
Author: Cai Xinchen <caixinchen1@huawei.com>

LSM: rename inode_getattr to path_getattr and pass struct file

This commit renames the inode_getattr LSM hook and security_inode_getattr()
wrapper to path_getattr and security_path_getattr(), and adds a struct file
parameter passed by file descriptor-based stat operations while path-based
callers pass NULL. It also updates LSM implementations, BPF LSM/tracing BTF
IDs, and BPF selftests accordingly.

[ ... ]

> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 96ae1b548e8d4..0f7712f516444 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -153,7 +153,8 @@ LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap,
>  	 const struct path *path, struct iattr *attr, struct file *file)
>  LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap,
>  	 struct dentry *dentry, int ia_valid)
> -LSM_HOOK(int, 0, inode_getattr, const struct path *path)
> +LSM_HOOK(int, 0, path_getattr, const struct path *path,
> +	 struct file *file)

[Severity: High]
Can this trigger a NULL pointer dereference when a BPF LSM program attaches
to lsm/path_getattr or lsm.s/path_getattr?

In fs/stat.c, both vfs_getattr() and vfs_statx() -> vfs_statx_path() pass
NULL for the file argument when calling security_path_getattr():

    retval = security_path_getattr(path, NULL);

and:

    error = vfs_statx_path(&path, flags, stat, request_mask, NULL);

In kernel/bpf/bpf_lsm.c, LSM_HOOK() generates the weak hook stub
bpf_lsm_path_getattr(const struct path *path, struct file *file). Because
bpf_lsm_path_getattr is not in untrusted_lsm_hooks, btf_ctx_access() in
kernel/bpf/btf.c marks its pointer arguments as PTR_TO_BTF_ID | PTR_TRUSTED
without PTR_MAYBE_NULL unless the parameter name has a __nullable suffix:

    info->reg_type = PTR_TO_BTF_ID;
    if (prog_args_trusted(prog))
        info->reg_type |= PTR_TRUSTED;

    if (btf_param_match_suffix(btf, &args[arg], "__nullable"))
        info->reg_type |= PTR_MAYBE_NULL;

Without PTR_MAYBE_NULL on the file argument:

1. bpf_may_fault_on_deref(PTR_TO_BTF_ID | PTR_TRUSTED) returns false in
   include/linux/bpf_verifier.h, so direct loads from file (such as
   file->f_inode) are permitted without a NULL check and are not rewritten
   to BPF_PROBE_MEM by convert_ctx_accesses().

2. Because bpf_lsm_path_getattr is in sleepable_lsm_hooks, a sleepable
   lsm.s/path_getattr program can pass file without a NULL check to kfuncs
   expecting a non-NULL trusted struct file * (such as bpf_get_file_xattr()
   in fs/bpf_fs_kfuncs.c), which immediately dereference file.

Should bpf_lsm_path_getattr() have a strong override in
kernel/bpf/bpf_lsm_proto.c with struct file *file__nullable, similar to
bpf_lsm_mmap_file()?

[ ... ]

> diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c
> index c66e02ed8ee3b..c67455e060f74 100644
> --- a/security/tomoyo/tomoyo.c
> +++ b/security/tomoyo/tomoyo.c
> @@ -112,13 +112,14 @@ static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
>  }
>  
>  /**
> - * tomoyo_inode_getattr - Target for security_inode_getattr().
> + * tomoyo_path_getattr - Target for security_path_getattr().
>   *
>   * @path: Pointer to "struct path".
>   *
>   * Returns 0 on success, negative value otherwise.
>   */
> -static int tomoyo_inode_getattr(const struct path *path)
> +static int tomoyo_path_getattr(const struct path *path,
> +			       struct file *file)

[Severity: Low]
Does the kernel-doc comment for tomoyo_path_getattr() also need a @file
entry to avoid a kernel-doc warning under W=1 builds?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=9

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 15/18] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-10-27 12:11 ` [PATCH v3 -next 15/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
@ 2026-10-27 14:49   ` sashiko-bot
  0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 14:49 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] landlock: missing ATTR_KILL_PRIV check in hook_path_setattr()
- [Medium] landlock: hook_path_getattr() misses file check in io_statx() AT_EMPTY_PATH
- [Medium] landlock: unguarded second metadata hook check on ecryptfs lower_path
--

commit c5b94acf0094997d0231e58b264ebd0f262e2631
Author: Cai Xinchen <caixinchen1@huawei.com>

landlock: Add READ_METADATA and WRITE_METADATA access rights

This commit introduces the LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA filesystem access rights to control
reading and modifying file and directory metadata through path and file
LSM hooks, bumping the Landlock ABI version to 13.

> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 2ab67bcad1983..73c6353ecbf85 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c

[ ... ]

> @@ -1676,6 +1678,192 @@ static int hook_path_truncate(const struct path *const path)
>  	return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
>  }
>  
> +static int check_access_file(const struct file *const file,
> +			     const access_mask_t access)
> +{

[ ... ]

> +}
> +
> +static int check_access_path_or_file(const struct path *const path,
> +				     const struct file *const file,
> +				     const access_mask_t access)
> +{
> +	if (file)
> +		return check_access_file(file, access);
> +
> +	return current_check_access_path(path, access);
> +}

[Severity: Medium]
How does check_access_path_or_file() interact with eCryptfs when forwarding
metadata operations to the lower filesystem?

Unlike overlayfs, eCryptfs forwards operations under current_cred(). While
ecryptfs_getattr(), ecryptfs_getxattr(), ecryptfs_listxattr(),
ecryptfs_removexattr(), ecryptfs_fileattr_get(), and ecryptfs_fileattr_set()
call unchecked VFS helpers so the LSM hooks run only once on the upper path,
four operations in fs/ecryptfs/inode.c call LSM-checked VFS helpers on
lower_path with file set to NULL:

fs/ecryptfs/inode.c:ecryptfs_setattr()
	notify_change(&nop_mnt_idmap, &lower_path, &lower_ia, NULL, NULL);

fs/ecryptfs/inode.c:ecryptfs_setxattr()
	__vfs_setxattr_locked(&nop_mnt_idmap, &lower_path, name, value,
			      size, flags, NULL, NULL);

fs/ecryptfs/inode.c:ecryptfs_get_acl()
	vfs_get_acl(idmap, &lower_path, posix_acl_xattr_name(type), NULL);

fs/ecryptfs/inode.c:ecryptfs_set_acl()
	vfs_set_acl(&nop_mnt_idmap, &lower_path,
		    posix_acl_xattr_name(type), acl, NULL);

When a sandboxed task invokes fchmod(), fchown(), futimens(), fsetxattr(),
or POSIX ACL operations on an eCryptfs file descriptor opened before
landlock_restrict_self(), the upper check succeeds via check_access_file(),
but the internal forward reaches hook_path_setattr(), hook_path_setxattr(),
hook_path_get_acl(), or hook_path_set_acl() with file == NULL and evaluates
current_check_access_path(&lower_path, access) against the current domain.
Path-based chmod(), chown(), utimensat(), setxattr(), and ACL operations
allowed by a Landlock rule on the upper eCryptfs mount point also fail with
-EACCES unless the current domain additionally grants metadata rights on
the underlying lower path.

> +
> +static int hook_path_getattr(const struct path *const path,
> +			     struct file *const file)
> +{
> +	return check_access_path_or_file(path, file,
> +					 LANDLOCK_ACCESS_FS_READ_METADATA);
> +}

[Severity: Medium]
When IORING_OP_STATX is submitted with AT_EMPTY_PATH and an empty pathname
string on an open file descriptor, will hook_path_getattr() check the
current task's domain instead of the file's open-time access rights?

For the statx() syscall, sys_statx() uses filename_maybe_null, which turns
an empty string with AT_EMPTY_PATH into name == NULL and dispatches to
do_statx_fd() -> vfs_statx_fd(), passing the struct file to
vfs_statx_path() -> security_path_getattr().

In contrast, io_uring's io_statx_prep() calls delayed_getname_uflags() ->
do_getname() with LOOKUP_EMPTY, producing a non-NULL struct filename where
name->name[0] == '\0', and io_statx() then calls do_statx() -> vfs_statx():

io_uring/statx.c:io_statx()
	ret = do_statx(sx->dfd, name, sx->flags, sx->mask, sx->buffer);

fs/stat.c:vfs_statx()
	error = filename_lookup(dfd, filename, lookup_flags, &path, NULL);
	if (error)
		return error;
	error = vfs_statx_path(&path, flags, stat, request_mask, NULL);

Because file is passed as NULL to vfs_statx_path(), hook_path_getattr()
falls back to current_check_access_path() instead of check_access_file().
Wouldn't this allow a file descriptor opened without
LANDLOCK_ACCESS_FS_READ_METADATA to leak metadata when passed to an
unrestricted process that uses IORING_OP_STATX with AT_EMPTY_PATH, while
rejecting IORING_OP_STATX with AT_EMPTY_PATH in a sandboxed process using
a file descriptor opened before sandboxing?

> +
> +static int hook_path_file_getattr(const struct path *const path,
> +				  struct file_kattr *const fa,
> +				  struct file *const file)
> +{
> +	return check_access_path_or_file(path, file,
> +					 LANDLOCK_ACCESS_FS_READ_METADATA);
> +}
> +
> +static int hook_path_setattr(const struct mnt_idmap *const idmap,
> +			     const struct path *const path,
> +			     struct iattr *const attr,
> +			     struct file *const file)
> +{
> +	unsigned int ia_valid = attr->ia_valid;
> +
> +	/*
> +	 * Only explicit metadata changes are checked: implicit
> +	 * timestamp updates never go through notify_change(), and size
> +	 * changes (ATTR_SIZE) are governed by the truncate hooks
> +	 * instead.  See the "File metadata" section in the user space
> +	 * documentation for the full semantics.
> +	 *
> +	 * The setuid/setgid bit clearing that the kernel performs
> +	 * after write(2) or truncate(2) is an implicit change as
> +	 * well: it comes through __remove_privs() and do_truncate()
> +	 * with ATTR_FORCE set, and do_notify_change() then adds a
> +	 * synthesized ATTR_MODE (with these bits removed) before
> +	 * calling this hook.  As do_notify_change() makes it a bug for
> +	 * callers to pass ATTR_MODE together with a kill flag, an
> +	 * ATTR_MODE seen along with ATTR_FORCE is always
> +	 * kernel-synthesized and must not be checked.  An explicit
> +	 * chown(2) does not set ATTR_FORCE, so the same clearing
> +	 * requested through chown(2), including chown(2) with
> +	 * (-1, -1) on a privileged file, is still checked through the
> +	 * synthesized ATTR_MODE, matching the SELinux behavior.  A
> +	 * chown(2) that changes nothing (e.g. with (-1, -1) on a file
> +	 * without privilege bits) sets neither a changed attribute
> +	 * nor a synthesized ATTR_MODE, and is not checked.
> +	 */
> +	if (ia_valid & ATTR_FORCE)
> +		ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_MODE);
> +
> +	if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
> +			  ATTR_TIMES_SET | ATTR_TOUCH)))
> +		return 0;
> +
> +	return check_access_path_or_file(path, file,
> +					 LANDLOCK_ACCESS_FS_WRITE_METADATA);
> +}

[Severity: Medium]
What happens in hook_path_setattr() when chown(path, -1, -1) or
fchown(fd, -1, -1) is called on a file that has file capabilities
(security.capability) but neither S_ISUID nor S_ISGID set?

In chown_common(), when user == -1 and group == -1 on a non-directory,
newattrs.ia_valid is set to ATTR_CTIME | ATTR_KILL_SUID | ATTR_KILL_PRIV
(plus potentially ATTR_KILL_SGID) without ATTR_UID, ATTR_GID, or ATTR_FORCE:

fs/open.c:chown_common()
	if (!S_ISDIR(inode->i_mode))
		newattrs.ia_valid |= ATTR_KILL_SUID | ATTR_KILL_PRIV |
				     setattr_should_drop_sgid(idmap, inode);

In do_notify_change(), security_inode_need_killpriv() returns 1 when
security.capability is present, so ATTR_KILL_PRIV remains set in
attr->ia_valid, while ATTR_MODE is not synthesized because S_ISUID and
S_ISGID are not set:

fs/attr.c:do_notify_change()
	if (ia_valid & ATTR_KILL_PRIV) {
		error = security_inode_need_killpriv(dentry);
		if (error < 0)
			return error;
		if (error == 0)
			ia_valid = attr->ia_valid &= ~ATTR_KILL_PRIV;
	}

When do_notify_change() then calls security_path_setattr() ->
hook_path_setattr(), ia_valid only contains ATTR_CTIME | ATTR_KILL_PRIV,
so hook_path_setattr() returns 0 without checking
LANDLOCK_ACCESS_FS_WRITE_METADATA. Execution then proceeds to
setattr_prepare(), which strips the security.capability xattr via
security_inode_killpriv() -> cap_inode_killpriv() -> __vfs_removexattr()
and updates inode ctime.

Should hook_path_setattr() also clear ATTR_KILL_PRIV when ATTR_FORCE is
set (for implicit capability removal during write() or truncate()) and
include ATTR_KILL_PRIV in the checked ia_valid mask alongside ATTR_MODE,
ATTR_UID, ATTR_GID, ATTR_TIMES_SET, and ATTR_TOUCH?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=15

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks
  2026-10-27 13:02   ` sashiko-bot
@ 2026-10-27 15:01     ` Alexei Starovoitov
  2026-10-28 10:51       ` Cai Xinchen
  0 siblings, 1 reply; 28+ messages in thread
From: Alexei Starovoitov @ 2026-10-27 15:01 UTC (permalink / raw)
  To: sashiko-reviews, Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

On Tue Oct 27, 2026 at 1:02 PM UTC, sashiko-bot wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [Critical] bpf: missing __nullable on file arg in inode xattr LSM hooks allows NULL deref

bot is correct.
Please see commit 94e948b7e684 ("bpf: annotate file argument as __nullable in bpf_lsm_mmap_file")
and add to kernel/bpf/bpf_lsm_proto.c when args can be NULL.
That's how verifier knows.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 16/18] selftests/landlock: Add tests for metadata access rights
  2026-10-27 12:11 ` [PATCH v3 -next 16/18] selftests/landlock: Add tests for metadata " Cai Xinchen
@ 2026-10-27 15:11   ` sashiko-bot
  0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-10-27 15:11 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] selftests/landlock: hardcoded GID 0 in chown metadata tests fails for non-root
- [Low] selftests/landlock: duplicate linux/fs.h include before sys/mount.h
- [Low] selftests/landlock: missing MIPS offset in file_getattr/setattr fallback NRs
--

commit 76c1b6128ed1afd1dd4933dbcba43ffec623cd33
Author: Cai Xinchen <caixinchen1@huawei.com>

selftests/landlock: Add tests for metadata access rights

This commit adds selftests covering the LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights across chmod, chown,
utimensat, xattr, stat, fileattr, open-time semantics, and overlayfs. It also
updates the ABI version and access mask definitions in the Landlock test
suite.

> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index f725882fa4a78..1e819ba952ac6 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -12,6 +12,7 @@
>  #include <fcntl.h>
>  #include <libgen.h>
>  #include <linux/fiemap.h>
> +#include <linux/fs.h>
>  #include <linux/landlock.h>
>  #include <linux/magic.h>
>  #include <sched.h>

[Severity: Low]
Could adding this include of linux/fs.h before sys/mount.h break compilation
on toolchains where glibc headers conflict with UAPI linux/mount.h?

Further down in tools/testing/selftests/landlock/fs_test.c, linux/fs.h is
already included after sys/mount.h and sys/vfs.h with a comment noting the
ordering requirement:

    /*
     * Intentionally included last to work around header conflict.
     * See https://sourceware.org/glibc/wiki/Synchronizing_Headers.
     */
    #include <linux/fs.h>
    #include <linux/mount.h>

Because UAPI linux/fs.h includes linux/mount.h when __KERNEL__ is not
defined, including linux/fs.h at the top of the file pulls in linux/mount.h
prior to sys/mount.h and makes the deferred include a no-op.

[ ... ]

> @@ -10916,4 +10921,918 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant)

[ ... ]

> +TEST_F_FORK(layout1, unhandled_metadata)
> +{
> +	int file_fd;
> +	int ruleset_fd;
> +	const char *const file1 = file1_s1d1;
> +	const char *const file2 = file2_s1d1;
> +	const char *const dir1 = dir_s1d1;
> +	struct stat statbuf;
> +	const struct rule rules[] = {
> +		{
> +			.path = file1,
> +			.access = LANDLOCK_ACCESS_FS_WRITE_FILE,
> +		},
> +		{
> +			.path = file2,
> +			.access = LANDLOCK_ACCESS_FS_READ_FILE |
> +				  LANDLOCK_ACCESS_FS_WRITE_FILE,
> +		},
> +		{
> +			.path = dir1,
> +			.access = ACCESS_RW,
> +		},
> +		{},
> +	};
> +
> +	/*
> +	 * READ_METADATA and WRITE_METADATA are not handled, so metadata
> +	 * access should be allowed.
> +	 */
> +	ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
> +	ASSERT_LE(0, ruleset_fd);
> +	file_fd = open(file1, O_WRONLY | O_CLOEXEC);
> +	ASSERT_LE(0, file_fd);
> +
> +	enforce_ruleset(_metadata, ruleset_fd);
> +	ASSERT_EQ(0, close(ruleset_fd));
> +
> +	EXPECT_EQ(0, test_chmod(file1, 0644));
> +	EXPECT_EQ(0, test_fchmod(file_fd, 0644));
> +	EXPECT_EQ(0, test_chmod(file2, 0400));
> +	EXPECT_EQ(0, test_chmod(dir1, 0700));
> +
> +	EXPECT_EQ(0, test_chown(file1, -1, 0));
> +	EXPECT_EQ(0, test_fchown(file_fd, -1, 0));
> +	EXPECT_EQ(0, test_lchown(file1, -1, 0));

[Severity: Medium]
What happens in unhandled_metadata (as well as in write_metadata,
metadata_open_time, and metadata_transfer) when the test binary runs with a
non-zero GID or inside a user namespace where GID 0 is unmapped?

Landlock selftests support running as a non-root user with capabilities via
SECBIT_NOROOT in _init_caps(). During fixture setup,
FIXTURE_SETUP(layout1) -> prepare_layout_opt() -> disable_caps() clears
effective capabilities and drops CAP_CHOWN from the permitted set, creating
test files owned by geteuid():getegid().

When getegid() is non-zero and the caller is not a member of group 0,
chown_common() -> notify_change() -> setattr_prepare() -> chgrp_ok() rejects
changing the group to hardcoded GID 0 with -EPERM because CAP_CHOWN is not
held, causing the positive EXPECT_EQ(0, ...) assertions to fail. Similarly,
if GID 0 is unmapped in the current user namespace, chown_common() fails
setattr_vfsgid() with -EINVAL before reaching security_path_setattr(),
causing the EXPECT_EQ(EACCES, ...) checks to fail as well.

Would passing getegid() instead of 0 to test_chown(), test_fchown(), and
test_lchown() avoid depending on the caller's GID while still setting
ATTR_GID in chown_common() and exercising hook_path_setattr()?

[ ... ]

> +#ifndef __NR_file_getattr
> +#if defined(__alpha__)
> +#define __NR_file_getattr 578
> +#else
> +#define __NR_file_getattr 468
> +#endif
> +#endif
> +#ifndef __NR_file_setattr
> +#if defined(__alpha__)
> +#define __NR_file_setattr 579
> +#else
> +#define __NR_file_setattr 469
> +#endif
> +#endif

[Severity: Low]
When building against older UAPI headers on MIPS, won't these fallback
definitions for __NR_file_getattr and __NR_file_setattr evaluate to raw 468
and 469 without the MIPS ABI base offset (__NR_Linux + 468 and
__NR_Linux + 469, where __NR_Linux is 4000 for o32, 5000 for n64, and 6000
for n32), causing test_file_getattr() and test_file_setattr() to fail with
ENOSYS?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261027121151.1887650-1-caixinchen1@huawei.com?part=16

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks
  2026-10-27 15:01     ` Alexei Starovoitov
@ 2026-10-28 10:51       ` Cai Xinchen
  0 siblings, 0 replies; 28+ messages in thread
From: Cai Xinchen @ 2026-10-28 10:51 UTC (permalink / raw)
  To: Alexei Starovoitov, sashiko-reviews
  Cc: bpf, linux-security-module, linux-trace-kernel, selinux, dsterba,
	Chuck Lever, Jeff Layton, Anna Schumaker

Thanks for the pointer. I will change in the next version.

The other issue the bot reported on this patch is real as well:😭
walking path->dentry in a BPF LSM program drops PTR_TRUSTED.
I will add BTF_TYPE_SAFE_TRUSTED for path in next version

On 10/27/2026 11:01 PM, Alexei Starovoitov wrote:
> On Tue Oct 27, 2026 at 1:02 PM UTC, sashiko-bot wrote:
>> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
>> - [Critical] bpf: missing __nullable on file arg in inode xattr LSM hooks allows NULL deref
> bot is correct.
> Please see commit 94e948b7e684 ("bpf: annotate file argument as __nullable in bpf_lsm_mmap_file")
> and add to kernel/bpf/bpf_lsm_proto.c when args can be NULL.
> That's how verifier knows.*2*2

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2026-10-28 10:51 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 12:11 [PATCH v3 -next 00/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 01/18] fs: pass struct path to notify_change() Cai Xinchen
2026-10-27 12:07   ` sashiko-bot
2026-10-27 12:11 ` [PATCH v3 -next 02/18] LSM: pass struct path to the inode_setsecctx hook Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 03/18] fs: pass struct path to xattr helpers Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 04/18] fs: pass struct path to POSIX ACL helpers Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 05/18] LSM: pass struct path and struct file to the inode_setattr hook Cai Xinchen
2026-10-27 12:48   ` sashiko-bot
2026-10-27 12:11 ` [PATCH v3 -next 06/18] LSM: pass struct path and struct file to the inode xattr hooks Cai Xinchen
2026-10-27 13:02   ` sashiko-bot
2026-10-27 15:01     ` Alexei Starovoitov
2026-10-28 10:51       ` Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 07/18] LSM: pass struct path and struct file to the inode posix acl hooks Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 08/18] LSM: pass struct path and struct file to the inode fileattr hooks Cai Xinchen
2026-10-27 13:45   ` sashiko-bot
2026-10-27 12:11 ` [PATCH v3 -next 09/18] LSM: rename inode_getattr to path_getattr and pass struct file Cai Xinchen
2026-10-27 14:01   ` sashiko-bot
2026-10-27 12:11 ` [PATCH v3 -next 10/18] LSM: rename inode_setattr to path_setattr Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 11/18] LSM: rename the inode xattr hooks to path_xattr hooks Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 12/18] LSM: rename the inode posix acl hooks to path_*_acl hooks Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 13/18] LSM: rename the inode fileattr hooks to path_file_* hooks Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 14/18] LSM: rename inode_setsecctx to path_setsecctx Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 15/18] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
2026-10-27 14:49   ` sashiko-bot
2026-10-27 12:11 ` [PATCH v3 -next 16/18] selftests/landlock: Add tests for metadata " Cai Xinchen
2026-10-27 15:11   ` sashiko-bot
2026-10-27 12:11 ` [PATCH v3 -next 17/18] samples/landlock: Add metadata rights to sandboxer Cai Xinchen
2026-10-27 12:11 ` [PATCH v3 -next 18/18] Documentation: Update landlock doc for metadata rights Cai Xinchen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox