mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
@ 2026-09-24 10:48 Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 01/12] fs: pass struct path to notify_change() Cai Xinchen
                   ` (14 more replies)
  0 siblings, 15 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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 and POSIX
ACLs.  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_getattr, inode_setattr, inode_setxattr,
inode_getxattr, inode_listxattr, inode_removexattr, inode_set_acl,
inode_get_acl, inode_remove_acl) only receive the dentry of the accessed
object.  Patches 1-7 therefore first pass struct path instead of dentry
through the metadata-related VFS helpers and LSM hooks.  This is a pure
refactoring with no behavior change, split so that every patch builds
and works 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
  6: inode xattr hooks
  7: inode POSIX ACL hooks

Two deliberate scoping decisions for this refactor:

- The hooks consistently take struct path rather than struct file.  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.

- 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.

Patches 8-12 then implement the new rights, their tests, the sandboxer
sample and the documentation.  Semantics:

- READ_METADATA covers stat(2) and friends, getxattr(2) and friends,
  listxattr(2) and friends, and POSIX ACL reads.
- WRITE_METADATA covers chmod(2), chown(2), utimensat(2), setxattr(2),
  removexattr(2) and friends, and POSIX ACL set and remove.
- Only explicit metadata changes requested by user space are restricted.
  Implicit changes performed by the kernel (e.g. timestamp updates on
  write(2), size changes on 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.
- 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.

The Landlock ABI version is incremented from 11 to 12.

The series is based on linux-next commit 5c4d4169604b ("Add linux-next
specific files for 20260921").

Testing: each patch has been built for aarch64 (gcc, -Werror) and the
landlock selftests (445 tests, including the new ones) pass in QEMU on
aarch64; base_test reports ABI v12.

[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/

Assisted-by: opencode: glm-5.3

Cai Xinchen (12):
  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 to the inode_setattr hook
  LSM: pass struct path to the inode xattr hooks
  LSM: pass struct path to the inode posix acl hooks
  landlock: Add READ_METADATA and WRITE_METADATA access rights
  landlock: Implement metadata access hooks
  selftests/landlock: Add tests for metadata access rights
  samples/landlock: Add metadata rights to sandboxer
  Documentation: Update landlock doc for metadata rights

 Documentation/userspace-api/landlock.rst     |  11 +-
 drivers/base/devtmpfs.c                      |   6 +-
 drivers/block/zloop.c                        |   4 +-
 fs/attr.c                                    |  20 +-
 fs/cachefiles/interface.c                    |   6 +-
 fs/cachefiles/xattr.c                        |  32 +-
 fs/coredump.c                                |   2 +-
 fs/ecryptfs/inode.c                          |  34 +-
 fs/exfat/file.c                              |   3 +-
 fs/fat/file.c                                |   3 +-
 fs/inode.c                                   |   7 +-
 fs/internal.h                                |  17 +-
 fs/namei.c                                   |   7 +-
 fs/nfsd/nfs4ctl.h                            |   4 +-
 fs/nfsd/nfs4state.c                          |  14 +-
 fs/nfsd/nfs4xdr.c                            |   2 +-
 fs/nfsd/state.h                              |   2 +-
 fs/nfsd/vfs.c                                |  73 +++--
 fs/open.c                                    |  18 +-
 fs/overlayfs/copy_up.c                       |   4 +-
 fs/overlayfs/inode.c                         |   4 +-
 fs/overlayfs/overlayfs.h                     |  39 ++-
 fs/overlayfs/xattrs.c                        |  13 +-
 fs/posix_acl.c                               |  46 +--
 fs/smb/server/smb2pdu.c                      |  77 ++---
 fs/smb/server/smb_common.c                   |   2 -
 fs/smb/server/smbacl.c                       |  21 +-
 fs/smb/server/tests/smbacl_kunit.c           |   6 +-
 fs/smb/server/vfs.c                          | 111 +++----
 fs/smb/server/vfs.h                          |  39 +--
 fs/smb/server/vfs_cache.c                    |   3 +-
 fs/utimes.c                                  |   3 +-
 fs/xattr.c                                   |  96 +++---
 include/linux/fs.h                           |   6 +-
 include/linux/landlock.h                     |   4 +-
 include/linux/lsm_hook_defs.h                |  29 +-
 include/linux/posix_acl.h                    |  21 +-
 include/linux/security.h                     |  65 ++--
 include/linux/xattr.h                        |  22 +-
 include/uapi/linux/landlock.h                |  26 +-
 samples/landlock/sandboxer.c                 |  17 +-
 security/commoncap.c                         |  22 +-
 security/integrity/evm/evm_crypto.c          |   8 +-
 security/integrity/evm/evm_main.c            |  36 ++-
 security/integrity/ima/ima_appraise.c        |  17 +-
 security/landlock/fs.c                       |  86 ++++++
 security/landlock/limits.h                   |   2 +-
 security/landlock/syscalls.c                 |   2 +-
 security/security.c                          |  99 +++---
 security/selinux/hooks.c                     |  49 +--
 security/smack/smack_lsm.c                   |  62 ++--
 tools/testing/selftests/landlock/base_test.c |   2 +-
 tools/testing/selftests/landlock/fs_test.c   | 309 ++++++++++++++++++-
 53 files changed, 999 insertions(+), 614 deletions(-)

-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 01/12] fs: pass struct path to notify_change()
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 02/12] LSM: pass struct path to the inode_setsecctx hook Cai Xinchen
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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 and derive the idmap and dentry from it.

This is a purely mechanical change with no behavior change.  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.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 drivers/base/devtmpfs.c   |  6 ++++--
 fs/attr.c                 | 18 +++++++++---------
 fs/cachefiles/interface.c |  6 ++----
 fs/coredump.c             |  2 +-
 fs/ecryptfs/inode.c       | 14 +++++++-------
 fs/inode.c                |  7 +++----
 fs/namei.c                |  7 +++----
 fs/nfsd/nfs4state.c       | 14 ++++++++------
 fs/nfsd/nfs4xdr.c         |  2 +-
 fs/nfsd/state.h           |  2 +-
 fs/nfsd/vfs.c             | 14 ++++++++++----
 fs/open.c                 | 18 +++++++++---------
 fs/overlayfs/overlayfs.h  |  7 ++++++-
 fs/smb/server/smb2pdu.c   |  2 +-
 fs/smb/server/smbacl.c    |  2 +-
 fs/utimes.c               |  3 +--
 include/linux/fs.h        |  6 +++---
 17 files changed, 70 insertions(+), 60 deletions(-)

diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c
index 11c70888f38b..71da9235ed49 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(&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(&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 71888ac903c2..ea91e4471faa 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -399,8 +399,7 @@ 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
+ * @path:	object affected
  * @attr:	new attributes
  * @delegated_inode: returns inode, if the inode is delegated
  *
@@ -418,15 +417,16 @@ EXPORT_SYMBOL(may_setattr);
  * 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.
+ * If the inode has been found through an idmapped mount, permission
+ * checking is performed based on the idmap of @path->mnt. This function
+ * will take care to map the inode according to the idmap before
+ * checking permissions.
  */
-int notify_change(struct mnt_idmap *idmap, struct dentry *dentry,
-		  struct iattr *attr, struct delegated_inode *delegated_inode)
+int notify_change(const struct path *path, struct iattr *attr,
+		  struct delegated_inode *delegated_inode)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	umode_t mode = inode->i_mode;
 	int error;
diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c
index a160d5c3e74c..bbaba93015cd 100644
--- a/fs/cachefiles/interface.c
+++ b/fs/cachefiles/interface.c
@@ -138,8 +138,7 @@ static int cachefiles_adjust_size(struct cachefiles_object *object)
 		newattrs.ia_size = oi_size & PAGE_MASK;
 		ret = cachefiles_inject_remove_error();
 		if (ret == 0)
-			ret = notify_change(&nop_mnt_idmap, file->f_path.dentry,
-					    &newattrs, NULL);
+			ret = notify_change(&file->f_path, &newattrs, NULL);
 		if (ret < 0)
 			goto truncate_failed;
 	}
@@ -148,8 +147,7 @@ static int cachefiles_adjust_size(struct cachefiles_object *object)
 	newattrs.ia_size = ni_size;
 	ret = cachefiles_inject_write_error();
 	if (ret == 0)
-		ret = notify_change(&nop_mnt_idmap, file->f_path.dentry,
-				    &newattrs, NULL);
+		ret = notify_change(&file->f_path, &newattrs, NULL);
 
 truncate_failed:
 	inode_unlock(file_inode(file));
diff --git a/fs/coredump.c b/fs/coredump.c
index f809e3fb4ba2..2e9903da7011 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1049,7 +1049,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(&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 525297c7ebd8..627d456f9c38 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(&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(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,14 @@ static int ecryptfs_setattr(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(&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 8cfbc11c2b1f..e1e9eb268803 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2356,8 +2356,7 @@ int dentry_needs_remove_privs(struct mnt_idmap *idmap,
 	return mask;
 }
 
-static int __remove_privs(struct mnt_idmap *idmap,
-			  struct dentry *dentry, int kill)
+static int __remove_privs(const struct path *path, int kill)
 {
 	struct iattr newattrs;
 
@@ -2366,7 +2365,7 @@ static int __remove_privs(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(path, &newattrs, NULL);
 }
 
 static int file_remove_privs_flags(struct file *file, unsigned int flags)
@@ -2387,7 +2386,7 @@ 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->f_path, kill);
 	}
 
 	if (!error)
diff --git a/fs/namei.c b/fs/namei.c
index 1dd4ec7d0e62..909da4078eb7 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4321,7 +4321,7 @@ static int may_open(struct mnt_idmap *idmap, const struct path *path,
 	return 0;
 }
 
-static int handle_truncate(struct mnt_idmap *idmap, struct file *filp)
+static int handle_truncate(struct file *filp)
 {
 	const struct path *path = &filp->f_path;
 	struct inode *inode = path->dentry->d_inode;
@@ -4331,8 +4331,7 @@ static int handle_truncate(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,
+		error = do_truncate(path, 0, ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
 				    filp);
 	}
 	put_write_access(inode);
@@ -4914,7 +4913,7 @@ static int do_open(struct nameidata *nd,
 	if (!error)
 		error = security_file_post_open(file, op->acc_mode);
 	if (!error && do_truncate)
-		error = handle_truncate(idmap, file);
+		error = handle_truncate(file);
 	if (unlikely(error > 0)) {
 		WARN_ON(1);
 		error = -EINVAL;
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 1de6c6d757c3..400a42941879 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(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(&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 00ddaac499c6..73bb099ec0f0 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -4096,7 +4096,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 4789f2ec2078..ed8e04cc794e 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,7 @@ 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(path, &size_attr, NULL);
 		if (host_err)
 			return host_err;
 		iap->ia_valid &= ~ATTR_SIZE;
@@ -501,7 +501,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(path, iap, NULL);
 }
 
 /**
@@ -524,6 +524,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 +570,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 +618,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/open.c b/fs/open.c
index 6069abe49664..41eb76798a23 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -36,10 +36,12 @@
 
 #include "internal.h"
 
-int do_truncate(struct mnt_idmap *idmap, struct dentry *dentry,
-		loff_t length, unsigned int time_attrs, struct file *filp)
+int do_truncate(const struct path *path, loff_t length,
+		unsigned int time_attrs, struct file *filp)
 {
 	int ret;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct iattr newattrs;
 
 	/* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */
@@ -65,7 +67,7 @@ int do_truncate(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(path, &newattrs, NULL);
 	inode_unlock(dentry->d_inode);
 	return ret;
 }
@@ -115,7 +117,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(path, length, 0, NULL);
 
 put_write_and_out:
 	put_write_access(inode);
@@ -191,7 +193,7 @@ 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,
+		return do_truncate(&file->f_path, length,
 				   ATTR_MTIME | ATTR_CTIME, file);
 }
 
@@ -683,8 +685,7 @@ 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(path, &newattrs, &delegated_inode);
 out_unlock:
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
@@ -822,8 +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,
-				      &delegated_inode);
+		error = notify_change(path, &newattrs, &delegated_inode);
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
 		error = break_deleg_wait(&delegated_inode);
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index e0d8c6152e9f..d915b87c0b06 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -206,7 +206,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(&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 15e2801a8ce1..fb3ee170db3c 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -8243,7 +8243,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(&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 e75247915c87..fcace71b0a29 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1721,7 +1721,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(path, &newattrs, NULL);
 	inode_unlock(inode);
 	if (rc)
 		goto out;
diff --git a/fs/utimes.c b/fs/utimes.c
index e22664e4115f..5de794587147 100644
--- a/fs/utimes.c
+++ b/fs/utimes.c
@@ -64,8 +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,
-			      &delegated_inode);
+	error = notify_change(path, &newattrs, &delegated_inode);
 	inode_unlock(inode);
 	if (is_delegated(&delegated_inode)) {
 		error = break_deleg_wait(&delegated_inode);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 05d05bb9c951..4087c45d88d5 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2483,7 +2483,7 @@ static inline bool is_idmapped_mnt(const struct vfsmount *mnt)
 }
 
 int vfs_truncate(const struct path *, loff_t);
-int do_truncate(struct mnt_idmap *, struct dentry *, loff_t start,
+int do_truncate(const struct path *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);
@@ -2707,8 +2707,8 @@ static inline int bmap(struct inode *inode,  sector_t *block)
 }
 #endif
 
-int notify_change(struct mnt_idmap *, struct dentry *,
-		  struct iattr *, struct delegated_inode *);
+int notify_change(const struct path *path, struct iattr *attr,
+		  struct delegated_inode *delegated_inode);
 int inode_permission(struct mnt_idmap *, struct inode *, int);
 int generic_permission(struct mnt_idmap *, struct inode *, int);
 static inline int file_permission(struct file *file, int mask)
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 02/12] LSM: pass struct path to the inode_setsecctx hook
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 01/12] fs: pass struct path to notify_change() Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 03/12] fs: pass struct path to xattr helpers Cai Xinchen
                   ` (12 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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.

Assisted-by: opencode: glm-5.3
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           | 6 +++---
 security/selinux/hooks.c      | 6 +++---
 security/smack/smack_lsm.c    | 6 +++---
 6 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index ed8e04cc794e..922337083fe9 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -625,7 +625,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 f4939137e66b..b22f7928523e 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -314,7 +314,7 @@ 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)
