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