+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 e3c8b4479a0a..6ac9fb634017 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -589,7 +589,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,
@@ -1637,7 +1637,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 bf0ec31f1047..de3ed5c18802 100644
--- a/security/security.c
+++ b/security/security.c
@@ -4134,7 +4134,7 @@ EXPORT_SYMBOL(security_inode_notifysecctx);
 
 /**
  * security_inode_setsecctx() - Change the security label of an inode
- * @dentry: inode
+ * @path: inode
  * @ctx: secctx
  * @ctxlen: length of secctx
  *
@@ -4148,9 +4148,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 f2394cf6646e..8af024fe4fb5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7166,10 +7166,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 4439821a5e57..d9e9f4a52567 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	[flat|nested] 20+ messages in thread

* [PATCH RFC -next 03/12] fs: pass struct path to xattr helpers
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 01/12] fs: pass struct path to notify_change() Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 02/12] LSM: pass struct path to the inode_setsecctx hook Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 11:12   ` Amir Goldstein
  2026-09-24 10:48 ` [PATCH RFC -next 04/12] fs: pass struct path to POSIX ACL helpers Cai Xinchen
                   ` (11 subsequent siblings)
  14 siblings, 1 reply; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

vfs_setxattr(), vfs_getxattr(), vfs_listxattr() and vfs_removexattr()
along with their __vfs_setxattr_locked()/__vfs_removexattr_locked()
counterparts and the do_setxattr()/do_getxattr()/listxattr()/
removexattr() syscall helpers take a struct mnt_idmap and a struct
dentry even though 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 them all to take a struct path instead
and derive the idmap and dentry from it where needed.

__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 now pass the path they got from the
previous patch straight through.

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.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 drivers/block/zloop.c               |   4 +-
 fs/cachefiles/xattr.c               |  32 ++++----
 fs/ecryptfs/inode.c                 |   8 +-
 fs/nfsd/nfs4ctl.h                   |   4 +-
 fs/nfsd/vfs.c                       |  57 +++++++-------
 fs/overlayfs/copy_up.c              |   4 +-
 fs/overlayfs/overlayfs.h            |  18 +++--
 fs/overlayfs/xattrs.c               |  13 +++-
 fs/smb/server/smb2pdu.c             |  75 +++++++------------
 fs/smb/server/smb_common.c          |   2 -
 fs/smb/server/smbacl.c              |  19 +++--
 fs/smb/server/tests/smbacl_kunit.c  |   6 +-
 fs/smb/server/vfs.c                 | 112 ++++++++++++----------------
 fs/smb/server/vfs.h                 |  39 ++++------
 fs/smb/server/vfs_cache.c           |   3 +-
 fs/xattr.c                          |  92 ++++++++++++-----------
 include/linux/xattr.h               |  22 +++---
 security/integrity/evm/evm_crypto.c |   8 +-
 security/selinux/hooks.c            |   4 +-
 security/smack/smack_lsm.c          |   4 +-
 20 files changed, 243 insertions(+), 283 deletions(-)

diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index f0ca221524db..d77b114d69fc 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -710,7 +710,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->f_path,
 				"user.zloop.wp", &zone->wp, sizeof(zone->wp), 0);
 		if (ret) {
 			pr_err("%pg: failed to record write pointer (%d)\n",
@@ -1395,7 +1395,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->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 c70bf67e52b0..5a0b50bcfb73 100644
--- a/fs/cachefiles/xattr.c
+++ b/fs/cachefiles/xattr.c
@@ -40,14 +40,12 @@ struct cachefiles_vol_xattr {
 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;
 	int ret;
 
 	if (!file)
 		return -ESTALE;
-	dentry = file->f_path.dentry;
 
 	_enter("%x,#%d", object->debug_id, len);
 
@@ -69,7 +67,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(&file->f_path,
 					   cachefiles_xattr_cache, buf,
 					   sizeof(struct cachefiles_xattr) + len, 0);
 			mnt_drop_write_file(file);
@@ -102,7 +100,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;
 	unsigned int len = object->cookie->aux_len, tlen;
 	const void *p = fscache_get_aux(object->cookie);
 	enum cachefiles_coherency_trace why;
@@ -117,7 +114,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, dentry, cachefiles_xattr_cache, buf, tlen);
+		xlen = vfs_getxattr(&file->f_path, cachefiles_xattr_cache, buf, tlen);
 	if (xlen != tlen) {
 		if (xlen < 0) {
 			ret = xlen;
@@ -161,14 +158,14 @@ 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;
 
 	ret = cachefiles_inject_remove_error();
 	if (ret == 0) {
 		ret = mnt_want_write(cache->mnt);
 		if (ret == 0) {
-			ret = vfs_removexattr(&nop_mnt_idmap, dentry,
-					      cachefiles_xattr_cache);
+			ret = vfs_removexattr(&path, cachefiles_xattr_cache);
 			mnt_drop_write(cache->mnt);
 		}
 	}
@@ -214,7 +211,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);
@@ -230,22 +228,21 @@ 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,
-					   cachefiles_xattr_cache,
+			ret = vfs_setxattr(&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);
 	}
 
@@ -260,9 +257,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;
@@ -276,11 +274,11 @@ 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(&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(
@@ -297,7 +295,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 627d456f9c38..3307cf13b5b4 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -991,17 +991,17 @@ 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(&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 922337083fe9..a3b86fa60dca 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))
 		/*
@@ -684,14 +686,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;
@@ -699,8 +701,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,
-			 NULL, 0) <= 0)
+	if (vfs_getxattr(path, NFSD_JUNCTION_XATTR_NAME, NULL, 0) <= 0)
 		return 0;
 	return 1;
 }
@@ -2619,19 +2620,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(&path, name, NULL, 0);
 
 	/*
 	 * Zero-length attribute, just return.
@@ -2658,7 +2660,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(&path, name, buf, len);
 	if (len <= 0) {
 		kvfree(buf);
 		buf = NULL;
@@ -2692,19 +2694,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;
@@ -2721,7 +2724,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);
@@ -2754,6 +2757,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)
@@ -2763,16 +2767,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(&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;
@@ -2784,6 +2789,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)
@@ -2792,16 +2798,17 @@ 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(&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 d915b87c0b06..c13f6b2c915c 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -291,8 +291,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(path, name, value, size);
 	len = (value && err > 0) ? err : 0;
 
 	pr_debug("getxattr(%pd2, \"%s\", \"%*pE\", %zu, 0) = %i\n",
@@ -325,9 +324,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,
-			       value, size, flags);
+	int err = vfs_setxattr(&path, name, value, size, flags);
 
 	pr_debug("setxattr(%pd2, \"%s\", \"%*pE\", %zu, %d) = %i\n",
 		 dentry, name, min((int)size, 48), value, size, flags, err);
@@ -344,7 +347,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(&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 5ae44b9c8790..b4658324b9f3 100644
--- a/fs/overlayfs/xattrs.c
+++ b/fs/overlayfs/xattrs.c
@@ -45,7 +45,7 @@ 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(&realpath, name, NULL, 0);
 		if (err < 0)
 			goto out;
 	}
@@ -86,7 +86,7 @@ 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(&realpath, name, value, size);
 }
 
 static bool ovl_can_list(struct super_block *sb, const char *s)
@@ -105,15 +105,20 @@ 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;
 
+	if (ovl_dentry_upper(dentry))
+		ovl_path_upper(dentry, &realpath);
+	else
+		ovl_path_lower(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 fb3ee170db3c..b5db5f280701 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3293,7 +3293,6 @@ static bool smb2_is_private_ea(const char *name, size_t name_len)
 static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 		       const struct path *path, bool get_write)
 {
-	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	char *attr_name = NULL, *value;
 	int rc = 0;
 	unsigned int next = 0;
@@ -3333,16 +3332,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(path,
 						     attr_name,
 						     XATTR_USER_PREFIX_LEN +
 						     eabuf->EaNameLength);
 
 			/* delete the EA only when it exits */
 			if (rc > 0) {
-				rc = ksmbd_vfs_remove_xattr(idmap,
-							    path,
+				rc = ksmbd_vfs_remove_xattr(path,
 							    attr_name,
 							    get_write);
 
@@ -3357,7 +3354,7 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 			/* if the EA doesn't exist, just do nothing. */
 			rc = 0;
 		} else {
-			rc = ksmbd_vfs_setxattr(idmap, path, attr_name, value,
+			rc = ksmbd_vfs_setxattr(path, attr_name, value,
 						le16_to_cpu(eabuf->EaValueLength),
 						0, get_write);
 			if (rc < 0) {
@@ -3394,7 +3391,6 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
 					       struct ksmbd_file *fp,
 					       char *stream_name, int s_type)
 {
-	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	size_t xattr_stream_size;
 	char *xattr_stream_name;
 	int rc;
@@ -3410,8 +3406,7 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
 	fp->stream.size = xattr_stream_size;
 
 	/* Check if there is stream prefix in xattr space */
-	rc = ksmbd_vfs_casexattr_len(idmap,
-				     path->dentry,
+	rc = ksmbd_vfs_casexattr_len(path,
 				     xattr_stream_name,
 				     xattr_stream_size);
 	if (rc >= 0)
@@ -3438,7 +3433,7 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
 				0x00, 0x00, 0x01, 0x00,	/* version */
 				[15] = 0x80,		/* backup time */
 			};
-			rc = ksmbd_vfs_setxattr(idmap, path, xattr_stream_name,
+			rc = ksmbd_vfs_setxattr(path, xattr_stream_name,
 						(void *)afpinfo_empty,
 						sizeof(afpinfo_empty), 0, false);
 			return rc < 0 ? rc : 0;
@@ -3447,7 +3442,7 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
 		return -EBADF;
 	}
 
-	rc = ksmbd_vfs_setxattr(idmap, path, xattr_stream_name, NULL, 0, 0, false);
+	rc = ksmbd_vfs_setxattr(path, xattr_stream_name, NULL, 0, 0, false);
 	if (rc < 0)
 		pr_err("Failed to store XATTR stream name :%d\n", rc);
 	return 0;
@@ -3462,8 +3457,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,
+	ssize_t slen = ksmbd_vfs_casexattr_len(&fp->filp->f_path,
 					       fp->stream.name,
 					       fp->stream.size);
 	return slen < 0 ? 0 : (loff_t)slen;
@@ -3471,12 +3465,11 @@ static loff_t ksmbd_stream_eof(struct ksmbd_file *fp)
 
 static int smb2_remove_smb_xattrs(const struct path *path)
 {
-	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	char *name, *xattr_list = NULL;
 	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) {
@@ -3491,8 +3484,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(path, name, true);
 			if (err)
 				ksmbd_debug(SMB, "remove xattr failed : %s\n",
 					    name);
@@ -3538,7 +3530,7 @@ static void smb2_new_xattrs(struct ksmbd_tree_connect *tcon, const struct path *
 	da.flags = XATTR_DOSINFO_ATTRIB | XATTR_DOSINFO_CREATE_TIME |
 		XATTR_DOSINFO_ITIME;
 
-	rc = ksmbd_vfs_set_dos_attrib_xattr(mnt_idmap(path->mnt), path, &da, true);
+	rc = ksmbd_vfs_set_dos_attrib_xattr(path, &da, true);
 	if (rc)
 		ksmbd_debug(SMB, "failed to store file attribute into xattr\n");
 }
@@ -3547,6 +3539,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;
@@ -3558,7 +3551,7 @@ 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(&parent_path, &da);
 	if (rc > 0 && da.attr & FILE_ATTRIBUTE_COMPRESSED)
 		compressed = true;
 
@@ -3578,8 +3571,7 @@ static void smb2_update_xattrs(struct ksmbd_tree_connect *tcon,
 	fp->f_ci->m_fattr &= ~(FILE_ATTRIBUTE_HIDDEN_LE | FILE_ATTRIBUTE_SYSTEM_LE);
 
 	/* get FileAttributes from XATTR_NAME_DOS_ATTRIBUTE */
-	rc = ksmbd_vfs_get_dos_attrib_xattr(mnt_idmap(path->mnt),
-					    path->dentry, &da);
+	rc = ksmbd_vfs_get_dos_attrib_xattr(path, &da);
 	if (rc > 0) {
 		if (store_dos_attrs) {
 			fp->f_ci->m_fattr = cpu_to_le32(da.attr);
@@ -4524,8 +4516,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 &&
+			    ksmbd_vfs_get_dos_attrib_xattr(&path, &da) > 0 &&
 			    da.attr & FILE_ATTRIBUTE_READONLY) {
 				rsp->hdr.Status = STATUS_CANNOT_DELETE;
 				rc = -EACCES;
@@ -4863,7 +4854,6 @@ int smb2_open(struct ksmbd_work *work)
 					}
 
 					rc = ksmbd_vfs_set_sd_xattr(conn,
-								    idmap,
 								    &path,
 								    pntsd,
 								    pntsd_size,
@@ -5886,7 +5876,6 @@ static int process_query_dir_entries(struct smb2_query_dir_private *priv)
 		ksmbd_kstat.kstat = &kstat;
 		if (priv->info_level != FILE_NAMES_INFORMATION) {
 			rc = ksmbd_vfs_fill_dentry_attrs(priv->work,
-							 idmap,
 							 dent,
 							 &ksmbd_kstat);
 			if (rc) {
@@ -6428,7 +6417,6 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 	ssize_t buf_free_len, alignment_bytes, next_offset, rsp_data_cnt = 0;
 	struct smb2_ea_info_req *ea_req = NULL;
 	const struct path *path;
-	struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
 
 	if (!(fp->daccess & FILE_READ_EA_LE)) {
 		pr_err("Not permitted to read ext attr : 0x%x\n",
@@ -6465,7 +6453,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;
@@ -6511,8 +6499,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(path, name, &buf);
 		if (value_len <= 0) {
 			rc = -ENOENT;
 			rsp->hdr.Status = STATUS_INVALID_HANDLE;
@@ -6802,7 +6789,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;
@@ -6905,7 +6892,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) {
@@ -6950,8 +6937,7 @@ static int get_file_stream_info(struct ksmbd_work *work,
 		streamlen *= 2;
 		kfree(stream_buf);
 		file_info->StreamNameLength = cpu_to_le32(streamlen);
-		slen = ksmbd_vfs_xattr_len(file_mnt_idmap(fp->filp),
-					   path->dentry, stream_name);
+		slen = ksmbd_vfs_xattr_len(path, stream_name);
 		ssize = slen < 0 ? 0 : (loff_t)slen;
 		file_info->StreamSize = cpu_to_le64(ssize);
 		file_info->StreamAllocationSize = cpu_to_le64(ssize);
@@ -7689,8 +7675,8 @@ 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,
+		ppntsd_size = ksmbd_vfs_get_sd_xattr(work->conn,
+						     &fp->filp->f_path,
 						     &ppntsd);
 
 	/* Check if sd buffer size exceeds response buffer size */
@@ -8051,8 +8037,7 @@ static int smb2_rename(struct ksmbd_work *work,
 				goto out;
 			}
 
-			stream_buf_len = ksmbd_vfs_getcasexattr(file_mnt_idmap(fp->filp),
-								fp->filp->f_path.dentry,
+			stream_buf_len = ksmbd_vfs_getcasexattr(&fp->filp->f_path,
 								fp->stream.name,
 								fp->stream.size,
 								&stream_buf);
@@ -8063,8 +8048,7 @@ static int smb2_rename(struct ksmbd_work *work,
 			}
 		}
 
-		rc = ksmbd_vfs_setxattr(file_mnt_idmap(fp->filp),
-					&fp->filp->f_path,
+		rc = ksmbd_vfs_setxattr(&fp->filp->f_path,
 					xattr_stream_name,
 					stream_buf, stream_buf_len, 0, true);
 		kfree(stream_buf);
@@ -8171,7 +8155,6 @@ static int set_file_basic_info(struct ksmbd_file *fp,
 	struct iattr attrs;
 	struct file *filp;
 	struct inode *inode;
-	struct mnt_idmap *idmap;
 	__le32 attrs_mask = FILE_ATTRIBUTE_DIRECTORY_LE |
 		FILE_ATTRIBUTE_COMPRESSED_LE;
 	int rc = 0;
@@ -8182,7 +8165,6 @@ static int set_file_basic_info(struct ksmbd_file *fp,
 	attrs.ia_valid = 0;
 	filp = fp->filp;
 	inode = file_inode(filp);
-	idmap = file_mnt_idmap(filp);
 
 	if (file_info->CreationTime)
 		fp->create_time = le64_to_cpu(file_info->CreationTime);
@@ -8227,7 +8209,7 @@ static int set_file_basic_info(struct ksmbd_file *fp,
 		da.flags = XATTR_DOSINFO_ATTRIB | XATTR_DOSINFO_CREATE_TIME |
 			XATTR_DOSINFO_ITIME;
 
-		rc = ksmbd_vfs_set_dos_attrib_xattr(idmap, &filp->f_path, &da,
+		rc = ksmbd_vfs_set_dos_attrib_xattr(&filp->f_path, &da,
 				true);
 		if (rc)
 			ksmbd_debug(SMB,
@@ -10780,7 +10762,6 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 				   struct file_sparse *sparse)
 {
 	struct ksmbd_file *fp;
-	struct mnt_idmap *idmap;
 	int ret = 0;
 	__le32 old_fattr;
 
@@ -10804,8 +10785,6 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 		goto out;
 	}
 
-	idmap = file_mnt_idmap(fp->filp);
-
 	old_fattr = fp->f_ci->m_fattr;
 	if (!sparse->SetSparse &&
 	    (old_fattr & FILE_ATTRIBUTE_SPARSE_FILE_LE)) {
@@ -10823,8 +10802,7 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 		const struct cred *saved_cred;
 		struct xattr_dos_attrib da = {0};
 
-		ret = ksmbd_vfs_get_dos_attrib_xattr(idmap,
-						     fp->filp->f_path.dentry, &da);
+		ret = ksmbd_vfs_get_dos_attrib_xattr(&fp->filp->f_path, &da);
 		if (ret <= 0) {
 			da.version = 4;
 			da.itime = fp->itime;
@@ -10836,8 +10814,7 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id,
 		da.attr = le32_to_cpu(fp->f_ci->m_fattr);
 		da.flags |= XATTR_DOSINFO_ATTRIB;
 		saved_cred = override_creds(fp->filp->f_cred);
-		ret = ksmbd_vfs_set_dos_attrib_xattr(idmap,
-						     &fp->filp->f_path,
+		ret = ksmbd_vfs_set_dos_attrib_xattr(&fp->filp->f_path,
 						     &da, true);
 		revert_creds(saved_cred);
 		if (ret)
diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c
index 086a1b85e5f4..2cc8e24dcd6c 100644
--- a/fs/smb/server/smb_common.c
+++ b/fs/smb/server/smb_common.c
@@ -467,7 +467,6 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
 {
 	int i, rc = 0;
 	struct ksmbd_conn *conn = work->conn;
-	struct mnt_idmap *idmap = file_mnt_idmap(dir->filp);
 
 	for (i = 0; i < 2; i++) {
 		struct kstat kstat;
@@ -493,7 +492,6 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level,
 
 			ksmbd_kstat.kstat = &kstat;
 			rc = ksmbd_vfs_fill_dentry_attrs(work,
-							 idmap,
 							 dentry,
 							 &ksmbd_kstat);
 			if (rc)
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index fcace71b0a29..f81e06918cfb 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -1199,8 +1199,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 mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct path parent_path = { .mnt = path->mnt,
+				    .dentry = path->dentry->d_parent };
 	int inherited_flags = 0, flags = 0, i, nt_size = 0, pdacl_size;
 	int rc = 0, pntsd_type, ppntsd_size, acl_len, aces_size;
 	unsigned int dacloffset;
@@ -1209,8 +1209,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, &parent_path,
+					    &parent_pntsd);
 	if (ppntsd_size <= 0)
 		return -ENOENT;
 
@@ -1427,7 +1427,7 @@ int smb_inherit_dacl(struct ksmbd_conn *conn,
 			pntsd_size += sizeof(struct smb_acl) + nt_size;
 		}
 
-		ksmbd_vfs_set_sd_xattr(conn, idmap, path, pntsd, pntsd_size, false);
+		ksmbd_vfs_set_sd_xattr(conn, path, pntsd, pntsd_size, false);
 		kfree(pntsd);
 	}
 
@@ -1476,8 +1476,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, path, &pntsd);
 	if (pntsd_size <= 0 || !pntsd)
 		goto err_out;
 
@@ -1701,7 +1700,7 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
 	newattrs.ia_valid |= ATTR_MODE;
 	newattrs.ia_mode = (inode->i_mode & ~0777) | (fattr.cf_mode & 0777);
 
-	ksmbd_vfs_remove_acl_xattrs(idmap, path);
+	ksmbd_vfs_remove_acl_xattrs(path);
 	/* Update posix acls */
 	if (IS_ENABLED(CONFIG_FS_POSIX_ACL) && fattr.cf_dacls) {
 		rc = set_posix_acl(idmap, path->dentry,
@@ -1732,8 +1731,8 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon,
 
 	if (test_share_config_flag(tcon->share_conf, KSMBD_SHARE_FLAG_ACL_XATTR)) {
 		/* Update WinACL in xattr */
-		ksmbd_vfs_remove_sd_xattrs(idmap, path);
-		ksmbd_vfs_set_sd_xattr(conn, idmap, path, pntsd, ntsd_len,
+		ksmbd_vfs_remove_sd_xattrs(path);
+		ksmbd_vfs_set_sd_xattr(conn, path, pntsd, ntsd_len,
 				get_write);
 	}
 
diff --git a/fs/smb/server/tests/smbacl_kunit.c b/fs/smb/server/tests/smbacl_kunit.c
index 33496b4d31a3..0e918b72f94f 100644
--- a/fs/smb/server/tests/smbacl_kunit.c
+++ b/fs/smb/server/tests/smbacl_kunit.c
@@ -221,8 +221,7 @@ static void ksmbd_smb_check_perm_dacl_boundary_test(struct kunit *test)
 				mk_vma_flags(VMA_NORESERVE_BIT));
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, file);
 
-	rc = ksmbd_vfs_set_sd_xattr(NULL, mnt_idmap(file->f_path.mnt),
-				    &file->f_path, pntsd, ntsd_size,
+	rc = ksmbd_vfs_set_sd_xattr(NULL, &file->f_path, pntsd, ntsd_size,
 				    false);
 	KUNIT_EXPECT_EQ(test, 0, rc);
 	if (rc)
@@ -263,8 +262,7 @@ ksmbd_smb_check_perm_dacl_maximal_boundary_test(struct kunit *test)
 				mk_vma_flags(VMA_NORESERVE_BIT));
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, file);
 
-	rc = ksmbd_vfs_set_sd_xattr(NULL, mnt_idmap(file->f_path.mnt),
-				    &file->f_path, pntsd, ntsd_size,
+	rc = ksmbd_vfs_set_sd_xattr(NULL, &file->f_path, pntsd, ntsd_size,
 				    false);
 	KUNIT_EXPECT_EQ(test, 0, rc);
 	if (rc)
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index db0f2de2bab3..eb904cf9ef2d 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -217,14 +217,14 @@ int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode)
 	return err;
 }
 
-ssize_t ksmbd_vfs_getcasexattr(struct mnt_idmap *idmap,
-			       struct dentry *dentry, char *attr_name,
+ssize_t ksmbd_vfs_getcasexattr(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;
 
@@ -234,8 +234,7 @@ ssize_t ksmbd_vfs_getcasexattr(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(path,
 					       name,
 					       attr_value);
 		if (value_len < 0)
@@ -259,8 +258,7 @@ static int ksmbd_vfs_stream_read(struct ksmbd_file *fp, char *buf, loff_t *pos,
 		    *pos, count);
 
 	saved_cred = override_creds(fp->filp->f_cred);
-	v_len = ksmbd_vfs_getcasexattr(file_mnt_idmap(fp->filp),
-				       fp->filp->f_path.dentry,
+	v_len = ksmbd_vfs_getcasexattr(&fp->filp->f_path,
 				       fp->stream.name,
 				       fp->stream.size,
 				       &stream_buf);
@@ -387,7 +385,6 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 {
 	const struct cred *saved_cred;
 	char *stream_buf = NULL, *wbuf;
-	struct mnt_idmap *idmap = file_mnt_idmap(fp->filp);
 	size_t size;
 	ssize_t v_len;
 	int err = 0;
@@ -407,8 +404,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
 	}
 
 	saved_cred = override_creds(fp->filp->f_cred);
-	v_len = ksmbd_vfs_getcasexattr(idmap,
-				       fp->filp->f_path.dentry,
+	v_len = ksmbd_vfs_getcasexattr(&fp->filp->f_path,
 				       fp->stream.name,
 				       fp->stream.size,
 				       &stream_buf);
@@ -433,8 +429,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(&fp->filp->f_path,
 				 fp->stream.name,
 				 (void *)stream_buf,
 				 size,
@@ -813,17 +808,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;
 
@@ -832,7 +827,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);
@@ -842,30 +837,27 @@ ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list)
 	return size;
 }
 
-ssize_t ksmbd_vfs_xattr_len(struct mnt_idmap *idmap,
-			    struct dentry *dentry, char *xattr_name)
+ssize_t ksmbd_vfs_xattr_len(const struct path *path, char *xattr_name)
 {
-	return vfs_getxattr(idmap, dentry, xattr_name, NULL, 0);
+	return vfs_getxattr(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(struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+ssize_t ksmbd_vfs_getxattr(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(path, xattr_name);
 	if (xattr_len < 0)
 		return xattr_len;
 
@@ -873,8 +865,7 @@ ssize_t ksmbd_vfs_getxattr(struct mnt_idmap *idmap,
 	if (!buf)
 		return -ENOMEM;
 
-	xattr_len = vfs_getxattr(idmap, dentry, xattr_name,
-				 (void *)buf, xattr_len);
+	xattr_len = vfs_getxattr(path, xattr_name, (void *)buf, xattr_len);
 	if (xattr_len > 0)
 		*xattr_buf = buf;
 	else
@@ -884,7 +875,6 @@ ssize_t ksmbd_vfs_getxattr(struct mnt_idmap *idmap,
 
 /**
  * ksmbd_vfs_setxattr() - vfs helper for smb set extended attributes value
- * @idmap:	idmap of the relevant mount
  * @path:	path of dentry to set XATTR at
  * @attr_name:	xattr name for setxattr
  * @attr_value:	xattr value to set
@@ -894,8 +884,7 @@ ssize_t ksmbd_vfs_getxattr(struct mnt_idmap *idmap,
  *
  * Return:	0 on success, otherwise error
  */
-int ksmbd_vfs_setxattr(struct mnt_idmap *idmap,
-		       const struct path *path, const char *attr_name,
+int ksmbd_vfs_setxattr(const struct path *path, const char *attr_name,
 		       void *attr_value, size_t attr_size, int flags,
 		       bool get_write)
 {
@@ -907,8 +896,7 @@ int ksmbd_vfs_setxattr(struct mnt_idmap *idmap,
 			return err;
 	}
 
-	err = vfs_setxattr(idmap,
-			   path->dentry,
+	err = vfs_setxattr(path,
 			   attr_name,
 			   attr_value,
 			   attr_size,
@@ -1178,8 +1166,7 @@ int ksmbd_vfs_query_allocated_ranges(struct ksmbd_file *fp, loff_t start,
 	return ret;
 }
 
-int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
-			   const struct path *path, char *attr_name,
+int ksmbd_vfs_remove_xattr(const struct path *path, char *attr_name,
 			   bool get_write)
 {
 	int err;
@@ -1190,7 +1177,7 @@ int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
 			return err;
 	}
 
-	err = vfs_removexattr(idmap, path->dentry, attr_name);
+	err = vfs_removexattr(path, attr_name);
 
 	if (get_write == true)
 		mnt_drop_write(path->mnt);
@@ -1472,14 +1459,13 @@ struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
 	return dent;
 }
 
-int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap,
-				const struct path *path)
+int ksmbd_vfs_remove_acl_xattrs(const struct path *path)
 {
 	char *name, *xattr_list = NULL;
 	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) {
@@ -1499,7 +1485,8 @@ int ksmbd_vfs_remove_acl_xattrs(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(mnt_idmap(path->mnt),
+					     path->dentry, name);
 			if (err)
 				ksmbd_debug(SMB,
 					    "remove acl xattr failed : %s\n", name);
@@ -1512,13 +1499,13 @@ int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap,
 	return err;
 }
 
-int ksmbd_vfs_remove_sd_xattrs(struct mnt_idmap *idmap, const struct path *path)
+int ksmbd_vfs_remove_sd_xattrs(const struct path *path)
 {
 	char *name, *xattr_list = NULL;
 	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) {
@@ -1531,7 +1518,7 @@ int ksmbd_vfs_remove_sd_xattrs(struct mnt_idmap *idmap, const struct path *path)
 		ksmbd_debug(SMB, "%s, len %zd\n", name, strlen(name));
 
 		if (!strncmp(name, XATTR_NAME_SD, XATTR_NAME_SD_LEN)) {
-			err = ksmbd_vfs_remove_xattr(idmap, path, name, true);
+			err = ksmbd_vfs_remove_xattr(path, name, true);
 			if (err)
 				ksmbd_debug(SMB, "remove xattr failed : %s\n", name);
 		}
@@ -1607,7 +1594,6 @@ static struct xattr_smb_acl *ksmbd_vfs_make_xattr_posix_acl(struct mnt_idmap *id
 }
 
 int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
-			   struct mnt_idmap *idmap,
 			   const struct path *path,
 			   struct smb_ntsd *pntsd, int len,
 			   bool get_write)
@@ -1618,6 +1604,7 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
 	struct xattr_smb_acl *smb_acl, *def_smb_acl = NULL;
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 
 	acl.version = 4;
 	acl.hash_type = XATTR_SD_HASH_TYPE_SHA256;
@@ -1659,7 +1646,7 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
 		goto out;
 	}
 
-	rc = ksmbd_vfs_setxattr(idmap, path,
+	rc = ksmbd_vfs_setxattr(path,
 				XATTR_NAME_SD, sd_ndr.data,
 				sd_ndr.offset, 0, get_write);
 	if (rc < 0)
@@ -1675,19 +1662,19 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
 EXPORT_SYMBOL_IF_KUNIT(ksmbd_vfs_set_sd_xattr);
 
 int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
-			   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;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	__u8 cmp_hash[XATTR_SD_HASH_SIZE] = {0};
 
-	rc = ksmbd_vfs_getxattr(idmap, dentry, XATTR_NAME_SD, &n.data);
+	rc = ksmbd_vfs_getxattr(path, XATTR_NAME_SD, &n.data);
 	if (rc <= 0)
 		return rc;
 
@@ -1744,8 +1731,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
 	return rc;
 }
 
-int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap,
-				   const struct path *path,
+int ksmbd_vfs_set_dos_attrib_xattr(const struct path *path,
 				   struct xattr_dos_attrib *da,
 				   bool get_write)
 {
@@ -1756,7 +1742,7 @@ int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap,
 	if (err)
 		goto out;
 
-	err = ksmbd_vfs_setxattr(idmap, path, XATTR_NAME_DOS_ATTRIBUTE,
+	err = ksmbd_vfs_setxattr(path, XATTR_NAME_DOS_ATTRIBUTE,
 				 (void *)n.data, n.offset, 0, get_write);
 	if (err)
 		ksmbd_debug(SMB, "failed to store dos attribute in xattr\n");
@@ -1766,14 +1752,13 @@ int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap,
 	return err;
 }
 
-int ksmbd_vfs_get_dos_attrib_xattr(struct mnt_idmap *idmap,
-				   struct dentry *dentry,
+int ksmbd_vfs_get_dos_attrib_xattr(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(path, XATTR_NAME_DOS_ATTRIBUTE,
 				 (char **)&n.data);
 	if (err > 0) {
 		n.length = err;
@@ -1822,7 +1807,6 @@ void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat)
 }
 
 int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
-				struct mnt_idmap *idmap,
 				struct dentry *dentry,
 				struct ksmbd_kstat *ksmbd_kstat)
 {
@@ -1856,7 +1840,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(&path, &da);
 		if (rc > 0) {
 			ksmbd_kstat->file_attributes = cpu_to_le32(da.attr);
 			ksmbd_kstat->create_time = da.create_time;
@@ -1879,7 +1863,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;
@@ -1897,14 +1881,14 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 	return 0;
 }
 
-ssize_t ksmbd_vfs_casexattr_len(struct mnt_idmap *idmap,
-				struct dentry *dentry, char *attr_name,
+ssize_t ksmbd_vfs_casexattr_len(const struct path *path,
+				char *attr_name,
 				int attr_name_len)
 {
 	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;
 
@@ -1914,7 +1898,7 @@ ssize_t ksmbd_vfs_casexattr_len(struct mnt_idmap *idmap,
 		if (strncasecmp(attr_name, name, attr_name_len))
 			continue;
 
-		value_len = ksmbd_vfs_xattr_len(idmap, dentry, name);
+		value_len = ksmbd_vfs_xattr_len(path, name);
 		break;
 	}
 
@@ -2067,8 +2051,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);
 		revert_creds(saved_cred);
 		if (src_file_size < 0)
@@ -2386,7 +2369,7 @@ 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(&fp->filp->f_path, &da);
 		if (rc <= 0) {
 			da.version = 4;
 			da.itime = fp->itime;
@@ -2397,8 +2380,7 @@ static int __ksmbd_vfs_set_compression(struct ksmbd_work *work,
 
 		da.attr = le32_to_cpu(fp->f_ci->m_fattr);
 		da.flags |= XATTR_DOSINFO_ATTRIB;
-		rc = ksmbd_vfs_set_dos_attrib_xattr(idmap,
-						    &fp->filp->f_path,
+		rc = ksmbd_vfs_set_dos_attrib_xattr(&fp->filp->f_path,
 						    &da, true);
 		if (rc)
 			rc = 0;
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 566c670c90be..a0cf77a7a2c7 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -103,27 +103,24 @@ 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_getxattr(struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+ssize_t ksmbd_vfs_listxattr(const struct path *path, char **list);
+ssize_t ksmbd_vfs_getxattr(const struct path *path,
 			   char *xattr_name,
 			   char **xattr_buf);
-ssize_t ksmbd_vfs_xattr_len(struct mnt_idmap *idmap,
-			    struct dentry *dentry, char *xattr_name);
-ssize_t ksmbd_vfs_getcasexattr(struct mnt_idmap *idmap,
-			       struct dentry *dentry, char *attr_name,
+ssize_t ksmbd_vfs_xattr_len(const struct path *path,
+			    char *xattr_name);
+ssize_t ksmbd_vfs_getcasexattr(const struct path *path,
+			       char *attr_name,
 			       int attr_name_len, char **attr_value);
-ssize_t ksmbd_vfs_casexattr_len(struct mnt_idmap *idmap,
-				struct dentry *dentry, char *attr_name,
+ssize_t ksmbd_vfs_casexattr_len(const struct path *path,
+				char *attr_name,
 				int attr_name_len);
-int ksmbd_vfs_setxattr(struct mnt_idmap *idmap,
-		       const struct path *path, const char *attr_name,
+int ksmbd_vfs_setxattr(const struct path *path, const char *attr_name,
 		       void *attr_value, size_t attr_size, int flags,
 		       bool get_write);
 int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name,
 				size_t *xattr_stream_name_size, int s_type);
-int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
-			   const struct path *path, char *attr_name,
+int ksmbd_vfs_remove_xattr(const struct path *path, char *attr_name,
 			   bool get_write);
 int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
 			unsigned int flags,
@@ -152,29 +149,23 @@ int ksmbd_vfs_query_allocated_ranges(struct ksmbd_file *fp, loff_t start,
 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,
-				struct mnt_idmap *idmap,
 				struct dentry *dentry,
 				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);
-int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap,
-				const struct path *path);
-int ksmbd_vfs_remove_sd_xattrs(struct mnt_idmap *idmap, const struct path *path);
+int ksmbd_vfs_remove_acl_xattrs(const struct path *path);
+int ksmbd_vfs_remove_sd_xattrs(const struct path *path);
 int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
-			   struct mnt_idmap *idmap,
 			   const struct path *path,
 			   struct smb_ntsd *pntsd, int len,
 			   bool get_write);
 int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
-			   struct mnt_idmap *idmap,
-			   struct dentry *dentry,
+			   const struct path *path,
 			   struct smb_ntsd **pntsd);
-int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap,
-				   const struct path *path,
+int ksmbd_vfs_set_dos_attrib_xattr(const struct path *path,
 				   struct xattr_dos_attrib *da,
 				   bool get_write);
-int ksmbd_vfs_get_dos_attrib_xattr(struct mnt_idmap *idmap,
-				   struct dentry *dentry,
+int ksmbd_vfs_get_dos_attrib_xattr(const struct path *path,
 				   struct xattr_dos_attrib *da);
 int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap,
 				 const struct path *path);
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index a96b764c4db5..b5700c6f096f 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -538,8 +538,7 @@ static void __ksmbd_inode_close(struct ksmbd_file *fp)
 			const struct cred *saved_cred;
 
 			saved_cred = override_creds(filp->f_cred);
-			err = ksmbd_vfs_remove_xattr(file_mnt_idmap(filp),
-						     &filp->f_path,
+			err = ksmbd_vfs_remove_xattr(&filp->f_path,
 						     fp->stream.name,
 						     true);
 			revert_creds(saved_cred);
diff --git a/fs/xattr.c b/fs/xattr.c
index d58979115200..818d6652b6d9 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -285,8 +285,7 @@ int __vfs_setxattr_noperm(struct mnt_idmap *idmap,
  * __vfs_setxattr_locked - set an extended attribute while holding the inode
  * 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 +294,12 @@ int __vfs_setxattr_noperm(struct mnt_idmap *idmap,
  *  a delegation was broken on, NULL if none.
  */
 int
-__vfs_setxattr_locked(struct mnt_idmap *idmap, struct dentry *dentry,
-		      const char *name, const void *value, size_t size,
-		      int flags, struct delegated_inode *delegated_inode)
+__vfs_setxattr_locked(const struct path *path, const char *name,
+		      const void *value, size_t size, int flags,
+		      struct delegated_inode *delegated_inode)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 
@@ -324,9 +325,11 @@ __vfs_setxattr_locked(struct mnt_idmap *idmap, struct dentry *dentry,
 EXPORT_SYMBOL_GPL(__vfs_setxattr_locked);
 
 int
-vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
-	     const char *name, const void *value, size_t size, int flags)
+vfs_setxattr(const struct path *path, const char *name, const void *value,
+	     size_t size, int flags)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	struct delegated_inode delegated_inode = { };
 	const void  *orig_value = value;
@@ -341,7 +344,7 @@ vfs_setxattr(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(path, name, value, size,
 				      flags, &delegated_inode);
 	inode_unlock(inode);
 
@@ -448,9 +451,11 @@ __vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name,
 EXPORT_SYMBOL(__vfs_getxattr);
 
 ssize_t
-vfs_getxattr(struct mnt_idmap *idmap, struct dentry *dentry,
-	     const char *name, void *value, size_t size)
+vfs_getxattr(const struct path *path, const char *name, void *value,
+	     size_t size)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 
@@ -482,12 +487,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 +508,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;
 
@@ -550,17 +556,17 @@ EXPORT_SYMBOL(__vfs_removexattr);
  * __vfs_removexattr_locked - set an extended attribute while holding the inode
  * 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(struct mnt_idmap *idmap,
-			 struct dentry *dentry, const char *name,
+__vfs_removexattr_locked(const struct path *path, const char *name,
 			 struct delegated_inode *delegated_inode)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 
@@ -589,17 +595,16 @@ __vfs_removexattr_locked(struct mnt_idmap *idmap,
 EXPORT_SYMBOL_GPL(__vfs_removexattr_locked);
 
 int
-vfs_removexattr(struct mnt_idmap *idmap, struct dentry *dentry,
-		const char *name)
+vfs_removexattr(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(path, name, &delegated_inode);
 	inode_unlock(inode);
 
 	if (is_delegated(&delegated_inode)) {
@@ -652,14 +657,13 @@ int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx)
 	return error;
 }
 
-static int do_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
-		struct kernel_xattr_ctx *ctx)
+static int do_setxattr(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(mnt_idmap(path->mnt), path->dentry,
+				  ctx->kname->name, ctx->kvalue, ctx->size);
 
-	return vfs_setxattr(idmap, dentry, ctx->kname->name,
+	return vfs_setxattr(path, ctx->kname->name,
 			ctx->kvalue, ctx->size, ctx->flags);
 }
 
@@ -669,7 +673,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(&f->f_path, ctx);
 		mnt_drop_write_file(f);
 	}
 	return error;
@@ -687,7 +691,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(&path, ctx);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
@@ -787,8 +791,7 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name,
  * Extended attribute GET operations
  */
 static ssize_t
-do_getxattr(struct mnt_idmap *idmap, struct dentry *d,
-	struct kernel_xattr_ctx *ctx)
+do_getxattr(const struct path *path, struct kernel_xattr_ctx *ctx)
 {
 	ssize_t error;
 	char *kname = ctx->kname->name;
@@ -803,9 +806,10 @@ do_getxattr(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(mnt_idmap(path->mnt), path->dentry,
+				   kname, kvalue, ctx->size);
 	else
-		error = vfs_getxattr(idmap, d, kname, kvalue, ctx->size);
+		error = vfs_getxattr(path, kname, kvalue, ctx->size);
 	if (error > 0) {
 		if (ctx->size && copy_to_user(ctx->value, kvalue, error))
 			error = -EFAULT;
@@ -822,7 +826,7 @@ do_getxattr(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(&f->f_path, ctx);
 }
 
 ssize_t filename_getxattr(int dfd, struct filename *filename,
@@ -834,7 +838,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(&path, ctx);
 	path_put(&path);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -925,7 +929,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 +942,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 +961,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 +975,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 +1033,12 @@ SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size)
  * Extended attribute REMOVE operations
  */
 static long
-removexattr(struct mnt_idmap *idmap, struct dentry *d, const char *name)
+removexattr(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(mnt_idmap(path->mnt), path->dentry,
+				      name);
+	return vfs_removexattr(path, name);
 }
 
 static int file_removexattr(struct file *f, struct xattr_name *kname)
@@ -1042,8 +1047,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(&f->f_path, kname->name);
 		mnt_drop_write_file(f);
 	}
 	return error;
@@ -1061,7 +1065,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(&path, kname->name);
 		mnt_drop_write(path.mnt);
 	}
 	path_put(&path);
diff --git a/include/linux/xattr.h b/include/linux/xattr.h
index 54ac3cbc133f..85042618fd81 100644
--- a/include/linux/xattr.h
+++ b/include/linux/xattr.h
@@ -77,22 +77,22 @@ struct xattr {
 };
 
 ssize_t __vfs_getxattr(struct dentry *, struct inode *, const char *, void *, size_t);
-ssize_t vfs_getxattr(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 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(struct mnt_idmap *, struct dentry *, struct inode *,
 		   const char *, const void *, size_t, int);
 int __vfs_setxattr_noperm(struct mnt_idmap *, struct dentry *,
 			  const char *, const void *, size_t, int);
-int __vfs_setxattr_locked(struct mnt_idmap *, struct dentry *,
-			  const char *, const void *, size_t, int,
-			  struct delegated_inode *);
-int vfs_setxattr(struct mnt_idmap *, struct dentry *, const char *,
-		 const void *, size_t, int);
+int __vfs_setxattr_locked(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 path *path, const char *name,
+		 const void *value, size_t size, int flags);
 int __vfs_removexattr(struct mnt_idmap *, struct dentry *, const char *);
-int __vfs_removexattr_locked(struct mnt_idmap *, struct dentry *,
-			     const char *, struct delegated_inode *);
-int vfs_removexattr(struct mnt_idmap *, struct dentry *, const char *);
+int __vfs_removexattr_locked(const struct path *path, const char *name,
+			     struct delegated_inode *delegated_inode);
+int vfs_removexattr(const struct path *path, const char *name);
 
 ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size);
 int vfs_getxattr_alloc(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 8af024fe4fb5..1d06b08f3c41 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7168,8 +7168,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(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 d9e9f4a52567..e517809c230d 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(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	[flat|nested] 20+ messages in thread

* [PATCH RFC -next 04/12] fs: pass struct path to POSIX ACL helpers
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (2 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 03/12] fs: pass struct path to xattr helpers Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 05/12] LSM: pass struct path to the inode_setattr hook Cai Xinchen
                   ` (10 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

vfs_set_acl(), vfs_get_acl() and vfs_remove_acl() together with the
do_set_acl()/do_get_acl() syscall helpers take a struct mnt_idmap and
a struct dentry even though their callers either already hold a
struct path or have to pass &nop_mnt_idmap because no idmapped mount
is involved.  Switch them all to take a struct path instead and
derive the idmap and dentry from it where needed.

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.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/ecryptfs/inode.c       | 12 ++++++------
 fs/internal.h             | 17 ++++++++---------
 fs/overlayfs/inode.c      |  4 ++--
 fs/overlayfs/overlayfs.h  | 14 ++++++++++++--
 fs/posix_acl.c            | 40 ++++++++++++++++++++-------------------
 fs/smb/server/vfs.c       |  3 +--
 fs/xattr.c                | 10 ++++------
 include/linux/posix_acl.h | 21 +++++++++-----------
 8 files changed, 63 insertions(+), 58 deletions(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 3307cf13b5b4..626341ef1a99 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -1093,8 +1093,9 @@ static int ecryptfs_fileattr_set(struct mnt_idmap *idmap,
 static struct posix_acl *ecryptfs_get_acl(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(&lower_path, posix_acl_xattr_name(type));
 }
 
 static int ecryptfs_set_acl(struct mnt_idmap *idmap,
@@ -1102,11 +1103,10 @@ static int ecryptfs_set_acl(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,
-			 posix_acl_xattr_name(type), acl);
+	rc = vfs_set_acl(&lower_path, posix_acl_xattr_name(type), acl);
 	if (!rc)
 		fsstack_copy_attr_all(d_inode(dentry), lower_inode);
 	return rc;
diff --git a/fs/internal.h b/fs/internal.h
index 71211ef0859f..c2088345462c 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -304,20 +304,19 @@ int import_xattr_name(struct xattr_name *kname, const char __user *name);
 int may_write_xattr(struct mnt_idmap *idmap, struct inode *inode);
 
 #ifdef CONFIG_FS_POSIX_ACL
-int do_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-	       const char *acl_name, const void *kvalue, size_t size);
-ssize_t do_get_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-		   const char *acl_name, void *kvalue, size_t size);
+int do_set_acl(const struct path *path, const char *acl_name,
+	       const void *kvalue, size_t size);
+ssize_t do_get_acl(const struct path *path, const char *acl_name,
+		   void *kvalue, size_t size);
 #else
-static inline int do_set_acl(struct mnt_idmap *idmap,
-			     struct dentry *dentry, const char *acl_name,
+static inline int do_set_acl(const struct path *path, const char *acl_name,
 			     const void *kvalue, size_t size)
 {
 	return -EOPNOTSUPP;
 }
-static inline ssize_t do_get_acl(struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name,
-				 void *kvalue, size_t size)
+static inline ssize_t do_get_acl(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 401cb8c75520..f73556d17d65 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(path, acl_name);
 	if (IS_ERR_OR_NULL(real_acl))
 		return real_acl;
 
@@ -502,7 +502,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), realdentry, acl_name);
+			real_acl = vfs_get_acl(&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 c13f6b2c915c..5df2adfea13d 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -366,13 +366,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(&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(&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 18b302f94174..be1643e18a6a 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -1081,8 +1081,7 @@ static int vfs_set_acl_idmapped_mnt(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
+ * @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 +1090,11 @@ static int vfs_set_acl_idmapped_mnt(struct mnt_idmap *idmap,
  *
  * Return: On success 0, on error negative errno.
  */
-int vfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-		const char *acl_name, struct posix_acl *kacl)
+int vfs_set_acl(const struct path *path, const char *acl_name,
+		struct posix_acl *kacl)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	int acl_type;
 	int error;
 	struct inode *inode = d_inode(dentry);
@@ -1159,8 +1160,7 @@ 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
+ * @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
@@ -1168,9 +1168,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(struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name)
+struct posix_acl *vfs_get_acl(const struct path *path, const char *acl_name)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	struct inode *inode = d_inode(dentry);
 	struct posix_acl *acl;
 	int acl_type, error;
@@ -1204,17 +1205,17 @@ 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
+ * @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(struct mnt_idmap *idmap, struct dentry *dentry,
-		   const char *acl_name)
+int vfs_remove_acl(const struct path *path, const char *acl_name)
 {
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+	struct dentry *dentry = path->dentry;
 	int acl_type;
 	int error;
 	struct inode *inode = d_inode(dentry);
@@ -1265,8 +1266,8 @@ int vfs_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry,
 }
 EXPORT_SYMBOL_GPL(vfs_remove_acl);
 
-int do_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-	       const char *acl_name, const void *kvalue, size_t size)
+int do_set_acl(const struct path *path, const char *acl_name,
+	       const void *kvalue, size_t size)
 {
 	int error;
 	struct posix_acl *acl = NULL;
@@ -1281,22 +1282,23 @@ int do_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
 			return PTR_ERR(acl);
 	}
 
-	error = vfs_set_acl(idmap, dentry, acl_name, acl);
+	error = vfs_set_acl(path, acl_name, acl);
 	posix_acl_release(acl);
 	return error;
 }
 
-ssize_t do_get_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-		   const char *acl_name, void *kvalue, size_t size)
+ssize_t do_get_acl(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(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(mnt_idmap(path->mnt),
+				       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 eb904cf9ef2d..97497e72fbe9 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1485,8 +1485,7 @@ int ksmbd_vfs_remove_acl_xattrs(const struct path *path)
 			     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(mnt_idmap(path->mnt),
-					     path->dentry, name);
+			err = vfs_remove_acl(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 818d6652b6d9..ee4a5f6d7ef3 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -660,8 +660,8 @@ int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx)
 static int do_setxattr(const struct path *path, struct kernel_xattr_ctx *ctx)
 {
 	if (is_posix_acl_xattr(ctx->kname->name))
-		return do_set_acl(mnt_idmap(path->mnt), path->dentry,
-				  ctx->kname->name, ctx->kvalue, ctx->size);
+		return do_set_acl(path, ctx->kname->name,
+				  ctx->kvalue, ctx->size);
 
 	return vfs_setxattr(path, ctx->kname->name,
 			ctx->kvalue, ctx->size, ctx->flags);
@@ -806,8 +806,7 @@ do_getxattr(const struct path *path, struct kernel_xattr_ctx *ctx)
 	}
 
 	if (is_posix_acl_xattr(kname))
-		error = do_get_acl(mnt_idmap(path->mnt), path->dentry,
-				   kname, kvalue, ctx->size);
+		error = do_get_acl(path, kname, kvalue, ctx->size);
 	else
 		error = vfs_getxattr(path, kname, kvalue, ctx->size);
 	if (error > 0) {
@@ -1036,8 +1035,7 @@ static long
 removexattr(const struct path *path, const char *name)
 {
 	if (is_posix_acl_xattr(name))
-		return vfs_remove_acl(mnt_idmap(path->mnt), path->dentry,
-				      name);
+		return vfs_remove_acl(path, name);
 	return vfs_removexattr(path, name);
 }
 
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 62d497763e25..384111cb6673 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -105,12 +105,11 @@ static inline void cache_no_acl(struct inode *inode)
 	inode->i_default_acl = NULL;
 }
 
-int vfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-		const char *acl_name, struct posix_acl *kacl);
-struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name);
-int vfs_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry,
-		   const char *acl_name);
+int vfs_set_acl(const struct path *path, const char *acl_name,
+		struct posix_acl *kacl);
+struct posix_acl *vfs_get_acl(const struct path *path,
+			      const char *acl_name);
+int vfs_remove_acl(const struct path *path, const char *acl_name);
 int posix_acl_listxattr(struct inode *inode, char **buffer,
 			ssize_t *remaining_size);
 #else
@@ -141,22 +140,20 @@ static inline void forget_all_cached_acls(struct inode *inode)
 {
 }
 
-static inline int vfs_set_acl(struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *name,
+static inline int vfs_set_acl(const struct path *path, const char *name,
 			      struct posix_acl *acl)
 {
 	return -EOPNOTSUPP;
 }
 
-static inline struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap,
-					    struct dentry *dentry,
+static inline struct posix_acl *vfs_get_acl(const struct path *path,
 					    const char *acl_name)
 {
 	return ERR_PTR(-EOPNOTSUPP);
 }
 
-static inline int vfs_remove_acl(struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name)
+static inline int vfs_remove_acl(const struct path *path,
+				 const char *acl_name)
 {
 	return -EOPNOTSUPP;
 }
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 05/12] LSM: pass struct path to the inode_setattr hook
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (3 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 04/12] fs: pass struct path to POSIX ACL helpers Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 06/12] LSM: pass struct path to the inode xattr hooks Cai Xinchen
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

The inode_setattr hook is called from notify_change(), which now
holds a struct path and used to derive the idmap and dentry from it
just for the hook.  The other in-kernel caller, the fat and exfat
attribute ioctls, already holds a struct file and can pass its
f_path directly.

Convert the hook and its SELinux, Smack and EVM implementations to
take a const struct path.  The implementations derive the idmap and
dentry they still need from the path, so this is a purely mechanical
change with no behavior change.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/attr.c                         |  2 +-
 fs/exfat/file.c                   |  3 +--
 fs/fat/file.c                     |  3 +--
 include/linux/lsm_hook_defs.h     |  2 +-
 include/linux/security.h          |  6 ++----
 security/integrity/evm/evm_main.c |  7 ++++---
 security/security.c               | 10 ++++------
 security/selinux/hooks.c          |  3 ++-
 security/smack/smack_lsm.c        |  3 ++-
 9 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index ea91e4471faa..f0eb4be496fc 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -537,7 +537,7 @@ int notify_change(const struct path *path, struct iattr *attr,
 	    !vfsgid_valid(i_gid_into_vfsgid(idmap, inode)))
 		return -EOVERFLOW;
 
-	error = security_inode_setattr(idmap, dentry, attr);
+	error = security_inode_setattr(path, attr);
 	if (error)
 		return error;
 
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index a2a9ee1a2004..db62a8c85d64 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -493,8 +493,7 @@ 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.dentry, &ia);
+	err = security_inode_setattr(&file->f_path, &ia);
 	if (err)
 		goto out_unlock_inode;
 
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 6c475c53334c..c0ec05825517 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -92,8 +92,7 @@ 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.dentry, &ia);
+	err = security_inode_setattr(&file->f_path, &ia);
 	if (err)
 		goto out_unlock_inode;
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index b22f7928523e..b54fdf7d32e8 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -142,7 +142,7 @@ LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode,
 	 bool rcu)
 LSM_HOOK(int, 0, inode_permission, struct mnt_idmap *idmap,
 	 struct inode *inode, int mask)
-LSM_HOOK(int, 0, inode_setattr, struct mnt_idmap *idmap, struct dentry *dentry,
+LSM_HOOK(int, 0, inode_setattr, const struct path *path,
 	 struct iattr *attr)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, struct mnt_idmap *idmap,
 	 struct dentry *dentry, int ia_valid)
diff --git a/include/linux/security.h b/include/linux/security.h
index 6ac9fb634017..09c14c83e58f 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -429,8 +429,7 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode,
 			       bool rcu);
 int security_inode_permission(struct mnt_idmap *idmap, struct inode *inode,
 			      int mask);
-int security_inode_setattr(struct mnt_idmap *idmap,
-			   struct dentry *dentry, struct iattr *attr);
+int security_inode_setattr(const struct path *path, struct iattr *attr);
 void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 				 int ia_valid);
 int security_inode_getattr(const struct path *path);
@@ -1001,8 +1000,7 @@ static inline int security_inode_permission(struct mnt_idmap *idmap,
 	return 0;
 }
 
-static inline int security_inode_setattr(struct mnt_idmap *idmap,
-					 struct dentry *dentry,
+static inline int security_inode_setattr(const struct path *path,
 					 struct iattr *attr)
 {
 	return 0;
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index b59e3f121b8a..b0af1bf86ab5 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -949,18 +949,19 @@ static int evm_attr_change(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
  *
  * 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(struct mnt_idmap *idmap, struct dentry *dentry,
+static int evm_inode_setattr(const struct path *path,
 			     struct iattr *attr)
 {
 	unsigned int ia_valid = attr->ia_valid;
 	enum integrity_status evm_status;
+	struct dentry *dentry = path->dentry;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 
 	/* 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 de3ed5c18802..ce74b9f1d110 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1891,8 +1891,7 @@ int security_inode_permission(struct mnt_idmap *idmap, struct inode *inode,
 
 /**
  * security_inode_setattr() - Check if setting file attributes is allowed
- * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file
  * @attr: new attributes
  *
  * Check permission before setting file attributes.  Note that the kernel call
@@ -1902,12 +1901,11 @@ int security_inode_permission(struct mnt_idmap *idmap, struct inode *inode,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_setattr(struct mnt_idmap *idmap,
-			   struct dentry *dentry, struct iattr *attr)
+int security_inode_setattr(const struct path *path, struct iattr *attr)
 {
-	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, path, attr);
 }
 EXPORT_SYMBOL_GPL(security_inode_setattr);
 
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 1d06b08f3c41..49785dd62df4 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3339,10 +3339,11 @@ static int selinux_inode_permission(struct mnt_idmap *idmap,
 	return rc;
 }
 
-static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+static int selinux_inode_setattr(const struct path *path,
 				 struct iattr *iattr)
 {
 	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 e517809c230d..991b967c4c4e 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1272,10 +1272,11 @@ static int smack_inode_permission(struct mnt_idmap *idmap, struct inode *inode,
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
+static int smack_inode_setattr(const struct path *path,
 			       struct iattr *iattr)
 {
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	int rc;
 
 	/*
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 06/12] LSM: pass struct path to the inode xattr hooks
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (4 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 05/12] LSM: pass struct path to the inode_setattr hook Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 07/12] LSM: pass struct path to the inode posix acl hooks Cai Xinchen
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

The inode_setxattr, inode_getxattr, inode_listxattr and
inode_removexattr hooks are called from fs/xattr.c, whose helpers
now hold a struct path and used to derive the idmap and dentry from
it 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
derive the idmap and dentry they still need from the path, so this
is a purely mechanical change with no behavior change.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/xattr.c                            |  8 ++---
 include/linux/lsm_hook_defs.h         | 13 ++++---
 include/linux/security.h              | 35 +++++++++---------
 security/commoncap.c                  | 22 +++++-------
 security/integrity/evm/evm_main.c     | 15 ++++----
 security/integrity/ima/ima_appraise.c |  9 ++---
 security/security.c                   | 51 +++++++++++++--------------
 security/selinux/hooks.c              | 23 ++++++------
 security/smack/smack_lsm.c            | 18 +++++-----
 9 files changed, 96 insertions(+), 98 deletions(-)

diff --git a/fs/xattr.c b/fs/xattr.c
index ee4a5f6d7ef3..7f09307d6845 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -307,7 +307,7 @@ __vfs_setxattr_locked(const struct path *path, const char *name,
 	if (error)
 		return error;
 
-	error = security_inode_setxattr(idmap, dentry, name, value, size,
+	error = security_inode_setxattr(path, name, value, size,
 					flags);
 	if (error)
 		goto out;
@@ -463,7 +463,7 @@ vfs_getxattr(const struct path *path, const char *name, void *value,
 	if (error)
 		return error;
 
-	error = security_inode_getxattr(dentry, name);
+	error = security_inode_getxattr(path, name);
 	if (error)
 		return error;
 
@@ -514,7 +514,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(dentry);
+	error = security_inode_listxattr(path);
 	if (error)
 		return error;
 
@@ -574,7 +574,7 @@ __vfs_removexattr_locked(const struct path *path, const char *name,
 	if (error)
 		return error;
 
-	error = security_inode_removexattr(idmap, dentry, name);
+	error = security_inode_removexattr(path, name);
 	if (error)
 		goto out;
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index b54fdf7d32e8..3a3512a3ee91 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -148,15 +148,14 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, struct mnt_idmap *idmap,
 	 struct dentry *dentry, int ia_valid)
 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, struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *name, const void *value,
-	 size_t size, int flags)
+LSM_HOOK(int, 0, inode_setxattr, const struct path *path,
+	 const char *name, const void *value, size_t size, int flags)
 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_removexattr, struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *name)
+LSM_HOOK(int, 0, inode_getxattr, const struct path *path, const char *name)
+LSM_HOOK(int, 0, inode_listxattr, const struct path *path)
+LSM_HOOK(int, 0, inode_removexattr, const struct path *path,
+	 const char *name)
 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 09c14c83e58f..f5dc67a937bd 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -185,10 +185,9 @@ 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,
+int cap_inode_setxattr(const struct path *path, const char *name,
 		       const void *value, size_t size, int flags);
-int cap_inode_removexattr(struct mnt_idmap *idmap,
-			  struct dentry *dentry, const char *name);
+int cap_inode_removexattr(const struct path *path, const char *name);
 int cap_inode_need_killpriv(struct dentry *dentry);
 int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry);
 int cap_inode_getsecurity(struct mnt_idmap *idmap,
@@ -433,9 +432,9 @@ int security_inode_setattr(const struct path *path, struct iattr *attr);
 void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 				 int ia_valid);
 int security_inode_getattr(const struct path *path);
-int security_inode_setxattr(struct mnt_idmap *idmap,
-			    struct dentry *dentry, const char *name,
-			    const void *value, size_t size, int flags);
+int security_inode_setxattr(const struct path *path,
+			    const char *name, const void *value,
+			    size_t size, int flags);
 int security_inode_set_acl(struct mnt_idmap *idmap,
 			   struct dentry *dentry, const char *acl_name,
 			   struct posix_acl *kacl);
@@ -450,10 +449,10 @@ void security_inode_post_remove_acl(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_removexattr(struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *name);
+int security_inode_getxattr(const struct path *path, const char *name);
+int security_inode_listxattr(const struct path *path);
+int security_inode_removexattr(const struct path *path,
+			       const char *name);
 void security_inode_post_removexattr(struct dentry *dentry, const char *name);
 int security_inode_file_setattr(struct dentry *dentry,
 			      struct file_kattr *fa);
@@ -1016,11 +1015,10 @@ static inline int security_inode_getattr(const struct path *path)
 	return 0;
 }
 
-static inline int security_inode_setxattr(struct mnt_idmap *idmap,
-		struct dentry *dentry, const char *name, const void *value,
-		size_t size, int flags)
+static inline int security_inode_setxattr(const struct path *path,
+		const char *name, const void *value, size_t size, int flags)
 {
-	return cap_inode_setxattr(dentry, name, value, size, flags);
+	return cap_inode_setxattr(path, name, value, size, flags);
 }
 
 static inline int security_inode_set_acl(struct mnt_idmap *idmap,
@@ -1059,22 +1057,21 @@ 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,
+static inline int security_inode_getxattr(const struct path *path,
 			const char *name)
 {
 	return 0;
 }
 
-static inline int security_inode_listxattr(struct dentry *dentry)
+static inline int security_inode_listxattr(const struct path *path)
 {
 	return 0;
 }
 
-static inline int security_inode_removexattr(struct mnt_idmap *idmap,
-					     struct dentry *dentry,
+static inline int security_inode_removexattr(const struct path *path,
 					     const char *name)
 {
-	return cap_inode_removexattr(idmap, dentry, name);
+	return cap_inode_removexattr(path, name);
 }
 
 static inline void security_inode_post_removexattr(struct dentry *dentry,
diff --git a/security/commoncap.c b/security/commoncap.c
index c5d2f263d75b..9a185a6036af 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,7 +1008,7 @@ 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
@@ -1019,9 +1020,10 @@ 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,
+int cap_inode_setxattr(const struct path *path, const char *name,
 		       const void *value, size_t size, int flags)
 {
+	struct dentry *dentry = path->dentry;
 	struct user_namespace *user_ns = dentry->d_sb->s_user_ns;
 
 	/* Ignore non-security xattrs */
@@ -1044,25 +1046,18 @@ 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
  *
  * 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(struct mnt_idmap *idmap,
-			  struct dentry *dentry, const char *name)
+int cap_inode_removexattr(const struct path *path, const char *name)
 {
+	struct dentry *dentry = path->dentry;
 	struct user_namespace *user_ns = dentry->d_sb->s_user_ns;
 
 	/* Ignore non-security xattrs */
@@ -1075,7 +1070,8 @@ int cap_inode_removexattr(struct mnt_idmap *idmap,
 		struct inode *inode = d_backing_inode(dentry);
 		if (!inode)
 			return -EINVAL;
-		if (!capable_wrt_inode_uidgid(idmap, inode, CAP_SETFCAP))
+		if (!capable_wrt_inode_uidgid(mnt_idmap(path->mnt), inode,
+					      CAP_SETFCAP))
 			return -EPERM;
 		return 0;
 	}
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index b0af1bf86ab5..47ad39d64c76 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -594,8 +594,7 @@ static int evm_protect_xattr(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
@@ -607,11 +606,13 @@ static int evm_protect_xattr(struct mnt_idmap *idmap,
  * userspace from writing HMAC value.  Writing 'security.evm' requires
  * requires CAP_SYS_ADMIN privileges.
  */
-static int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
+static int evm_inode_setxattr(const struct path *path,
 			      const char *xattr_name, const void *xattr_value,
 			      size_t xattr_value_len, int flags)
 {
 	const struct evm_ima_xattr_data *xattr_data = xattr_value;
+	struct dentry *dentry = path->dentry;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 
 	/* Policy permits modification of the protected xattrs even though
 	 * there's no HMAC key loaded
@@ -632,16 +633,18 @@ static int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
 
 /**
  * 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
  *
  * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that
  * the current value is valid.
  */
-static int evm_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry,
+static int evm_inode_removexattr(const struct path *path,
 				 const char *xattr_name)
 {
+	struct dentry *dentry = path->dentry;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
+
 	/* 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 b280488e15fc..58ba674bc172 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -759,11 +759,12 @@ static int validate_hash_algo(struct dentry *dentry,
 	return -EACCES;
 }
 
-static int ima_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry,
+static int ima_inode_setxattr(const struct path *path,
 			      const char *xattr_name, const void *xattr_value,
 			      size_t xattr_value_len, int flags)
 {
 	const struct evm_ima_xattr_data *xvalue = xattr_value;
+	struct dentry *dentry = path->dentry;
 	int digsig = 0;
 	int result;
 	int err;
@@ -801,16 +802,16 @@ static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
 	return 0;
 }
 
-static int ima_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry,
+static int ima_inode_removexattr(const struct path *path,
 				 const char *xattr_name)
 {
 	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 ce74b9f1d110..3a8892d8ca5c 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1942,15 +1942,14 @@ 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
  * @name: xattr name
  * @value: xattr value
  * @size: size of xattr value
  * @flags: flags
  *
  * 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.
  *
@@ -1966,23 +1965,23 @@ int security_inode_getattr(const struct path *path)
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_setxattr(struct mnt_idmap *idmap,
-			    struct dentry *dentry, const char *name,
-			    const void *value, size_t size, int flags)
+int security_inode_setxattr(const struct path *path,
+			    const char *name, const void *value,
+			    size_t size, int flags)
 {
 	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);
 		if (rc)
 			return rc;
 	}
 
-	return call_int_hook(inode_setxattr, idmap, dentry, name, value, size,
+	return call_int_hook(inode_setxattr, path, name, value, size,
 			     flags);
 }
 
@@ -2099,45 +2098,44 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name,
 
 /**
  * security_inode_getxattr() - Check if xattr access is allowed
- * @dentry: file
+ * @path: file
  * @name: xattr name
  *
  * 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)
 {
-	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);
 }
 
 /**
  * security_inode_listxattr() - Check if listing xattrs is allowed
- * @dentry: file
+ * @path: file
  *
  * 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)
 {
-	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);
 }
 
 /**
  * security_inode_removexattr() - Check if removing an xattr is allowed
- * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file
  * @name: xattr name
  *
  * 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.
  *
@@ -2153,22 +2151,21 @@ int security_inode_listxattr(struct dentry *dentry)
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_removexattr(struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *name)
+int security_inode_removexattr(const struct path *path, const char *name)
 {
 	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(path, name);
 		if (rc)
 			return rc;
 	}
 
-	return call_int_hook(inode_removexattr, idmap, dentry, name);
+	return call_int_hook(inode_removexattr, path, name);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 49785dd62df4..5d98ec73df9f 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3410,10 +3410,12 @@ static int selinux_inode_xattr_skipcap(const char *name)
 	return !strcmp(name, XATTR_NAME_SELINUX);
 }
 
-static int selinux_inode_setxattr(struct mnt_idmap *idmap,
-				  struct dentry *dentry, const char *name,
-				  const void *value, size_t size, int flags)
+static int selinux_inode_setxattr(const struct path *path,
+				  const char *name, const void *value,
+				  size_t size, int flags)
 {
+	struct dentry *dentry = path->dentry;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	struct inode *inode = d_backing_inode(dentry);
 	struct inode_security_struct *isec;
 	struct superblock_security_struct *sbsec;
@@ -3555,26 +3557,27 @@ 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)
 {
 	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)
 {
 	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(struct mnt_idmap *idmap,
-				     struct dentry *dentry, const char *name)
+static int selinux_inode_removexattr(const struct path *path,
+				     const char *name)
 {
 	/* 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;
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 991b967c4c4e..4adb2fd9cf70 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1351,11 +1351,12 @@ static int smack_inode_xattr_skipcap(const char *name)
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_setxattr(struct mnt_idmap *idmap,
-				struct dentry *dentry, const char *name,
-				const void *value, size_t size, int flags)
+static int smack_inode_setxattr(const struct path *path,
+				const char *name, const void *value,
+				size_t size, int flags)
 {
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	struct smack_known *skp;
 	int check_priv = 0;
 	int check_import = 0;
@@ -1462,9 +1463,10 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name,
  *
  * 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 smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	int rc;
 
 	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY);
@@ -1477,19 +1479,19 @@ 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
  *
  * Removing the Smack attribute requires CAP_MAC_ADMIN
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_removexattr(struct mnt_idmap *idmap,
-				   struct dentry *dentry, const char *name)
+static int smack_inode_removexattr(const struct path *path,
+				   const char *name)
 {
 	struct inode_smack *isp;
 	struct smk_audit_info ad;
+	struct dentry *dentry = path->dentry;
 	int rc = 0;
 
 	if (strcmp(name, XATTR_NAME_SMACK) == 0 ||
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 07/12] LSM: pass struct path to the inode posix acl hooks
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (5 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 06/12] LSM: pass struct path to the inode xattr hooks Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 08/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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 idmap and 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.  The implementations
derive the idmap and dentry they still need from the path, so this
is a purely mechanical change with no behavior change.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 fs/posix_acl.c                        |  6 ++---
 include/linux/lsm_hook_defs.h         | 12 ++++-----
 include/linux/security.h              | 20 +++++----------
 security/integrity/evm/evm_main.c     | 14 +++++-----
 security/integrity/ima/ima_appraise.c |  8 +++---
 security/security.c                   | 32 ++++++++++-------------
 security/selinux/hooks.c              | 19 +++++++-------
 security/smack/smack_lsm.c            | 37 ++++++++++++---------------
 8 files changed, 66 insertions(+), 82 deletions(-)

diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index be1643e18a6a..72e77540a0e9 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -1128,7 +1128,7 @@ int vfs_set_acl(const struct path *path, const char *acl_name,
 	if (error)
 		goto out_inode_unlock;
 
-	error = security_inode_set_acl(idmap, dentry, acl_name, kacl);
+	error = security_inode_set_acl(path, acl_name, kacl);
 	if (error)
 		goto out_inode_unlock;
 
@@ -1184,7 +1184,7 @@ struct posix_acl *vfs_get_acl(const struct path *path, const char *acl_name)
 	 * 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(path, acl_name);
 	if (error)
 		return ERR_PTR(error);
 
@@ -1236,7 +1236,7 @@ int vfs_remove_acl(const struct path *path, const char *acl_name)
 	if (error)
 		goto out_inode_unlock;
 
-	error = security_inode_remove_acl(idmap, dentry, acl_name);
+	error = security_inode_remove_acl(path, acl_name);
 	if (error)
 		goto out_inode_unlock;
 
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 3a3512a3ee91..45cf0ca24260 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -160,14 +160,14 @@ 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_set_acl, struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *acl_name, struct posix_acl *kacl)
+LSM_HOOK(int, 0, inode_set_acl, const struct path *path,
+	 const char *acl_name, struct posix_acl *kacl)
 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, struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *acl_name)
-LSM_HOOK(int, 0, inode_remove_acl, struct mnt_idmap *idmap,
-	 struct dentry *dentry, const char *acl_name)
+LSM_HOOK(int, 0, inode_get_acl, const struct path *path,
+	 const char *acl_name)
+LSM_HOOK(int, 0, inode_remove_acl, const struct path *path,
+	 const char *acl_name)
 LSM_HOOK(void, LSM_RET_VOID, inode_post_remove_acl, 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/security.h b/include/linux/security.h
index f5dc67a937bd..8b02b3bfe46d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -435,15 +435,12 @@ int security_inode_getattr(const struct path *path);
 int security_inode_setxattr(const struct path *path,
 			    const char *name, const void *value,
 			    size_t size, int flags);
-int security_inode_set_acl(struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name,
-			   struct posix_acl *kacl);
+int security_inode_set_acl(const struct path *path,
+			   const char *acl_name, struct posix_acl *kacl);
 void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name,
 				 struct posix_acl *kacl);
-int security_inode_get_acl(struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name);
-int security_inode_remove_acl(struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name);
+int security_inode_get_acl(const struct path *path, const char *acl_name);
+int security_inode_remove_acl(const struct path *path, const char *acl_name);
 void security_inode_post_remove_acl(struct mnt_idmap *idmap,
 				    struct dentry *dentry,
 				    const char *acl_name);
@@ -1021,8 +1018,7 @@ static inline int security_inode_setxattr(const struct path *path,
 	return cap_inode_setxattr(path, name, value, size, flags);
 }
 
-static inline int security_inode_set_acl(struct mnt_idmap *idmap,
-					 struct dentry *dentry,
+static inline int security_inode_set_acl(const struct path *path,
 					 const char *acl_name,
 					 struct posix_acl *kacl)
 {
@@ -1034,15 +1030,13 @@ static inline void security_inode_post_set_acl(struct dentry *dentry,
 					       struct posix_acl *kacl)
 { }
 
-static inline int security_inode_get_acl(struct mnt_idmap *idmap,
-					 struct dentry *dentry,
+static inline int security_inode_get_acl(const struct path *path,
 					 const char *acl_name)
 {
 	return 0;
 }
 
-static inline int security_inode_remove_acl(struct mnt_idmap *idmap,
-					    struct dentry *dentry,
+static inline int security_inode_remove_acl(const struct path *path,
 					    const char *acl_name)
 {
 	return 0;
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index 47ad39d64c76..c83befd32e99 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -685,8 +685,7 @@ static inline int evm_inode_set_acl_change(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
+ * @path: pointer to the affected object
  * @acl_name: name of the posix acl
  * @kacl: pointer to the posix acls
  *
@@ -696,10 +695,12 @@ static inline int evm_inode_set_acl_change(struct mnt_idmap *idmap,
  *
  * Return: zero on success, -EPERM on failure.
  */
-static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+static int evm_inode_set_acl(const struct path *path,
 			     const char *acl_name, struct posix_acl *kacl)
 {
 	enum integrity_status evm_status;
+	struct dentry *dentry = path->dentry;
+	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 
 	/* Policy permits modification of the protected xattrs even though
 	 * there's no HMAC key loaded
@@ -738,8 +739,7 @@ static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
 
 /**
  * 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
  *
  * Prevent removing posix acls causing the EVM HMAC to be re-calculated
@@ -748,10 +748,10 @@ static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
  *
  * Return: zero on success, -EPERM on failure.
  */
-static int evm_inode_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+static int evm_inode_remove_acl(const struct path *path,
 				const char *acl_name)
 {
-	return evm_inode_set_acl(idmap, dentry, acl_name, NULL);
+	return evm_inode_set_acl(path, acl_name, NULL);
 }
 
 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 58ba674bc172..518faf04ddde 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -793,11 +793,11 @@ static int ima_inode_setxattr(const struct path *path,
 	return result;
 }
 
-static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+static int ima_inode_set_acl(const struct path *path,
 			     const char *acl_name, struct posix_acl *kacl)
 {
 	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;
 }
@@ -818,10 +818,10 @@ static int ima_inode_removexattr(const struct path *path,
 	return result;
 }
 
-static int ima_inode_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry,
+static int ima_inode_remove_acl(const struct path *path,
 				const char *acl_name)
 {
-	return ima_inode_set_acl(idmap, dentry, acl_name, NULL);
+	return ima_inode_set_acl(path, acl_name, NULL);
 }
 
 static struct security_hook_list ima_appraise_hooks[] __ro_after_init = {
diff --git a/security/security.c b/security/security.c
index 3a8892d8ca5c..74bcd8c0502c 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1987,8 +1987,7 @@ int security_inode_setxattr(const struct path *path,
 
 /**
  * security_inode_set_acl() - Check if setting posix acls is allowed
- * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file
  * @acl_name: acl name
  * @kacl: acl struct
  *
@@ -1997,13 +1996,12 @@ int security_inode_setxattr(const struct path *path,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_set_acl(struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name,
-			   struct posix_acl *kacl)
+int security_inode_set_acl(const struct path *path,
+			   const char *acl_name, struct posix_acl *kacl)
 {
-	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, path, acl_name, kacl);
 }
 
 /**
@@ -2025,8 +2023,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
- * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file
  * @acl_name: acl name
  *
  * Check permission before getting osix acls, the posix acls are identified by
@@ -2034,18 +2031,16 @@ 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(struct mnt_idmap *idmap,
-			   struct dentry *dentry, const char *acl_name)
+int security_inode_get_acl(const struct path *path, const char *acl_name)
 {
-	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, path, acl_name);
 }
 
 /**
  * security_inode_remove_acl() - Check if removing a posix acl is allowed
- * @idmap: idmap of the mount
- * @dentry: file
+ * @path: file
  * @acl_name: acl name
  *
  * Check permission before removing posix acls, the posix acls are identified
@@ -2053,12 +2048,11 @@ int security_inode_get_acl(struct mnt_idmap *idmap,
  *
  * Return: Returns 0 if permission is granted.
  */
-int security_inode_remove_acl(struct mnt_idmap *idmap,
-			      struct dentry *dentry, const char *acl_name)
+int security_inode_remove_acl(const struct path *path, const char *acl_name)
 {
-	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, path, acl_name);
 }
 
 /**
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 5d98ec73df9f..45ece734463e 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3498,23 +3498,22 @@ static int selinux_inode_setxattr(const struct path *path,
 			    &ad);
 }
 
-static int selinux_inode_set_acl(struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name,
-				 struct posix_acl *kacl)
+static int selinux_inode_set_acl(const struct path *path,
+				 const char *acl_name, struct posix_acl *kacl)
 {
-	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(struct mnt_idmap *idmap,
-				 struct dentry *dentry, const char *acl_name)
+static int selinux_inode_get_acl(const struct path *path,
+				 const char *acl_name)
 {
-	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(struct mnt_idmap *idmap,
-				    struct dentry *dentry, const char *acl_name)
+static int selinux_inode_remove_acl(const struct path *path,
+				    const char *acl_name)
 {
-	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 4adb2fd9cf70..7889f63ec739 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -1548,62 +1548,59 @@ static int smack_inode_removexattr(const struct path *path,
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_set_acl(struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *acl_name,
-			       struct posix_acl *kacl)
+static int smack_inode_set_acl(const struct path *path,
+			       const char *acl_name, struct posix_acl *kacl)
 {
 	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
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_get_acl(struct mnt_idmap *idmap,
-			       struct dentry *dentry, const char *acl_name)
+static int smack_inode_get_acl(const struct path *path,
+			       const char *acl_name)
 {
 	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
  *
  * Returns 0 if access is permitted, an error code otherwise
  */
-static int smack_inode_remove_acl(struct mnt_idmap *idmap,
-				  struct dentry *dentry, const char *acl_name)
+static int smack_inode_remove_acl(const struct path *path,
+				  const char *acl_name)
 {
 	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	[flat|nested] 20+ messages in thread

* [PATCH RFC -next 08/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (6 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 07/12] LSM: pass struct path to the inode posix acl hooks Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 09/12] landlock: Implement metadata access hooks Cai Xinchen
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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.

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 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.

Increment the Landlock ABI version from 11 to 12, and update the ABI
version check in tools/testing/selftests/landlock/base_test.c
accordingly.

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

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 include/linux/landlock.h                     |  4 ++-
 include/uapi/linux/landlock.h                | 26 +++++++++++++++++---
 security/landlock/limits.h                   |  2 +-
 security/landlock/syscalls.c                 |  2 +-
 tools/testing/selftests/landlock/base_test.c |  2 +-
 5 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/include/linux/landlock.h b/include/linux/landlock.h
index 004cbd0b9298..86fc2181ab24 100644
--- a/include/linux/landlock.h
+++ b/include/linux/landlock.h
@@ -39,7 +39,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 cceda3b3b961..40d64e8b5e2d 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -343,6 +343,24 @@ struct landlock_net_port_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 twelfth 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)`) are not restricted by this
+ *   access right.
+ *
+ *   This access right is available since the twelfth 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
@@ -406,10 +424,8 @@ struct landlock_net_port_attr {
  * .. warning::
  *
  *   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)`.
+ *   accessible through these syscall families: :manpage:`chdir(2)',
+ *   :manpage:`flock(2)', :manpage:`fcntl(2)', :manpage:`access(2)'.
  *   Future Landlock evolutions will enable to restrict them.
  */
 /* clang-format off */
@@ -430,6 +446,8 @@ struct landlock_net_port_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/limits.h b/security/landlock/limits.h
index 1a7c5fb8f6fd..665ff238bd27 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -19,7 +19,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 400a2f928de3..205d64c534d7 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -172,7 +172,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 = 11;
+const int landlock_abi_version = 12;
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index d20ab8f0862c..58fe322d8637 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(11, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(12, landlock_create_ruleset(NULL, 0,
 					      LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 09/12] landlock: Implement metadata access hooks
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (7 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 08/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-26  8:38   ` Günther Noack
  2026-09-24 10:48 ` [PATCH RFC -next 10/12] selftests/landlock: Add tests for metadata access rights Cai Xinchen
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

Implement the LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights by hooking the
inode_getattr, inode_setattr, inode_setxattr, inode_getxattr,
inode_listxattr, inode_removexattr, inode_set_acl, inode_get_acl and
inode_remove_acl LSM hooks, which now receive a struct path thanks to
the preceding VFS and LSM refactoring.

The following system calls are now controlled:

- stat(2), fstat(2), lstat(2), newfstatat(2), getxattr(2) and
  friends, listxattr(2) and friends, and POSIX ACL reads via
  inode_getattr, inode_getxattr, inode_listxattr and inode_get_acl
  (READ_METADATA)
- chmod(2), fchmod(2), fchmodat(2), fchmodat2(2), chown(2), fchown(2),
  lchown(2), fchownat(2), chgrp(2), utimensat(2), futimens(2),
  utime(2), setxattr(2) and friends, removexattr(2) and friends, and
  POSIX ACL set and remove via inode_setattr, inode_setxattr,
  inode_removexattr, inode_set_acl and inode_remove_acl
  (WRITE_METADATA)

Both new rights are added to ACCESS_FILE as they apply to both files
and directories.

hook_inode_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, and neither are chmod(2)/chown(2) calls
that do not change any attribute (e.g. chown(2) with -1/-1, which is
a no-op that never reaches the hook), matching the behavior of the
SELinux inode_setattr hook.

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.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 security/landlock/fs.c | 86 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index cab43892ec2f..e58b2aa0da65 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,81 @@ static int hook_path_truncate(const struct path *const path)
 	return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
 }
 
+static int hook_inode_getattr(const struct path *const path)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_setattr(const struct path *const path,
+			      struct iattr *const attr)
+{
+	/*
+	 * Explicit metadata changes (i.e. mode, ownership, and timestamps
+	 * set with utimes() and friends) require
+	 * LANDLOCK_ACCESS_FS_WRITE_METADATA.  Implicit timestamp updates
+	 * (e.g. ATTR_CTIME set for a write) and size changes (handled by
+	 * the truncate hooks) are not restricted.
+	 */
+	if (!(attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
+				ATTR_TIMES_SET | ATTR_TOUCH)))
+		return 0;
+
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_setxattr(const struct path *const path,
+			       const char *const name,
+			       const void *const value, const size_t size,
+			       const int flags)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_getxattr(const struct path *const path,
+			       const char *const name)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_listxattr(const struct path *const path)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_removexattr(const struct path *const path,
+				  const char *const name)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_set_acl(const struct path *const path,
+			      const char *const acl_name,
+			      struct posix_acl *const kacl)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_WRITE_METADATA);
+}
+
+static int hook_inode_get_acl(const struct path *const path,
+			      const char *const acl_name)
+{
+	return current_check_access_path(path,
+					LANDLOCK_ACCESS_FS_READ_METADATA);
+}
+
+static int hook_inode_remove_acl(const struct path *const path,
+				 const char *const acl_name)
+{
+	return current_check_access_path(path,
+					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
@@ -2100,6 +2177,15 @@ 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(inode_getattr, hook_inode_getattr),
+	LSM_HOOK_INIT(inode_setattr, hook_inode_setattr),
+	LSM_HOOK_INIT(inode_setxattr, hook_inode_setxattr),
+	LSM_HOOK_INIT(inode_getxattr, hook_inode_getxattr),
+	LSM_HOOK_INIT(inode_listxattr, hook_inode_listxattr),
+	LSM_HOOK_INIT(inode_removexattr, hook_inode_removexattr),
+	LSM_HOOK_INIT(inode_set_acl, hook_inode_set_acl),
+	LSM_HOOK_INIT(inode_get_acl, hook_inode_get_acl),
+	LSM_HOOK_INIT(inode_remove_acl, hook_inode_remove_acl),
 	LSM_HOOK_INIT(unix_find, hook_unix_find),
 
 	LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 10/12] selftests/landlock: Add tests for metadata access rights
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (8 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 09/12] landlock: Implement metadata access hooks Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 11/12] samples/landlock: Add metadata rights to sandboxer Cai Xinchen
                   ` (4 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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 a file (which
  triggers implicit timestamp updates) stays allowed without
  WRITE_METADATA;
- read_metadata: stat(2), fstat(2), getxattr(2), and listxattr(2) are
  denied with EACCES without READ_METADATA, and allowed with it.

Rule enforcement is checked per file and per directory.  chown(2) is
tested with a group change because chown(2) with (-1, -1) is a no-op
that is not visible to the inode_setattr hook.

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

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 tools/testing/selftests/landlock/fs_test.c | 309 ++++++++++++++++++++-
 1 file changed, 308 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index fd20a2b3c0a5..a3e764cc56d5 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -28,6 +28,7 @@
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/un.h>
+#include <sys/xattr.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
@@ -578,11 +579,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 | \
@@ -10923,4 +10926,308 @@ 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 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 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;
+	int ruleset_fd;
+	const char *const file1 = file1_s1d1;
+	const char *const file2 = file2_s1d1;
+	const char *const dir1 = dir_s1d1;
+	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 = 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_WRONLY | O_CLOEXEC);
+	ASSERT_LE(0, file2_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/* 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 are allowed without WRITE_METADATA.
+	 */
+	EXPECT_EQ(1, write(file2_fd, "a", 1));
+
+	/* 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"));
+
+	/* dir1 does not have WRITE_METADATA: denied */
+	EXPECT_EQ(EACCES, test_chmod(dir1, 0700));
+	EXPECT_EQ(EACCES, test_chown(dir1, -1, 0));
+
+	ASSERT_EQ(0, close(file_fd));
+	ASSERT_EQ(0, close(file2_fd));
+}
+
+TEST_F_FORK(layout1, read_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_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));
+
+	file_fd = open(file1, O_RDONLY | O_CLOEXEC);
+	ASSERT_LE(0, file_fd);
+
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	/* file1 has READ_METADATA: allowed */
+	EXPECT_EQ(0, test_stat(file1, &statbuf));
+	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));
+
+	/* file2 does not have READ_METADATA: denied */
+	EXPECT_EQ(EACCES, test_stat(file2, &statbuf));
+	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));
+}
+
 TEST_HARNESS_MAIN
-- 
2.18.0.huawei.25


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

* [PATCH RFC -next 11/12] samples/landlock: Add metadata rights to sandboxer
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (9 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 10/12] selftests/landlock: Add tests for metadata access rights Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-24 10:48 ` [PATCH RFC -next 12/12] Documentation: Update landlock doc for metadata rights Cai Xinchen
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

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 < 12, and bump LANDLOCK_ABI_LAST to 12.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 samples/landlock/sandboxer.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 030583273f3f..1a77a675f654 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -115,6 +115,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)
@@ -286,7 +288,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 | \
@@ -302,7 +305,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 */
 
@@ -369,7 +373,7 @@ static int add_quiet_access(const char *const env_var,
 	return 0;
 }
 
-#define LANDLOCK_ABI_LAST 11
+#define LANDLOCK_ABI_LAST 12
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -552,7 +556,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
 		supported_restrict_flags &=
 			~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
 		set_restrict_flags &= ~LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS;
-
+		__attribute__((fallthrough));
+	case 11:
+		/* Removes metadata rights for ABI < 12 */
+		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	[flat|nested] 20+ messages in thread

* [PATCH RFC -next 12/12] Documentation: Update landlock doc for metadata rights
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (10 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 11/12] samples/landlock: Add metadata rights to sandboxer Cai Xinchen
@ 2026-09-24 10:48 ` Cai Xinchen
  2026-09-25 15:33 ` [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Christian Brauner
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 20+ messages in thread
From: Cai Xinchen @ 2026-09-24 10:48 UTC (permalink / raw)
  To: mic, gnoack, paul, jmorris, serge, corbet, skhan
  Cc: rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, caixinchen1, lujialin4

Update the user space documentation to include the new
LANDLOCK_ACCESS_FS_READ_METADATA and
LANDLOCK_ACCESS_FS_WRITE_METADATA access rights in the example
ruleset attributes, and extend the ABI version fallback switch to
remove them for ABI < 12.

Assisted-by: opencode: glm-5.3
Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
---
 Documentation/userspace-api/landlock.rst | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 84cb7bf6b3ed..f6389b9668b4 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -78,7 +78,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 |
@@ -140,6 +142,13 @@ version, and only use the available subset of access rights:
         ruleset_attr.handled_access_net &=
             ~(LANDLOCK_ACCESS_NET_BIND_UDP |
               LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+        __attribute__((fallthrough));
+    case 10:
+    case 11:
+        /* Removes metadata rights for ABI < 12 */
+        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.
-- 
2.18.0.huawei.25


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

* Re: [PATCH RFC -next 03/12] fs: pass struct path to xattr helpers
  2026-09-24 10:48 ` [PATCH RFC -next 03/12] fs: pass struct path to xattr helpers Cai Xinchen
@ 2026-09-24 11:12   ` Amir Goldstein
  0 siblings, 0 replies; 20+ messages in thread
From: Amir Goldstein @ 2026-09-24 11:12 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: mic, gnoack, paul, jmorris, serge, corbet, skhan, rdunlap,
	gregkh, rafael, dakr, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, miklos, senozhatsky,
	chenxiaosong, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, nanx95726,
	djwong, daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4

On Thu, Sep 24, 2026 at 12:22 PM Cai Xinchen <caixinchen1@huawei.com> wrote:
>
> vfs_setxattr(), vfs_getxattr(), vfs_listxattr() and vfs_removexattr()
> along with their __vfs_setxattr_locked()/__vfs_removexattr_locked()
> counterparts and the do_setxattr()/do_getxattr()/listxattr()/
> removexattr() syscall helpers take a struct mnt_idmap and a struct
> dentry even though 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 them all to take a struct path instead
> and derive the idmap and dentry from it where needed.
>
> __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 now pass the path they got from the
> previous patch straight through.
>
> 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.
>
> Assisted-by: opencode: glm-5.3
> Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
> ---
>  drivers/block/zloop.c               |   4 +-
>  fs/cachefiles/xattr.c               |  32 ++++----
>  fs/ecryptfs/inode.c                 |   8 +-
>  fs/nfsd/nfs4ctl.h                   |   4 +-
>  fs/nfsd/vfs.c                       |  57 +++++++-------
>  fs/overlayfs/copy_up.c              |   4 +-
>  fs/overlayfs/overlayfs.h            |  18 +++--
>  fs/overlayfs/xattrs.c               |  13 +++-
>  fs/smb/server/smb2pdu.c             |  75 +++++++------------
>  fs/smb/server/smb_common.c          |   2 -
>  fs/smb/server/smbacl.c              |  19 +++--
>  fs/smb/server/tests/smbacl_kunit.c  |   6 +-
>  fs/smb/server/vfs.c                 | 112 ++++++++++++----------------
>  fs/smb/server/vfs.h                 |  39 ++++------
>  fs/smb/server/vfs_cache.c           |   3 +-
>  fs/xattr.c                          |  92 ++++++++++++-----------
>  include/linux/xattr.h               |  22 +++---
>  security/integrity/evm/evm_crypto.c |   8 +-
>  security/selinux/hooks.c            |   4 +-
>  security/smack/smack_lsm.c          |   4 +-
>  20 files changed, 243 insertions(+), 283 deletions(-)
>

[...]

> 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 d915b87c0b06..c13f6b2c915c 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -291,8 +291,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(path, name, value, size);
>         len = (value && err > 0) ? err : 0;
>
>         pr_debug("getxattr(%pd2, \"%s\", \"%*pE\", %zu, 0) = %i\n",
> @@ -325,9 +324,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,
> -                              value, size, flags);
> +       int err = vfs_setxattr(&path, name, value, size, flags);
>
>         pr_debug("setxattr(%pd2, \"%s\", \"%*pE\", %zu, %d) = %i\n",
>                  dentry, name, min((int)size, 48), value, size, flags, err);
> @@ -344,7 +347,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(&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 5ae44b9c8790..b4658324b9f3 100644
> --- a/fs/overlayfs/xattrs.c
> +++ b/fs/overlayfs/xattrs.c
> @@ -45,7 +45,7 @@ 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(&realpath, name, NULL, 0);
>                 if (err < 0)
>                         goto out;
>         }
> @@ -86,7 +86,7 @@ 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(&realpath, name, value, size);
>  }
>
>  static bool ovl_can_list(struct super_block *sb, const char *s)
> @@ -105,15 +105,20 @@ 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;
>
> +       if (ovl_dentry_upper(dentry))
> +               ovl_path_upper(dentry, &realpath);
> +       else
> +               ovl_path_lower(dentry, &realpath);
> +

that's ovl_path_real()

I have no technical issue with the ovl patch bits in this series.
Anyway, I guess landlock is not going to enforce anything on the
private mnt with ovl_creds anyway?

Thanks,
Amir.

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

* Re: [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (11 preceding siblings ...)
  2026-09-24 10:48 ` [PATCH RFC -next 12/12] Documentation: Update landlock doc for metadata rights Cai Xinchen
@ 2026-09-25 15:33 ` Christian Brauner
  2026-09-25 17:07   ` Paul Moore
  2026-09-25 18:03 ` Justin Suess
  2026-09-26  8:27 ` Günther Noack
  14 siblings, 1 reply; 20+ messages in thread
From: Christian Brauner @ 2026-09-25 15:33 UTC (permalink / raw)
  To: Cai Xinchen, mic
  Cc: gnoack, paul, jmorris, serge, corbet, skhan, rdunlap, gregkh,
	rafael, dakr, dlemoal, hch, axboe, viro, jack, dhowells, code,
	linkinjeon, sj1557.seo, yuezhang.mo, hirofumi, cel, jlayton,
	neil, okorniev, Dai.Ngo, tom, miklos, amir73il, senozhatsky,
	chenxiaosong, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, nanx95726,
	djwong, daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4

On Thu, Sep 24, 2026 at 06:48:19PM +0800, Cai Xinchen wrote:
> 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 and POSIX
> ACLs.  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.

This is mostly fine for me. @Michael, for the fs side of things I would
give you a stable branch vfs-7.*.shared.landlock.path that you can pull
once this is ready.

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

* Re: [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-09-25 15:33 ` [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Christian Brauner
@ 2026-09-25 17:07   ` Paul Moore
  0 siblings, 0 replies; 20+ messages in thread
From: Paul Moore @ 2026-09-25 17:07 UTC (permalink / raw)
  To: Christian Brauner, Mickaël Salaün
  Cc: Cai Xinchen, gnoack, jmorris, serge, corbet, skhan, rdunlap,
	gregkh, rafael, dakr, dlemoal, hch, axboe, viro, jack, dhowells,
	code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi, cel,
	jlayton, neil, okorniev, Dai.Ngo, tom, miklos, amir73il,
	senozhatsky, chenxiaosong, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, nanx95726,
	djwong, daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4

On Fri, Sep 25, 2026 at 11:33 AM Christian Brauner <brauner@kernel.org> wrote:
> On Thu, Sep 24, 2026 at 06:48:19PM +0800, Cai Xinchen wrote:
> > 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 and POSIX
> > ACLs.  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.
>
> This is mostly fine for me. @Michael, for the fs side of things I would
> give you a stable branch vfs-7.*.shared.landlock.path that you can pull
> once this is ready.

This is in my queue, but I haven't reviewed the changes yet.

@Christian, I'm guessing you will merge patches 1/12, 3/13, and 4/12
since those are the VFS changes?  Once you have a branch for that, and
we have all the necessary ACKs, I'll merge the LSM changes in 2/12,
5/12, 6/12, and 7/12 and then Mickaël can merge the Landlock patches.

Are we all okay with that?

--
paul-moore.com

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

* Re: [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (12 preceding siblings ...)
  2026-09-25 15:33 ` [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Christian Brauner
@ 2026-09-25 18:03 ` Justin Suess
  2026-09-26  7:56   ` Günther Noack
  2026-09-26  8:27 ` Günther Noack
  14 siblings, 1 reply; 20+ messages in thread
From: Justin Suess @ 2026-09-25 18:03 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: mic, gnoack, paul, jmorris, serge, corbet, skhan, rdunlap,
	gregkh, rafael, dakr, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, miklos, amir73il,
	senozhatsky, chenxiaosong, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, nanx95726,
	djwong, daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4

On Thu, Sep 24, 2026 at 06:48:19PM +0800, Cai Xinchen wrote:
> 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 and POSIX
> ACLs.  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_getattr, inode_setattr, inode_setxattr,
> inode_getxattr, inode_listxattr, inode_removexattr, inode_set_acl,
> inode_get_acl, inode_remove_acl) only receive the dentry of the accessed
> object.  Patches 1-7 therefore first pass struct path instead of dentry
> through the metadata-related VFS helpers and LSM hooks.  This is a pure
> refactoring with no behavior change, split so that every patch builds
> and works on its own:
>
I like these patches, but is the ability to read metadata already
sorta controlled by LANDLOCK_ACCESS_FS_READ_DIR on the parent
directory?

The one case I see this being different is:

1. if you wanted to grant read access to the file, but not metadata
read access, but I can't think of any usecase for being able to read
the contents of a file, but not the metadata. (see below)

2. If you had the absolute path already and didn't need READ_DIR.

I see introducing this READ_METADATA as causing potential
hard-to-diagnose issues.

Say you handle READ_METADATA and READ_FILE, but only grant READ_FILE.

The program can technically open the file with the READ_FILE permission,
but it may error out because the stat() on it beforehand failed.
It's pretty common for programs to do that kind of thing (stat before
open), like for checking for config files (strace bash and you see it
stat .profile, /etc/profile)

There may be other bugs, because being able to set permissions to read
a file *but not read it's metadata* isn't possible currently in posix
acl and userspace may not work well if that assumption no longer holds.

So maybe WRITE_METADATA is good enough?

Interested in your thoughts,
Justin
>   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
>   6: inode xattr hooks
>   7: inode POSIX ACL hooks
> 
> Two deliberate scoping decisions for this refactor:
> 
> - The hooks consistently take struct path rather than struct file.  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.
> 
> - 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.
> 
> Patches 8-12 then implement the new rights, their tests, the sandboxer
> sample and the documentation.  Semantics:
> 
> - READ_METADATA covers stat(2) and friends, getxattr(2) and friends,
>   listxattr(2) and friends, and POSIX ACL reads.
> - WRITE_METADATA covers chmod(2), chown(2), utimensat(2), setxattr(2),
>   removexattr(2) and friends, and POSIX ACL set and remove.
> - Only explicit metadata changes requested by user space are restricted.
>   Implicit changes performed by the kernel (e.g. timestamp updates on
>   write(2), size changes on 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.
> - 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.
> 
> The Landlock ABI version is incremented from 11 to 12.
> 
> The series is based on linux-next commit 5c4d4169604b ("Add linux-next
> specific files for 20260921").
> 
> Testing: each patch has been built for aarch64 (gcc, -Werror) and the
> landlock selftests (445 tests, including the new ones) pass in QEMU on
> aarch64; base_test reports ABI v12.
> 
> [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/
> 
> Assisted-by: opencode: glm-5.3
> 
> Cai Xinchen (12):
>   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 to the inode_setattr hook
>   LSM: pass struct path to the inode xattr hooks
>   LSM: pass struct path to the inode posix acl hooks
>   landlock: Add READ_METADATA and WRITE_METADATA access rights
>   landlock: Implement metadata access hooks
>   selftests/landlock: Add tests for metadata access rights
>   samples/landlock: Add metadata rights to sandboxer
>   Documentation: Update landlock doc for metadata rights
> 
>  Documentation/userspace-api/landlock.rst     |  11 +-
>  drivers/base/devtmpfs.c                      |   6 +-
>  drivers/block/zloop.c                        |   4 +-
>  fs/attr.c                                    |  20 +-
>  fs/cachefiles/interface.c                    |   6 +-
>  fs/cachefiles/xattr.c                        |  32 +-
>  fs/coredump.c                                |   2 +-
>  fs/ecryptfs/inode.c                          |  34 +-
>  fs/exfat/file.c                              |   3 +-
>  fs/fat/file.c                                |   3 +-
>  fs/inode.c                                   |   7 +-
>  fs/internal.h                                |  17 +-
>  fs/namei.c                                   |   7 +-
>  fs/nfsd/nfs4ctl.h                            |   4 +-
>  fs/nfsd/nfs4state.c                          |  14 +-
>  fs/nfsd/nfs4xdr.c                            |   2 +-
>  fs/nfsd/state.h                              |   2 +-
>  fs/nfsd/vfs.c                                |  73 +++--
>  fs/open.c                                    |  18 +-
>  fs/overlayfs/copy_up.c                       |   4 +-
>  fs/overlayfs/inode.c                         |   4 +-
>  fs/overlayfs/overlayfs.h                     |  39 ++-
>  fs/overlayfs/xattrs.c                        |  13 +-
>  fs/posix_acl.c                               |  46 +--
>  fs/smb/server/smb2pdu.c                      |  77 ++---
>  fs/smb/server/smb_common.c                   |   2 -
>  fs/smb/server/smbacl.c                       |  21 +-
>  fs/smb/server/tests/smbacl_kunit.c           |   6 +-
>  fs/smb/server/vfs.c                          | 111 +++----
>  fs/smb/server/vfs.h                          |  39 +--
>  fs/smb/server/vfs_cache.c                    |   3 +-
>  fs/utimes.c                                  |   3 +-
>  fs/xattr.c                                   |  96 +++---
>  include/linux/fs.h                           |   6 +-
>  include/linux/landlock.h                     |   4 +-
>  include/linux/lsm_hook_defs.h                |  29 +-
>  include/linux/posix_acl.h                    |  21 +-
>  include/linux/security.h                     |  65 ++--
>  include/linux/xattr.h                        |  22 +-
>  include/uapi/linux/landlock.h                |  26 +-
>  samples/landlock/sandboxer.c                 |  17 +-
>  security/commoncap.c                         |  22 +-
>  security/integrity/evm/evm_crypto.c          |   8 +-
>  security/integrity/evm/evm_main.c            |  36 ++-
>  security/integrity/ima/ima_appraise.c        |  17 +-
>  security/landlock/fs.c                       |  86 ++++++
>  security/landlock/limits.h                   |   2 +-
>  security/landlock/syscalls.c                 |   2 +-
>  security/security.c                          |  99 +++---
>  security/selinux/hooks.c                     |  49 +--
>  security/smack/smack_lsm.c                   |  62 ++--
>  tools/testing/selftests/landlock/base_test.c |   2 +-
>  tools/testing/selftests/landlock/fs_test.c   | 309 ++++++++++++++++++-
>  53 files changed, 999 insertions(+), 614 deletions(-)
> 
> -- 
> 2.18.0.huawei.25
> 
> 

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

* Re: [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-09-25 18:03 ` Justin Suess
@ 2026-09-26  7:56   ` Günther Noack
  0 siblings, 0 replies; 20+ messages in thread
From: Günther Noack @ 2026-09-26  7:56 UTC (permalink / raw)
  To: Justin Suess
  Cc: Cai Xinchen, mic, gnoack, paul, jmorris, serge, corbet, skhan,
	rdunlap, gregkh, rafael, dakr, dlemoal, hch, axboe, viro,
	brauner, jack, dhowells, code, linkinjeon, sj1557.seo,
	yuezhang.mo, hirofumi, cel, jlayton, neil, okorniev, Dai.Ngo,
	tom, miklos, amir73il, senozhatsky, chenxiaosong, zohar,
	roberto.sassu, dmitry.kasatkin, eric.snowberg,
	stephen.smalley.work, omosnacek, casey, nanx95726, djwong,
	daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4

Hello!

On Fri, Sep 25, 2026 at 02:03:05PM -0400, Justin Suess wrote:
> On Thu, Sep 24, 2026 at 06:48:19PM +0800, Cai Xinchen wrote:
> > 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 and POSIX
> > ACLs.  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_getattr, inode_setattr, inode_setxattr,
> > inode_getxattr, inode_listxattr, inode_removexattr, inode_set_acl,
> > inode_get_acl, inode_remove_acl) only receive the dentry of the accessed
> > object.  Patches 1-7 therefore first pass struct path instead of dentry
> > through the metadata-related VFS helpers and LSM hooks.  This is a pure
> > refactoring with no behavior change, split so that every patch builds
> > and works on its own:
> >
> I like these patches, but is the ability to read metadata already
> sorta controlled by LANDLOCK_ACCESS_FS_READ_DIR on the parent
> directory?
> 
> The one case I see this being different is:
> 
> 1. if you wanted to grant read access to the file, but not metadata
> read access, but I can't think of any usecase for being able to read
> the contents of a file, but not the metadata. (see below)
> 
> 2. If you had the absolute path already and didn't need READ_DIR.
> 
> I see introducing this READ_METADATA as causing potential
> hard-to-diagnose issues.
> 
> Say you handle READ_METADATA and READ_FILE, but only grant READ_FILE.
> 
> The program can technically open the file with the READ_FILE permission,
> but it may error out because the stat() on it beforehand failed.
> It's pretty common for programs to do that kind of thing (stat before
> open), like for checking for config files (strace bash and you see it
> stat .profile, /etc/profile)
> 
> There may be other bugs, because being able to set permissions to read
> a file *but not read it's metadata* isn't possible currently in posix
> acl and userspace may not work well if that assumption no longer holds.
> 
> So maybe WRITE_METADATA is good enough?

The existing use cases are the combinations of (a) READ_DIR
allowed/denied and (b) READ_METADATA allowed/denied.  Because these
two access rights overlap slightly, it seems likely that for a given
directory or file, users will want to either grant both, or deny both.

At the moment, where the (not yet existing) READ_METADATA is
implicitly always allowed, the problematic case is the one where the
Landlock user wants to deny READ_DIR, but where much of the same
metadata is still available through stat() and the various
get-attribute syscalls.  (c.f. the warning box in the Landlock docs
[1])

In my view the READ_METADATA right closes a gap that READ_DIR left
open (which is also potentially surprising to callers if they did not
read the docs closely).  Also, if its implementation is symmetric to
WRITE_METADATA, I feel that it's worth having it in the same patch
set.

–Günther

P.S.: I know, even after we can control stat(), there are likely ways
to infer the presence of a file by observing Landlock error codes.
This would be nice to fix as well, but is harder to do without
controlling the path walk itself [2].  But also, the fact that this is
currently not controllable is not an excuse for leaving READ_METADATA
open IMHO.

[1] https://docs.kernel.org/userspace-api/landlock.html#filesystem-flags
[2] https://github.com/landlock-lsm/linux/issues/9

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

* Re: [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights
  2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
                   ` (13 preceding siblings ...)
  2026-09-25 18:03 ` Justin Suess
@ 2026-09-26  8:27 ` Günther Noack
  14 siblings, 0 replies; 20+ messages in thread
From: Günther Noack @ 2026-09-26  8:27 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: mic, gnoack, paul, jmorris, serge, corbet, skhan, rdunlap,
	gregkh, rafael, dakr, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, miklos, amir73il,
	senozhatsky, chenxiaosong, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, nanx95726,
	djwong, daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4, bpf, kpsingh, matt,
	alexei.starovoitov

On Thu, Sep 24, 2026 at 06:48:19PM +0800, Cai Xinchen wrote:
> 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 and POSIX
> ACLs.  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_getattr, inode_setattr, inode_setxattr,
> inode_getxattr, inode_listxattr, inode_removexattr, inode_set_acl,
> inode_get_acl, inode_remove_acl) only receive the dentry of the accessed
> object.  Patches 1-7 therefore first pass struct path instead of dentry
> through the metadata-related VFS helpers and LSM hooks.  This is a pure
> refactoring with no behavior change, split so that every patch builds
> and works 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
>   6: inode xattr hooks
>   7: inode POSIX ACL hooks
> 
> Two deliberate scoping decisions for this refactor:
> 
> - The hooks consistently take struct path rather than struct file.  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.
> 
> - 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.
> 
> Patches 8-12 then implement the new rights, their tests, the sandboxer
> sample and the documentation.  Semantics:
> 
> - READ_METADATA covers stat(2) and friends, getxattr(2) and friends,
>   listxattr(2) and friends, and POSIX ACL reads.
> - WRITE_METADATA covers chmod(2), chown(2), utimensat(2), setxattr(2),
>   removexattr(2) and friends, and POSIX ACL set and remove.
> - Only explicit metadata changes requested by user space are restricted.
>   Implicit changes performed by the kernel (e.g. timestamp updates on
>   write(2), size changes on 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.
> - 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.
> 
> The Landlock ABI version is incremented from 11 to 12.
> 
> The series is based on linux-next commit 5c4d4169604b ("Add linux-next
> specific files for 20260921").
> 
> Testing: each patch has been built for aarch64 (gcc, -Werror) and the
> landlock selftests (445 tests, including the new ones) pass in QEMU on
> aarch64; base_test reports ABI v12.
> 
> [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/

Thank you for sending this patch set!

Some meta-remarks at the beginning:

* You might want to link the bugtracker feature request:
  https://github.com/landlock-lsm/linux/issues/11
* In the final version, I think it's preferred to merge patches 8
  (adding the access right enums) and 9 (adding the LSM hooks that use
  them).  Having the feature as an atomic commit makes it harder to
  accidentally mess it up during a backport, because you can't patch 8
  without 9.
* As Paul alluded to, the changes to the LSM hook interface and to the
  existing callers in VFS are likely the hardest part of this patch
  set.  Alexei from the BPF subsystem has also reiterated recently
  that he wants BPF to be looped into such changes.  BPF hooks do not
  give the same backwards compatibility guarantees as the syscall
  layer, but there are existing users of LSM hooks specifically
  through the BPF LSM.

* In https://github.com/landlock-lsm/linux/issues/18, we came across
  statfs(), which returns file system meta-information based for the
  file system that a given file belongs to.  I have weak confidence
  that READ_METADATA would be the right access right to protect this
  with, but it's a somewhat related operation.  Maybe you have some
  thoughts on this?

More concrete questions:

* If a "inode" LSM hook gets a "path" argument now, should it be
  renamed from "inode_..." to "path_..."?

  (Maybe the BPF people can chime in about to what extent that would
  cause additional churn for BPF users, in a situation where they
  anyway already need to make a change due to the changing function
  signature?)

–Günther

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

* Re: [PATCH RFC -next 09/12] landlock: Implement metadata access hooks
  2026-09-24 10:48 ` [PATCH RFC -next 09/12] landlock: Implement metadata access hooks Cai Xinchen
@ 2026-09-26  8:38   ` Günther Noack
  0 siblings, 0 replies; 20+ messages in thread
From: Günther Noack @ 2026-09-26  8:38 UTC (permalink / raw)
  To: Cai Xinchen
  Cc: mic, gnoack, paul, jmorris, serge, corbet, skhan, rdunlap,
	gregkh, rafael, dakr, dlemoal, hch, axboe, viro, brauner, jack,
	dhowells, code, linkinjeon, sj1557.seo, yuezhang.mo, hirofumi,
	cel, jlayton, neil, okorniev, Dai.Ngo, tom, miklos, amir73il,
	senozhatsky, chenxiaosong, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, stephen.smalley.work, omosnacek, casey, nanx95726,
	djwong, daniel, linux-security-module, linux-doc, linux-kernel,
	driver-core, linux-block, linux-fsdevel, netfs, ecryptfs, exfat,
	linux-nfs, linux-unionfs, linux-cifs, linux-integrity, selinux,
	linux-kselftest, xiujianfeng, lujialin4

On Thu, Sep 24, 2026 at 06:48:28PM +0800, Cai Xinchen wrote:
> Implement the LANDLOCK_ACCESS_FS_READ_METADATA and
> LANDLOCK_ACCESS_FS_WRITE_METADATA access rights by hooking the
> inode_getattr, inode_setattr, inode_setxattr, inode_getxattr,
> inode_listxattr, inode_removexattr, inode_set_acl, inode_get_acl and
> inode_remove_acl LSM hooks, which now receive a struct path thanks to
> the preceding VFS and LSM refactoring.
> 
> The following system calls are now controlled:
> 
> - stat(2), fstat(2), lstat(2), newfstatat(2), getxattr(2) and
>   friends, listxattr(2) and friends, and POSIX ACL reads via
>   inode_getattr, inode_getxattr, inode_listxattr and inode_get_acl
>   (READ_METADATA)
> - chmod(2), fchmod(2), fchmodat(2), fchmodat2(2), chown(2), fchown(2),
>   lchown(2), fchownat(2), chgrp(2), utimensat(2), futimens(2),
>   utime(2), setxattr(2) and friends, removexattr(2) and friends, and
>   POSIX ACL set and remove via inode_setattr, inode_setxattr,
>   inode_removexattr, inode_set_acl and inode_remove_acl
>   (WRITE_METADATA)
> 
> Both new rights are added to ACCESS_FILE as they apply to both files
> and directories.
> 
> hook_inode_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, and neither are chmod(2)/chown(2) calls
> that do not change any attribute (e.g. chown(2) with -1/-1, which is
> a no-op that never reaches the hook), matching the behavior of the
> SELinux inode_setattr hook.
> 
> 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.
> 
> Assisted-by: opencode: glm-5.3
> Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
> ---
>  security/landlock/fs.c | 86 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 86 insertions(+)
> 
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index cab43892ec2f..e58b2aa0da65 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,81 @@ static int hook_path_truncate(const struct path *const path)
>  	return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
>  }
>  
> +static int hook_inode_getattr(const struct path *const path)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_READ_METADATA);
> +}
> +
> +static int hook_inode_setattr(const struct path *const path,
> +			      struct iattr *const attr)
> +{
> +	/*
> +	 * Explicit metadata changes (i.e. mode, ownership, and timestamps
> +	 * set with utimes() and friends) require
> +	 * LANDLOCK_ACCESS_FS_WRITE_METADATA.  Implicit timestamp updates
> +	 * (e.g. ATTR_CTIME set for a write) and size changes (handled by
> +	 * the truncate hooks) are not restricted.

Nit: It feels like this comment about implicit timestamp updates
(especially the size change) should go in the top-level documentation
for the WRITE_METADATA right?  setattr() can not result in a size
change, after all, AFAIK?

Remark on the side, apart from truncation, normal writes into the file
can of course also change its size ;-) and ATTR_ATIME and ATTR_MTIME
also come to mind as implicit metadata changes.

> +	 */
> +	if (!(attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
> +				ATTR_TIMES_SET | ATTR_TOUCH)))
> +		return 0;
> +
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_WRITE_METADATA);
> +}
> +
> +static int hook_inode_setxattr(const struct path *const path,
> +			       const char *const name,
> +			       const void *const value, const size_t size,
> +			       const int flags)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_WRITE_METADATA);
> +}
> +
> +static int hook_inode_getxattr(const struct path *const path,
> +			       const char *const name)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_READ_METADATA);
> +}
> +
> +static int hook_inode_listxattr(const struct path *const path)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_READ_METADATA);
> +}
> +
> +static int hook_inode_removexattr(const struct path *const path,
> +				  const char *const name)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_WRITE_METADATA);
> +}
> +
> +static int hook_inode_set_acl(const struct path *const path,
> +			      const char *const acl_name,
> +			      struct posix_acl *const kacl)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_WRITE_METADATA);
> +}
> +
> +static int hook_inode_get_acl(const struct path *const path,
> +			      const char *const acl_name)
> +{
> +	return current_check_access_path(path,
> +					LANDLOCK_ACCESS_FS_READ_METADATA);
> +}
> +
> +static int hook_inode_remove_acl(const struct path *const path,
> +				 const char *const acl_name)
> +{
> +	return current_check_access_path(path,
> +					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
> @@ -2100,6 +2177,15 @@ 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(inode_getattr, hook_inode_getattr),
> +	LSM_HOOK_INIT(inode_setattr, hook_inode_setattr),
> +	LSM_HOOK_INIT(inode_setxattr, hook_inode_setxattr),
> +	LSM_HOOK_INIT(inode_getxattr, hook_inode_getxattr),
> +	LSM_HOOK_INIT(inode_listxattr, hook_inode_listxattr),
> +	LSM_HOOK_INIT(inode_removexattr, hook_inode_removexattr),
> +	LSM_HOOK_INIT(inode_set_acl, hook_inode_set_acl),
> +	LSM_HOOK_INIT(inode_get_acl, hook_inode_get_acl),
> +	LSM_HOOK_INIT(inode_remove_acl, hook_inode_remove_acl),
>  	LSM_HOOK_INIT(unix_find, hook_unix_find),
>  
>  	LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
> -- 
> 2.18.0.huawei.25
> 

On the Landlock side, the implementation looks quite straightforward,
without having double checked for missing hooks now.  This commit is
probably fine as soon as we have agreement on the LSM hook changes.

–Günther

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

end of thread, other threads:[~2026-09-26  8:38 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 10:48 [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 01/12] fs: pass struct path to notify_change() Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 02/12] LSM: pass struct path to the inode_setsecctx hook Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 03/12] fs: pass struct path to xattr helpers Cai Xinchen
2026-09-24 11:12   ` Amir Goldstein
2026-09-24 10:48 ` [PATCH RFC -next 04/12] fs: pass struct path to POSIX ACL helpers Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 05/12] LSM: pass struct path to the inode_setattr hook Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 06/12] LSM: pass struct path to the inode xattr hooks Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 07/12] LSM: pass struct path to the inode posix acl hooks Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 08/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 09/12] landlock: Implement metadata access hooks Cai Xinchen
2026-09-26  8:38   ` Günther Noack
2026-09-24 10:48 ` [PATCH RFC -next 10/12] selftests/landlock: Add tests for metadata access rights Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 11/12] samples/landlock: Add metadata rights to sandboxer Cai Xinchen
2026-09-24 10:48 ` [PATCH RFC -next 12/12] Documentation: Update landlock doc for metadata rights Cai Xinchen
2026-09-25 15:33 ` [PATCH RFC -next 00/12] landlock: Add READ_METADATA and WRITE_METADATA access rights Christian Brauner
2026-09-25 17:07   ` Paul Moore
2026-09-25 18:03 ` Justin Suess
2026-09-26  7:56   ` Günther Noack
2026-09-26  8:27 ` Günther Noack

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

all inboxes | Powered by JetHome®