All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/15] smb: add native security and trusted xattrs
@ 2026-07-24 10:39 Ze Tan
  2026-07-24 10:39 ` [PATCH 01/15] ksmbd: extract SMB EA backing xattr name mapping Ze Tan
                   ` (15 more replies)
  0 siblings, 16 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:39 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

The CIFS client and ksmbd historically carry SMB extended attributes
through the Linux user.* namespace. The client removes "user." from an
EA name before sending it, and ksmbd adds the prefix when selecting the
backing xattr.

That mapping cannot provide native Linux semantics for
security.capability, security.xfstests, or trusted.*. For example,
security.capability stored as user.security.capability is not treated as
a file capability and is not removed by the VFS killpriv path after a
write.

These xattrs are needed by generic/093:

  - setcap and getcap use security.capability;
  - _require_attrs security uses security.xfstests;
  - setfattr and getfattr use trusted.name.

This series adds an opt-in native mapping for those names. The default
non-POSIX mapping remains unchanged. On a mount where SMB3 POSIX
extensions have been requested and negotiated, the client exposes:

  security.capability
  security.xfstests
  trusted.*

The client sends these names without a user prefix. In this mode it
rejects user.security.capability, user.security.xfstests, and
user.trusted.*. Other security.* names are not enabled.

ksmbd activates the native mapping only for a handle opened with an
SMB3 POSIX create context. The mapped fsuid must be root, or the tree
connection must have KSMBD_TREE_CONN_FLAG_ADMIN_ACCOUNT. An
unauthorized native request fails instead of falling back to user.*.

Native xattrs are returned without a prefix when EAs are listed. Their
corresponding user.* backing xattrs are hidden in native mode. The
internal XFS trusted.SGI_ACL_FILE and trusted.SGI_ACL_DEFAULT xattrs
remain unsupported.

No new SMB negotiate context or capability bit is added in this
version. The existing -o posix opt-in and per-open SMB3 POSIX create
context are reused. One question for review is whether that is
sufficient, or whether native xattrs require a dedicated protocol
capability to distinguish patched servers from other POSIX-capable
servers.

Writing a file with security.capability also exercises the VFS killpriv
path. That path can issue EA queries and removals while the original
write handle is active. A temporary EA open can otherwise break the
write handle's caching state and wait for the blocked write path.

The series is arranged as follows:

  - patches 1-2 separate ksmbd EA name conversion from request and
    response construction;
  - patches 3-6 fix exact-name lookup, empty and disappearing EAs,
    CREATE-context mount write access, and bounded debug output;
  - patches 7-10 add the authorized native mappings to ksmbd;
  - patches 11-12 prepare lease reuse and the no-lease oplock fallback;
  - patches 13-15 enable the native names in the CIFS client.

The client and server changes are kept in one RFC so the end-to-end
mapping, authorization, and protocol activation can be reviewed
together. They can be split for merging after the interface is agreed.

Testing
=======

The test and scratch shares were mounted with an SMB account mapped to
server root or an admin account. Both xfstests mount option variables
included:

  vers=3.1.1,posix,idsfromsid,uid=<fsgqa-uid>,gid=<fsgqa-gid>

generic/093 was run in all three ksmbd configurations:

  1. oplocks = yes, smb2 leases = yes
  2. oplocks = yes, smb2 leases = no
  3. oplocks = no

The test passed in all three configurations.

The following cases were also checked manually:

  - security.xfstests set and retrieved on the client, and appeared as
    security.xfstests on the server backing filesystem;
  - security.capability set through setcap and removed after a write;
  - trusted.name remained present after the same write;
  - the server backing filesystem stored trusted.name without a user
    prefix.

Changes since RFC v2
--------------------

  - Keep the default user EA mapping unchanged and require -o posix for
    native client names.
  - Require a per-open SMB3 POSIX create context and a root or admin
    server mapping.
  - Reject colliding user.* spellings instead of silently downgrading
    native requests.
  - Use tables for exact native names, native prefixes, and unsupported
    names.
  - Add the ksmbd EA correctness fixes found while reviewing the native
    mapping paths.
  - Test generic/093 with oplocks disabled, and with oplocks enabled
    both with and without SMB2 leases.


RFC v2:
https://lore.kernel.org/linux-cifs/cover.1784258165.git.tanze@kylinos.cn/

RFC v1:
https://lore.kernel.org/all/20260715074908.641940-1-tanze@kylinos.cn/


Ze Tan (15):
  ksmbd: extract SMB EA backing xattr name mapping
  ksmbd: extract SMB EA response name handling
  ksmbd: match SMB2 EA names by exact length
  ksmbd: handle empty and disappearing EAs
  ksmbd: take mount write access for CREATE EAs
  ksmbd: bound SMB2 EA name debug output
  ksmbd: query security.capability on POSIX EA handles
  ksmbd: set security.capability on POSIX EA handles
  ksmbd: support security.xfstests on POSIX EA handles
  ksmbd: support trusted xattrs on POSIX EA handles
  smb: client: prepare EA opens for inode lease reuse
  smb: client: avoid batch oplocks for reentrant POSIX EAs
  smb: client: gate security.capability EA on POSIX mounts
  smb: client: support security.xfstests on POSIX mounts
  smb: client: support trusted EAs on POSIX mounts

 fs/smb/client/cifsfs.h    |   8 ++
 fs/smb/client/cifsglob.h  |   5 +-
 fs/smb/client/cifssmb.c   |   7 +-
 fs/smb/client/inode.c     |   2 +-
 fs/smb/client/smb1ops.c   |   4 +-
 fs/smb/client/smb1proto.h |   5 +-
 fs/smb/client/smb2file.c  |  18 +++
 fs/smb/client/smb2ops.c   |  56 +++++++---
 fs/smb/client/smb2proto.h |   3 +-
 fs/smb/client/xattr.c     | 156 ++++++++++++++++++++++++--
 fs/smb/server/smb2pdu.c   | 229 ++++++++++++++++++++++++++++++--------
 11 files changed, 413 insertions(+), 80 deletions(-)


base-commit: 4a03a16a93001f5af67911b722c4d2f7b2c313f8
-- 
2.43.0


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

* [PATCH 01/15] ksmbd: extract SMB EA backing xattr name mapping
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
@ 2026-07-24 10:39 ` Ze Tan
  2026-07-24 10:39 ` [PATCH 02/15] ksmbd: extract SMB EA response name handling Ze Tan
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:39 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

smb2_set_ea() currently prefixes each wire EA name with "user." before
calling the VFS xattr helpers. Move this translation into
ksmbd_map_ea_name_to_xattr() so native namespace mappings can be added
without mixing them with EA request parsing.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 27 ++++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index bec692bca1ca..0cef7dab453a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2643,6 +2643,18 @@ static noinline int create_smb2_pipe(struct ksmbd_work *work)
 	return err;
 }
 
+static int ksmbd_map_ea_name_to_xattr(const char *ea_name,
+				      size_t ea_name_len, char *attr_name)
+{
+	if (ea_name_len > XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)
+		return -EINVAL;
+
+	memcpy(attr_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
+	memcpy(&attr_name[XATTR_USER_PREFIX_LEN], ea_name, ea_name_len);
+	attr_name[XATTR_USER_PREFIX_LEN + ea_name_len] = '\0';
+	return XATTR_USER_PREFIX_LEN + ea_name_len;
+}
+
 /**
  * smb2_set_ea() - handler for setting extended attributes using set
  *		info command
@@ -2658,7 +2670,7 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 {
 	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	char *attr_name = NULL, *value;
-	int rc = 0;
+	int rc = 0, attr_name_len;
 	unsigned int next = 0;
 
 	if (buf_len < sizeof(struct smb2_ea_info) + eabuf->EaNameLength + 1 +
@@ -2679,24 +2691,21 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 			    le16_to_cpu(eabuf->EaValueLength),
 			    le32_to_cpu(eabuf->NextEntryOffset));
 
-		if (eabuf->EaNameLength >
-		    (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) {
+		attr_name_len = ksmbd_map_ea_name_to_xattr(eabuf->name,
+							   eabuf->EaNameLength,
+							   attr_name);
+		if (attr_name_len < 0) {
 			rc = -EINVAL;
 			break;
 		}
 
-		memcpy(attr_name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
-		memcpy(&attr_name[XATTR_USER_PREFIX_LEN], eabuf->name,
-		       eabuf->EaNameLength);
-		attr_name[XATTR_USER_PREFIX_LEN + eabuf->EaNameLength] = '\0';
 		value = (char *)&eabuf->name + eabuf->EaNameLength + 1;
 
 		if (!eabuf->EaValueLength) {
 			rc = ksmbd_vfs_casexattr_len(idmap,
 						     path->dentry,
 						     attr_name,
-						     XATTR_USER_PREFIX_LEN +
-						     eabuf->EaNameLength);
+						     attr_name_len);
 
 			/* delete the EA only when it exits */
 			if (rc > 0) {
-- 
2.43.0


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

* [PATCH 02/15] ksmbd: extract SMB EA response name handling
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
  2026-07-24 10:39 ` [PATCH 01/15] ksmbd: extract SMB EA backing xattr name mapping Ze Tan
@ 2026-07-24 10:39 ` Ze Tan
  2026-07-24 10:39 ` [PATCH 03/15] ksmbd: match SMB2 EA names by exact length Ze Tan
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:39 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

smb2_get_ea() exposes only backing xattrs in the user namespace and
removes the "user." prefix from their SMB EA names. Move that filtering
and name conversion into ksmbd_is_visible_ea_name() so response
construction is independent of namespace policy.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 53 ++++++++++++++++++++++-------------------
 1 file changed, 29 insertions(+), 24 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 0cef7dab453a..baa6e8f0eba4 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2655,6 +2655,27 @@ static int ksmbd_map_ea_name_to_xattr(const char *ea_name,
 	return XATTR_USER_PREFIX_LEN + ea_name_len;
 }
 
+static bool ksmbd_is_visible_ea_name(const char *name, const char **ea_name,
+				     size_t *ea_name_len)
+{
+	size_t name_len = strlen(name);
+
+	if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+		return false;
+
+	*ea_name = name + XATTR_USER_PREFIX_LEN;
+	*ea_name_len = name_len - XATTR_USER_PREFIX_LEN;
+
+	if (!strncmp(*ea_name, STREAM_PREFIX, STREAM_PREFIX_LEN))
+		return false;
+
+	if (!strncmp(*ea_name, DOS_ATTRIBUTE_PREFIX,
+		     DOS_ATTRIBUTE_PREFIX_LEN))
+		return false;
+
+	return true;
+}
+
 /**
  * smb2_set_ea() - handler for setting extended attributes using set
  *		info command
@@ -5203,36 +5224,25 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 	idx = 0;
 
 	while (idx < xattr_list_len) {
+		const char *ea_name;
+		size_t visible_name_len;
+
 		name = xattr_list + idx;
 		name_len = strlen(name);
 
 		ksmbd_debug(SMB, "%s, len %d\n", name, name_len);
 		idx += name_len + 1;
 
-		/*
-		 * CIFS does not support EA other than user.* namespace,
-		 * still keep the framework generic, to list other attrs
-		 * in future.
-		 */
-		if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
+		if (!ksmbd_is_visible_ea_name(name, &ea_name,
+					      &visible_name_len))
 			continue;
 
-		if (!strncmp(&name[XATTR_USER_PREFIX_LEN], STREAM_PREFIX,
-			     STREAM_PREFIX_LEN))
-			continue;
+		name_len = visible_name_len;
 
 		if (req->InputBufferLength &&
-		    strncmp(&name[XATTR_USER_PREFIX_LEN], ea_req->name,
-			    ea_req->EaNameLength))
+		    strncmp(ea_name, ea_req->name, ea_req->EaNameLength))
 			continue;
 
-		if (!strncmp(&name[XATTR_USER_PREFIX_LEN],
-			     DOS_ATTRIBUTE_PREFIX, DOS_ATTRIBUTE_PREFIX_LEN))
-			continue;
-
-		if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
-			name_len -= XATTR_USER_PREFIX_LEN;
-
 		ptr = eainfo->name + name_len + 1;
 		buf_free_len -= (offsetof(struct smb2_ea_info, name) +
 				name_len + 1);
@@ -5257,12 +5267,7 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 		ptr += value_len;
 		eainfo->Flags = 0;
 		eainfo->EaNameLength = name_len;
-
-		if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
-			memcpy(eainfo->name, &name[XATTR_USER_PREFIX_LEN],
-			       name_len);
-		else
-			memcpy(eainfo->name, name, name_len);
+		memcpy(eainfo->name, ea_name, name_len);
 
 		eainfo->name[name_len] = '\0';
 		eainfo->EaValueLength = cpu_to_le16(value_len);
-- 
2.43.0


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

* [PATCH 03/15] ksmbd: match SMB2 EA names by exact length
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
  2026-07-24 10:39 ` [PATCH 01/15] ksmbd: extract SMB EA backing xattr name mapping Ze Tan
  2026-07-24 10:39 ` [PATCH 02/15] ksmbd: extract SMB EA response name handling Ze Tan
@ 2026-07-24 10:39 ` Ze Tan
  2026-07-24 10:39 ` [PATCH 04/15] ksmbd: handle empty and disappearing EAs Ze Tan
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:39 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

smb2_get_ea() uses the requested name length as the comparison bound.
That also matches a longer EA whose name starts with the requested
bytes. Compare equal-length names so a request for "foo" cannot return
the value of "foobar".

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index baa6e8f0eba4..2131625dbc2d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -5240,7 +5240,8 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 		name_len = visible_name_len;
 
 		if (req->InputBufferLength &&
-		    strncmp(ea_name, ea_req->name, ea_req->EaNameLength))
+		    (name_len != ea_req->EaNameLength ||
+		     memcmp(ea_name, ea_req->name, name_len)))
 			continue;
 
 		ptr = eainfo->name + name_len + 1;
-- 
2.43.0


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

* [PATCH 04/15] ksmbd: handle empty and disappearing EAs
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (2 preceding siblings ...)
  2026-07-24 10:39 ` [PATCH 03/15] ksmbd: match SMB2 EA names by exact length Ze Tan
@ 2026-07-24 10:39 ` Ze Tan
  2026-07-24 10:39 ` [PATCH 05/15] ksmbd: take mount write access for CREATE EAs Ze Tan
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:39 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

A zero-length xattr value is valid, but smb2_get_ea() currently treats
every non-positive result from ksmbd_vfs_getxattr() as a missing EA.
Accept zero and encode an empty value in the response.

An xattr can also disappear between listxattr() and getxattr(). Skip an
entry that returns -ENODATA without consuming response space, while
preserving other errors.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 2131625dbc2d..39a72cda6704 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -5245,24 +5245,26 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 			continue;
 
 		ptr = eainfo->name + name_len + 1;
-		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);
-		if (value_len <= 0) {
-			rc = -ENOENT;
+		if (value_len == -ENODATA)
+			continue;
+		if (value_len < 0) {
+			rc = value_len;
 			rsp->hdr.Status = STATUS_INVALID_HANDLE;
 			goto out;
 		}
 
+		buf_free_len -= (offsetof(struct smb2_ea_info, name) +
+				name_len + 1);
 		buf_free_len -= value_len;
 		if (buf_free_len < 0) {
 			kfree(buf);
 			break;
 		}
 
-		memcpy(ptr, buf, value_len);
+		if (value_len)
+			memcpy(ptr, buf, value_len);
 		kfree(buf);
 
 		ptr += value_len;
-- 
2.43.0


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

* [PATCH 05/15] ksmbd: take mount write access for CREATE EAs
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (3 preceding siblings ...)
  2026-07-24 10:39 ` [PATCH 04/15] ksmbd: handle empty and disappearing EAs Ze Tan
@ 2026-07-24 10:39 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 06/15] ksmbd: bound SMB2 EA name debug output Ze Tan
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:39 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

smb2_open() applies EAs from an SMB2 CREATE context after creating the
path, but calls smb2_set_ea() without requesting mount write access.
The SET_INFO path already requests it before the same VFS xattr
operations.

Pass get_write=true for CREATE-context EAs so setxattr and removexattr
run with mount write access in both paths.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 39a72cda6704..dd707f67378b 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3694,7 +3694,7 @@ int smb2_open(struct ksmbd_work *work)
 
 			rc = smb2_set_ea(&ea_buf->ea,
 					 le32_to_cpu(ea_buf->ccontext.DataLength),
-					 &path, false);
+					 &path, true);
 			if (rc == -EOPNOTSUPP)
 				rc = 0;
 			else if (rc)
-- 
2.43.0


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

* [PATCH 06/15] ksmbd: bound SMB2 EA name debug output
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (4 preceding siblings ...)
  2026-07-24 10:39 ` [PATCH 05/15] ksmbd: take mount write access for CREATE EAs Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 07/15] ksmbd: query security.capability on POSIX EA handles Ze Tan
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

SMB2 EA names carry an explicit length, but the debug print uses %s and
therefore trusts the client-provided terminator. A malformed entry can
make it read into the value or past the request buffer.

Use EaNameLength as the printk field width.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index dd707f67378b..1f0872bf11ae 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2707,8 +2707,9 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 			goto next;
 
 		ksmbd_debug(SMB,
-			    "name : <%s>, name_len : %u, value_len : %u, next : %u\n",
-			    eabuf->name, eabuf->EaNameLength,
+			    "name : <%.*s>, name_len : %u, value_len : %u, next : %u\n",
+			    eabuf->EaNameLength, eabuf->name,
+			    eabuf->EaNameLength,
 			    le16_to_cpu(eabuf->EaValueLength),
 			    le32_to_cpu(eabuf->NextEntryOffset));
 
-- 
2.43.0


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

* [PATCH 07/15] ksmbd: query security.capability on POSIX EA handles
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (5 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 06/15] ksmbd: bound SMB2 EA name debug output Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 08/15] ksmbd: set " Ze Tan
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Treat security.capability as a native xattr only for a file handle
opened with an SMB3 POSIX create context. Non-POSIX handles retain the
existing user namespace mapping.

Allow native queries when the mapped fsuid is root or the tree
connection has KSMBD_TREE_CONN_FLAG_ADMIN_ACCOUNT. In native mode,
return security.capability without a prefix and hide a colliding
user.security.capability backing xattr. Reject embedded NUL bytes before
classifying a requested SMB EA name.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 80 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 68 insertions(+), 12 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 1f0872bf11ae..7fdc6df6310a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2643,6 +2643,43 @@ static noinline int create_smb2_pipe(struct ksmbd_work *work)
 	return err;
 }
 
+static const char * const ksmbd_native_xattr_names[] = {
+	XATTR_NAME_CAPS,
+};
+
+static bool ksmbd_native_xattrs_allowed(struct ksmbd_work *work)
+{
+	return uid_eq(current_fsuid(), GLOBAL_ROOT_UID) ||
+	       test_tree_conn_flag(work->tcon,
+				   KSMBD_TREE_CONN_FLAG_ADMIN_ACCOUNT);
+}
+
+static int ksmbd_check_native_xattr(struct ksmbd_work *work,
+				    const char *name, size_t name_len)
+{
+	bool native = false;
+	size_t i;
+
+	if (memchr(name, '\0', name_len))
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(ksmbd_native_xattr_names); i++) {
+		size_t xattr_len = strlen(ksmbd_native_xattr_names[i]);
+
+		if (name_len == xattr_len &&
+		    !memcmp(name, ksmbd_native_xattr_names[i], name_len)) {
+			native = true;
+			break;
+		}
+	}
+
+	if (!native)
+		return 0;
+	if (!ksmbd_native_xattrs_allowed(work))
+		return -EACCES;
+	return 1;
+}
+
 static int ksmbd_map_ea_name_to_xattr(const char *ea_name,
 				      size_t ea_name_len, char *attr_name)
 {
@@ -2655,24 +2692,36 @@ static int ksmbd_map_ea_name_to_xattr(const char *ea_name,
 	return XATTR_USER_PREFIX_LEN + ea_name_len;
 }
 
-static bool ksmbd_is_visible_ea_name(const char *name, const char **ea_name,
-				     size_t *ea_name_len)
+static bool ksmbd_is_visible_ea_name(struct ksmbd_work *work,
+				     bool native_xattrs, const char *name,
+				     const char **ea_name, size_t *ea_name_len)
 {
 	size_t name_len = strlen(name);
 
-	if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
-		return false;
+	if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) {
+		*ea_name = name + XATTR_USER_PREFIX_LEN;
+		*ea_name_len = name_len - XATTR_USER_PREFIX_LEN;
 
-	*ea_name = name + XATTR_USER_PREFIX_LEN;
-	*ea_name_len = name_len - XATTR_USER_PREFIX_LEN;
+		if (!strncmp(*ea_name, STREAM_PREFIX, STREAM_PREFIX_LEN))
+			return false;
 
-	if (!strncmp(*ea_name, STREAM_PREFIX, STREAM_PREFIX_LEN))
-		return false;
+		if (!strncmp(*ea_name, DOS_ATTRIBUTE_PREFIX,
+			     DOS_ATTRIBUTE_PREFIX_LEN))
+			return false;
 
-	if (!strncmp(*ea_name, DOS_ATTRIBUTE_PREFIX,
-		     DOS_ATTRIBUTE_PREFIX_LEN))
+		if (native_xattrs &&
+		    ksmbd_check_native_xattr(work, *ea_name, *ea_name_len))
+			return false;
+
+		return true;
+	}
+
+	if (!native_xattrs ||
+	    ksmbd_check_native_xattr(work, name, name_len) <= 0)
 		return false;
 
+	*ea_name = name;
+	*ea_name_len = name_len;
 	return true;
 }
 
@@ -5181,7 +5230,6 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 	}
 
 	path = &fp->filp->f_path;
-	/* single EA entry is requested with given user.* name */
 	if (req->InputBufferLength) {
 		if (le32_to_cpu(req->InputBufferLength) <=
 		    sizeof(struct smb2_ea_info_req))
@@ -5194,6 +5242,13 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 		    offsetof(struct smb2_ea_info_req, name) +
 		    ea_req->EaNameLength)
 			return -EINVAL;
+
+		if (fp->is_posix_ctxt) {
+			rc = ksmbd_check_native_xattr(work, ea_req->name,
+						     ea_req->EaNameLength);
+			if (rc < 0)
+				return rc;
+		}
 	} else {
 		/* need to send all EAs, if no specific EA is requested*/
 		if (le32_to_cpu(req->Flags) & SL_RETURN_SINGLE_ENTRY)
@@ -5234,7 +5289,8 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp,
 		ksmbd_debug(SMB, "%s, len %d\n", name, name_len);
 		idx += name_len + 1;
 
-		if (!ksmbd_is_visible_ea_name(name, &ea_name,
+		if (!ksmbd_is_visible_ea_name(work, fp->is_posix_ctxt, name,
+					      &ea_name,
 					      &visible_name_len))
 			continue;
 
-- 
2.43.0


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

* [PATCH 08/15] ksmbd: set security.capability on POSIX EA handles
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (6 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 07/15] ksmbd: query security.capability on POSIX EA handles Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 09/15] ksmbd: support security.xfstests " Ze Tan
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Map security.capability directly to its native backing xattr when the
file handle was opened with an SMB3 POSIX create context. Ordinary EAs
and non-POSIX handles retain the existing user namespace mapping.

Pass the ksmbd_work and per-open POSIX state through smb2_set_ea() from
both the CREATE and SET_INFO paths. This lets
ksmbd_map_ea_name_to_xattr() apply the same root or admin authorization
used by native queries.

Return native mapping errors to the caller instead of replacing them
with -EINVAL.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 43 +++++++++++++++++++++++++++++++----------
 1 file changed, 33 insertions(+), 10 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 7fdc6df6310a..6baeac99b12f 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2680,9 +2680,27 @@ static int ksmbd_check_native_xattr(struct ksmbd_work *work,
 	return 1;
 }
 
-static int ksmbd_map_ea_name_to_xattr(const char *ea_name,
-				      size_t ea_name_len, char *attr_name)
+static int ksmbd_map_ea_name_to_xattr(struct ksmbd_work *work,
+				      bool native_xattrs,
+				      const char *ea_name, size_t ea_name_len,
+				      char *attr_name)
 {
+	int rc;
+
+	if (native_xattrs) {
+		rc = ksmbd_check_native_xattr(work, ea_name, ea_name_len);
+		if (rc < 0)
+			return rc;
+		if (rc) {
+			if (ea_name_len > XATTR_NAME_MAX)
+				return -EINVAL;
+
+			memcpy(attr_name, ea_name, ea_name_len);
+			attr_name[ea_name_len] = '\0';
+			return ea_name_len;
+		}
+	}
+
 	if (ea_name_len > XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)
 		return -EINVAL;
 
@@ -2728,15 +2746,18 @@ static bool ksmbd_is_visible_ea_name(struct ksmbd_work *work,
 /**
  * smb2_set_ea() - handler for setting extended attributes using set
  *		info command
+ * @work:	smb work containing session and tree information
  * @eabuf:	set info command buffer
  * @buf_len:	set info command buffer length
  * @path:	dentry path for get ea
  * @get_write:	get write access to a mount
+ * @native_xattrs:	the open included an SMB3 POSIX create context
  *
  * Return:	0 on success, otherwise error
  */
-static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
-		       const struct path *path, bool get_write)
+static int smb2_set_ea(struct ksmbd_work *work, struct smb2_ea_info *eabuf,
+		       unsigned int buf_len, const struct path *path,
+		       bool get_write, bool native_xattrs)
 {
 	struct mnt_idmap *idmap = mnt_idmap(path->mnt);
 	char *attr_name = NULL, *value;
@@ -2762,11 +2783,12 @@ static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len,
 			    le16_to_cpu(eabuf->EaValueLength),
 			    le32_to_cpu(eabuf->NextEntryOffset));
 
-		attr_name_len = ksmbd_map_ea_name_to_xattr(eabuf->name,
+		attr_name_len = ksmbd_map_ea_name_to_xattr(work, native_xattrs,
+							   eabuf->name,
 							   eabuf->EaNameLength,
 							   attr_name);
 		if (attr_name_len < 0) {
-			rc = -EINVAL;
+			rc = attr_name_len;
 			break;
 		}
 
@@ -3742,9 +3764,9 @@ int smb2_open(struct ksmbd_work *work)
 				goto err_out;
 			}
 
-			rc = smb2_set_ea(&ea_buf->ea,
+			rc = smb2_set_ea(work, &ea_buf->ea,
 					 le32_to_cpu(ea_buf->ccontext.DataLength),
-					 &path, true);
+					 &path, true, posix_ctxt);
 			if (rc == -EOPNOTSUPP)
 				rc = 0;
 			else if (rc)
@@ -7132,8 +7154,9 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
 		if (buf_len < sizeof(struct smb2_ea_info))
 			return -EMSGSIZE;
 
-		return smb2_set_ea((struct smb2_ea_info *)buffer,
-				   buf_len, &fp->filp->f_path, true);
+		return smb2_set_ea(work, (struct smb2_ea_info *)buffer,
+				   buf_len, &fp->filp->f_path, true,
+				   fp->is_posix_ctxt);
 	}
 	case FILE_POSITION_INFORMATION:
 	{
-- 
2.43.0


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

* [PATCH 09/15] ksmbd: support security.xfstests on POSIX EA handles
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (7 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 08/15] ksmbd: set " Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 10/15] ksmbd: support trusted xattrs " Ze Tan
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Add security.xfstests to the exact native xattr allowlist. It uses the
same per-open POSIX activation, root or admin authorization, collision
handling, and list filtering as security.capability.

With the matching POSIX client support applied, the mapping can be
checked with CIFS_MNT set on the client and SHARE_ROOT set on the
server:

  $ file="$CIFS_MNT/security-xfstests"
  $ touch "$file"
  $ setfattr -n security.xfstests -v attr "$file"
  $ test "$(getfattr --only-values -n security.xfstests "$file")" = attr

On the server:

  $ file="$SHARE_ROOT/security-xfstests"
  $ test "$(getfattr --only-values -n security.xfstests "$file")" = attr

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 6baeac99b12f..dba97df7b29f 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2645,6 +2645,7 @@ static noinline int create_smb2_pipe(struct ksmbd_work *work)
 
 static const char * const ksmbd_native_xattr_names[] = {
 	XATTR_NAME_CAPS,
+	XATTR_SECURITY_PREFIX "xfstests",
 };
 
 static bool ksmbd_native_xattrs_allowed(struct ksmbd_work *work)
-- 
2.43.0


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

* [PATCH 10/15] ksmbd: support trusted xattrs on POSIX EA handles
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (8 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 09/15] ksmbd: support security.xfstests " Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 11/15] smb: client: prepare EA opens for inode lease reuse Ze Tan
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Add trusted.* to the native prefix allowlist. Use the same per-open
POSIX activation and root or admin authorization for set, remove, get,
and list operations.

Check the unsupported exact-name table before the prefix table so the
XFS trusted.SGI_ACL_* xattrs remain inaccessible.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/server/smb2pdu.c | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index dba97df7b29f..d3c4434f8eb3 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2648,6 +2648,15 @@ static const char * const ksmbd_native_xattr_names[] = {
 	XATTR_SECURITY_PREFIX "xfstests",
 };
 
+static const char * const ksmbd_native_xattr_prefixes[] = {
+	XATTR_TRUSTED_PREFIX,
+};
+
+static const char * const ksmbd_unsupported_xattr_names[] = {
+	XATTR_TRUSTED_PREFIX "SGI_ACL_FILE",
+	XATTR_TRUSTED_PREFIX "SGI_ACL_DEFAULT",
+};
+
 static bool ksmbd_native_xattrs_allowed(struct ksmbd_work *work)
 {
 	return uid_eq(current_fsuid(), GLOBAL_ROOT_UID) ||
@@ -2664,6 +2673,15 @@ static int ksmbd_check_native_xattr(struct ksmbd_work *work,
 	if (memchr(name, '\0', name_len))
 		return -EINVAL;
 
+	for (i = 0; i < ARRAY_SIZE(ksmbd_unsupported_xattr_names); i++) {
+		size_t xattr_len = strlen(ksmbd_unsupported_xattr_names[i]);
+
+		if (name_len == xattr_len &&
+		    !memcmp(name, ksmbd_unsupported_xattr_names[i],
+			    name_len))
+			return -EOPNOTSUPP;
+	}
+
 	for (i = 0; i < ARRAY_SIZE(ksmbd_native_xattr_names); i++) {
 		size_t xattr_len = strlen(ksmbd_native_xattr_names[i]);
 
@@ -2674,6 +2692,21 @@ static int ksmbd_check_native_xattr(struct ksmbd_work *work,
 		}
 	}
 
+	if (!native) {
+		for (i = 0; i < ARRAY_SIZE(ksmbd_native_xattr_prefixes);
+		     i++) {
+			size_t prefix_len;
+
+			prefix_len = strlen(ksmbd_native_xattr_prefixes[i]);
+			if (name_len > prefix_len &&
+			    !memcmp(name, ksmbd_native_xattr_prefixes[i],
+				    prefix_len)) {
+				native = true;
+				break;
+			}
+		}
+	}
+
 	if (!native)
 		return 0;
 	if (!ksmbd_native_xattrs_allowed(work))
-- 
2.43.0


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

* [PATCH 11/15] smb: client: prepare EA opens for inode lease reuse
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (9 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 10/15] ksmbd: support trusted xattrs " Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 12/15] smb: client: avoid batch oplocks for reentrant POSIX EAs Ze Tan
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Extend the query_all_EAs and set_EA callbacks, and
smb2_query_info_compound(), with an optional inode. For SMB2 and SMB3,
smb2_reuse_inode_lease() can use that inode to attach an existing lease
key to temporary QUERY_INFO and SET_INFO opens.

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/client/cifsglob.h  |  5 +++--
 fs/smb/client/cifssmb.c   |  7 ++++---
 fs/smb/client/inode.c     |  2 +-
 fs/smb/client/smb1ops.c   |  4 ++--
 fs/smb/client/smb1proto.h |  5 +++--
 fs/smb/client/smb2ops.c   | 31 ++++++++++++++++++++++++-------
 fs/smb/client/smb2proto.h |  3 ++-
 fs/smb/client/xattr.c     |  6 +++---
 8 files changed, 42 insertions(+), 21 deletions(-)

diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 79e4e84f8985..5e752d8731a9 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -568,10 +568,11 @@ struct smb_version_operations {
 	int (*validate_negotiate)(const unsigned int, struct cifs_tcon *);
 	ssize_t (*query_all_EAs)(const unsigned int, struct cifs_tcon *,
 			const unsigned char *, const unsigned char *, char *,
-			size_t, struct cifs_sb_info *);
+			size_t, struct cifs_sb_info *, struct inode *);
 	int (*set_EA)(const unsigned int, struct cifs_tcon *, const char *,
 			const char *, const void *, const __u16,
-			const struct nls_table *, struct cifs_sb_info *);
+			const struct nls_table *, struct cifs_sb_info *,
+			struct inode *);
 	struct smb_ntsd * (*get_acl)(struct cifs_sb_info *cifssb, struct inode *ino,
 			const char *patch, u32 *plen, u32 info);
 	struct smb_ntsd * (*get_acl_by_fid)(struct cifs_sb_info *cifssmb,
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 40162d5554ea..82dfb024b6bf 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3142,7 +3142,8 @@ struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data,
 					  &ea->ea_data[ea->ea_name_length+1],
 					  le16_to_cpu(ea->ea_value_length),
 					  cifs_sb->local_nls,
-					  cifs_sb);
+					  cifs_sb,
+					  NULL);
 			if (rc)
 				goto out_close;
 			if (le32_to_cpu(ea->next_entry_offset) == 0)
@@ -6113,7 +6114,7 @@ ssize_t
 CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon,
 		const unsigned char *searchName, const unsigned char *ea_name,
 		char *EAData, size_t buf_size,
-		struct cifs_sb_info *cifs_sb)
+		struct cifs_sb_info *cifs_sb, struct inode *inode)
 {
 		/* BB assumes one setup word */
 	TRANSACTION2_QPI_REQ *pSMB = NULL;
@@ -6304,7 +6305,7 @@ int
 CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon,
 	     const char *fileName, const char *ea_name, const void *ea_value,
 	     const __u16 ea_value_len, const struct nls_table *nls_codepage,
-	     struct cifs_sb_info *cifs_sb)
+	     struct cifs_sb_info *cifs_sb, struct inode *inode)
 {
 	struct smb_com_transaction2_spi_req *pSMB = NULL;
 	struct smb_com_transaction2_spi_rsp *pSMBr = NULL;
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index b2806371bfde..a18c327afe03 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -742,7 +742,7 @@ static int cifs_sfu_mode(struct cifs_fattr *fattr, const unsigned char *path,
 
 	rc = tcon->ses->server->ops->query_all_EAs(xid, tcon, path,
 			"SETFILEBITS", ea_value, 4 /* size of buf */,
-			cifs_sb);
+			cifs_sb, NULL);
 	cifs_put_tlink(tlink);
 	if (rc < 0)
 		return (int)rc;
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index dc5a8c1da623..cbe1f4cc00e0 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -663,7 +663,7 @@ static int cifs_query_path_info(const unsigned int xid,
 
 		rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_MODE,
 				    &ea->ea_data[SMB2_WSL_XATTR_NAME_LEN + 1],
-				    SMB2_WSL_XATTR_MODE_SIZE, cifs_sb);
+				    SMB2_WSL_XATTR_MODE_SIZE, cifs_sb, NULL);
 		if (rc == SMB2_WSL_XATTR_MODE_SIZE) {
 			ea->next_entry_offset = cpu_to_le32(0);
 			ea->flags = 0;
@@ -709,7 +709,7 @@ static int cifs_query_path_info(const unsigned int xid,
 
 		rc = CIFSSMBQAllEAs(xid, tcon, full_path, SMB2_WSL_XATTR_DEV,
 				    &ea->ea_data[SMB2_WSL_XATTR_NAME_LEN + 1],
-				    SMB2_WSL_XATTR_DEV_SIZE, cifs_sb);
+				    SMB2_WSL_XATTR_DEV_SIZE, cifs_sb, NULL);
 		if (rc == SMB2_WSL_XATTR_DEV_SIZE) {
 			ea->next_entry_offset = cpu_to_le32(0);
 			ea->flags = 0;
diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h
index 80eaeb3dd2ec..cc3fb7370ab4 100644
--- a/fs/smb/client/smb1proto.h
+++ b/fs/smb/client/smb1proto.h
@@ -208,12 +208,13 @@ int CIFSSMBUnixSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
 ssize_t CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon,
 		       const unsigned char *searchName,
 		       const unsigned char *ea_name, char *EAData,
-		       size_t buf_size, struct cifs_sb_info *cifs_sb);
+		       size_t buf_size, struct cifs_sb_info *cifs_sb,
+		       struct inode *inode);
 int CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon,
 		 const char *fileName, const char *ea_name,
 		 const void *ea_value, const __u16 ea_value_len,
 		 const struct nls_table *nls_codepage,
-		 struct cifs_sb_info *cifs_sb);
+		 struct cifs_sb_info *cifs_sb, struct inode *inode);
 
 /*
  * smb1debug.c
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index cbd51a08e97e..e85d6b5a4564 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -1042,6 +1042,20 @@ static int smb2_query_file_info(const unsigned int xid, struct cifs_tcon *tcon,
 	return SMB2_query_info(xid, tcon, fid->persistent_fid, fid->volatile_fid, &data->fi);
 }
 
+static void smb2_reuse_inode_lease(struct cifs_tcon *tcon,
+				   struct TCP_Server_Info *server,
+				   struct inode *inode, struct cifs_fid *fid,
+				   u8 *oplock)
+{
+	if (!inode || !tcon->posix_extensions ||
+	    !(server->capabilities & SMB2_GLOBAL_CAP_LEASING) ||
+	    !CIFS_I(inode)->lease_granted || !server->ops->get_lease_key)
+		return;
+
+	*oplock = SMB2_OPLOCK_LEVEL_LEASE;
+	server->ops->get_lease_key(inode, fid);
+}
+
 #ifdef CONFIG_CIFS_XATTR
 static ssize_t
 move_smb2_ea_to_cifs(char *dst, size_t dst_size,
@@ -1132,7 +1146,7 @@ static ssize_t
 smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 	       const unsigned char *path, const unsigned char *ea_name,
 	       char *ea_data, size_t buf_size,
-	       struct cifs_sb_info *cifs_sb)
+	       struct cifs_sb_info *cifs_sb, struct inode *inode)
 {
 	int rc;
 	struct kvec rsp_iov = {NULL, 0};
@@ -1147,7 +1161,7 @@ smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 				      CIFSMaxBufSize -
 				      MAX_SMB2_CREATE_RESPONSE_SIZE -
 				      MAX_SMB2_CLOSE_RESPONSE_SIZE,
-				      &rsp_iov, &buftype, cifs_sb);
+				      &rsp_iov, &buftype, cifs_sb, inode);
 	if (rc) {
 		/*
 		 * If ea_name is NULL (listxattr) and there are no EAs,
@@ -1181,7 +1195,7 @@ static int
 smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 	    const char *path, const char *ea_name, const void *ea_value,
 	    const __u16 ea_value_len, const struct nls_table *nls_codepage,
-	    struct cifs_sb_info *cifs_sb)
+	    struct cifs_sb_info *cifs_sb, struct inode *inode)
 {
 	struct smb2_compound_vars *vars;
 	struct cifs_ses *ses = tcon->ses;
@@ -1234,7 +1248,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 		if (!ea_value) {
 			rc = ses->server->ops->query_all_EAs(xid, tcon, path,
 							     ea_name, NULL, 0,
-							     cifs_sb);
+							     cifs_sb, inode);
 			if (rc == -ENODATA)
 				goto sea_exit;
 		} else {
@@ -1250,7 +1264,8 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 				      CIFSMaxBufSize -
 				      MAX_SMB2_CREATE_RESPONSE_SIZE -
 				      MAX_SMB2_CLOSE_RESPONSE_SIZE,
-				      &rsp_iov[1], &resp_buftype[1], cifs_sb);
+				      &rsp_iov[1], &resp_buftype[1], cifs_sb,
+				      inode);
 			if (rc == 0) {
 				rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base;
 				used_len = le32_to_cpu(rsp->OutputBufferLength);
@@ -1286,6 +1301,7 @@ smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 		.replay = !!(retries),
 	};
 
+	smb2_reuse_inode_lease(tcon, server, inode, &fid, &oplock);
 	rc = SMB2_open_init(tcon, server,
 			    &rqst[0], &oplock, &oparms, utf16_path);
 	if (rc)
@@ -2861,7 +2877,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
 			 const char *path, u32 desired_access,
 			 u32 class, u32 type, u32 output_len,
 			 struct kvec *rsp, int *buftype,
-			 struct cifs_sb_info *cifs_sb)
+			 struct cifs_sb_info *cifs_sb, struct inode *inode)
 {
 	struct smb2_compound_vars *vars;
 	struct cifs_ses *ses = tcon->ses;
@@ -2923,6 +2939,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
 		.replay = !!(retries),
 	};
 
+	smb2_reuse_inode_lease(tcon, server, inode, &fid, &oplock);
 	rc = SMB2_open_init(tcon, server,
 			    &rqst[0], &oplock, &oparms, utf16_path);
 	if (rc)
@@ -3032,7 +3049,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 				      FS_FULL_SIZE_INFORMATION,
 				      SMB2_O_INFO_FILESYSTEM,
 				      sizeof(struct smb2_fs_full_size_info),
-				      &rsp_iov, &buftype, cifs_sb);
+				      &rsp_iov, &buftype, cifs_sb, NULL);
 	if (rc)
 		goto qfs_exit;
 
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 2e9f70096825..99978e63df68 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -267,7 +267,8 @@ void smb311_update_preauth_hash(struct cifs_ses *ses,
 int smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon,
 			     const char *path, u32 desired_access, u32 class,
 			     u32 type, u32 output_len, struct kvec *rsp,
-			     int *buftype, struct cifs_sb_info *cifs_sb);
+			     int *buftype, struct cifs_sb_info *cifs_sb,
+			     struct inode *inode);
 /* query path info from the server using SMB311 POSIX extensions*/
 int posix_info_parse(const void *beg, const void *end,
 		     struct smb2_posix_info_parsed *out);
diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c
index 5091f6c0d7fe..d52c52866651 100644
--- a/fs/smb/client/xattr.c
+++ b/fs/smb/client/xattr.c
@@ -155,7 +155,7 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
 		if (pTcon->ses->server->ops->set_EA) {
 			rc = pTcon->ses->server->ops->set_EA(xid, pTcon,
 				full_path, name, value, (__u16)size,
-				cifs_sb->local_nls, cifs_sb);
+				cifs_sb->local_nls, cifs_sb, NULL);
 			if (rc == 0)
 				inode_set_ctime_current(inode);
 		}
@@ -314,7 +314,7 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
 
 		if (pTcon->ses->server->ops->query_all_EAs)
 			rc = pTcon->ses->server->ops->query_all_EAs(xid, pTcon,
-				full_path, name, value, size, cifs_sb);
+				full_path, name, value, size, cifs_sb, NULL);
 		break;
 
 	case XATTR_CIFS_ACL:
@@ -423,7 +423,7 @@ ssize_t cifs_listxattr(struct dentry *direntry, char *data, size_t buf_size)
 
 	if (pTcon->ses->server->ops->query_all_EAs)
 		rc = pTcon->ses->server->ops->query_all_EAs(xid, pTcon,
-				full_path, NULL, data, buf_size, cifs_sb);
+				full_path, NULL, data, buf_size, cifs_sb, NULL);
 list_ea_exit:
 	free_dentry_path(page);
 	free_xid(xid);
-- 
2.43.0


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

* [PATCH 12/15] smb: client: avoid batch oplocks for reentrant POSIX EAs
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (10 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 11/15] smb: client: prepare EA opens for inode lease reuse Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 13/15] smb: client: gate security.capability EA on POSIX mounts Ze Tan
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Native killpriv handling can reopen a path for EA queries or removal
while a write handle is active. Without lease support, a batch oplock
cannot identify the temporary EA open as the same caching owner and can
deadlock while breaking against the blocked write path.

Request no oplock for data-writing opens when POSIX extensions and
xattrs are enabled but leasing is unavailable. Leave read opens,
non-POSIX mounts, and servers with lease support unchanged.

After applying the native EA patches, configure the server with oplocks
enabled and SMB2 leases disabled. Use an SMB account that maps to root
or an admin account on the server.

Mount both the TEST and SCRATCH shares with SMB3 POSIX extensions and
SID-encoded Unix IDs:

  $ TEST_USER=fsgqa
  $ TEST_UID=$(id -u "$TEST_USER")
  $ TEST_GID=$(id -g "$TEST_USER")
  $ MOUNT_OPTS="credentials=/path/to/credentials,vers=3.1.1"
  $ MOUNT_OPTS="$MOUNT_OPTS,posix,idsfromsid"
  $ MOUNT_OPTS="$MOUNT_OPTS,uid=$TEST_UID,gid=$TEST_GID"
  $ export TEST_FS_MOUNT_OPTS="-o $MOUNT_OPTS"
  $ export MOUNT_OPTIONS="-o $MOUNT_OPTS"

Then run:

  $ su root
  $ XFSTESTS_DIR=/path/to/xfstests
  $ cd "$XFSTESTS_DIR"
  $ ./check generic/093

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/client/smb2file.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c
index f35b6488d810..b07ff4570f56 100644
--- a/fs/smb/client/smb2file.c
+++ b/fs/smb/client/smb2file.c
@@ -160,6 +160,13 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec
 					 cifs_sb);
 }
 
+static bool smb2_open_writes_data(const struct cifs_open_parms *oparms)
+{
+	return oparms->desired_access &
+		(FILE_WRITE_DATA | FILE_APPEND_DATA |
+		 GENERIC_WRITE | GENERIC_ALL);
+}
+
 int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
 		   __u32 *oplock, void *buf)
 {
@@ -192,6 +199,17 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms,
 		retry_without_read_attributes = true;
 	}
 	smb2_oplock = SMB2_OPLOCK_LEVEL_BATCH;
+	/*
+	 * A killpriv EA reopen without a lease can break this write's batch
+	 * oplock and deadlock against itself.
+	 */
+	if (IS_ENABLED(CONFIG_CIFS_XATTR) && oparms->cifs_sb &&
+	    !(cifs_sb_flags(oparms->cifs_sb) & CIFS_MOUNT_NO_XATTR) &&
+	    oparms->tcon->posix_extensions &&
+	    !(oparms->tcon->ses->server->capabilities &
+	      SMB2_GLOBAL_CAP_LEASING) &&
+	    smb2_open_writes_data(oparms))
+		smb2_oplock = SMB2_OPLOCK_LEVEL_NONE;
 
 	rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, data, NULL, &err_iov,
 		       &err_buftype);
-- 
2.43.0


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

* [PATCH 13/15] smb: client: gate security.capability EA on POSIX mounts
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (11 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 12/15] smb: client: avoid batch oplocks for reentrant POSIX EAs Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 14/15] smb: client: support security.xfstests " Ze Tan
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Register the security xattr handler, but allow security.capability to
use its native SMB EA name only after SMB3 POSIX extensions have been
requested and negotiated. Non-POSIX mounts retain the existing user EA
mapping.

On a POSIX mount, reject the colliding user.security.capability spelling
and preserve security.capability in listxattr output.

The native xattr and killpriv behavior can be checked as root:

  $ CIFS_MNT=/path/to/cifs-mount
  $ file="$CIFS_MNT/capability"
  $ touch "$file"
  $ setcap cap_chown+ep "$file"
  $ getcap "$file" | grep -q 'cap_chown=ep'
  $ printf 'data\n' >> "$file"
  $ test -z "$(getcap "$file")"

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/client/cifsfs.h  |   7 +++
 fs/smb/client/smb2ops.c |  22 +++++---
 fs/smb/client/xattr.c   | 112 +++++++++++++++++++++++++++++++++++++---
 3 files changed, 129 insertions(+), 12 deletions(-)

diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index 854e672a4e37..4ab1a203be3e 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -133,6 +133,13 @@ int cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
 		 struct dentry *direntry, const char *symname);
 
 #ifdef CONFIG_CIFS_XATTR
+enum cifs_ea_name_type {
+	CIFS_EA_USER,
+	CIFS_EA_NATIVE,
+};
+
+enum cifs_ea_name_type
+cifs_ea_name_type(const char *name, size_t name_len, bool posix_extensions);
 extern const struct xattr_handler * const cifs_xattr_handlers[];
 ssize_t cifs_listxattr(struct dentry *direntry, char *data, size_t buf_size);
 #else
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index e85d6b5a4564..fd778d903e93 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -1060,7 +1060,7 @@ static void smb2_reuse_inode_lease(struct cifs_tcon *tcon,
 static ssize_t
 move_smb2_ea_to_cifs(char *dst, size_t dst_size,
 		     struct smb2_file_full_ea_info *src, size_t src_size,
-		     const unsigned char *ea_name)
+		     const unsigned char *ea_name, bool posix_extensions)
 {
 	int rc = 0;
 	unsigned int ea_name_len = ea_name ? strlen(ea_name) : 0;
@@ -1099,16 +1099,25 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
 				goto out;
 			}
 		} else {
-			/* 'user.' plus a terminating null */
-			user_name_len = 5 + 1 + name_len;
+			enum cifs_ea_name_type name_type;
+
+			name_type = cifs_ea_name_type(name, name_len,
+						     posix_extensions);
+			if (name_type == CIFS_EA_NATIVE)
+				user_name_len = name_len + 1;
+			else
+				/* 'user.' plus a terminating null */
+				user_name_len = 5 + 1 + name_len;
 
 			if (buf_size == 0) {
 				/* skip copy - calc size only */
 				rc += user_name_len;
 			} else if (dst_size >= user_name_len) {
 				dst_size -= user_name_len;
-				memcpy(dst, "user.", 5);
-				dst += 5;
+				if (name_type == CIFS_EA_USER) {
+					memcpy(dst, "user.", 5);
+					dst += 5;
+				}
 				memcpy(dst, src->ea_data, name_len);
 				dst += name_len;
 				*dst = 0;
@@ -1184,7 +1193,8 @@ smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 	info = (struct smb2_file_full_ea_info *)(
 			le16_to_cpu(rsp->OutputBufferOffset) + (char *)rsp);
 	rc = move_smb2_ea_to_cifs(ea_data, buf_size, info,
-			le32_to_cpu(rsp->OutputBufferLength), ea_name);
+			le32_to_cpu(rsp->OutputBufferLength), ea_name,
+			tcon->posix_extensions);
 
  qeas_exit:
 	free_rsp_buf(buftype, rsp_iov.iov_base);
diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c
index d52c52866651..f975bccf98a5 100644
--- a/fs/smb/client/xattr.c
+++ b/fs/smb/client/xattr.c
@@ -36,12 +36,63 @@
 #define SMB3_XATTR_CIFS_NTSD_FULL "system.smb3_ntsd_full" /* owner/DACL/SACL */
 #define SMB3_XATTR_ATTRIB "smb3.dosattrib"  /* full name: user.smb3.dosattrib */
 #define SMB3_XATTR_CREATETIME "smb3.creationtime"  /* user.smb3.creationtime */
-/* BB need to add server (Samba e.g) support for security and trusted prefix */
-
-enum { XATTR_USER, XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT,
+enum { XATTR_USER, XATTR_SECURITY,
+	XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT,
 	XATTR_CIFS_NTSD_SACL, XATTR_CIFS_NTSD_OWNER,
 	XATTR_CIFS_NTSD, XATTR_CIFS_NTSD_FULL };
 
+static const char * const cifs_native_xattr_names[] = {
+	XATTR_NAME_CAPS,
+};
+
+enum cifs_ea_name_type
+cifs_ea_name_type(const char *name, size_t name_len, bool posix_extensions)
+{
+	size_t i;
+
+	if (!posix_extensions)
+		return CIFS_EA_USER;
+
+	for (i = 0; i < ARRAY_SIZE(cifs_native_xattr_names); i++) {
+		size_t xattr_len = strlen(cifs_native_xattr_names[i]);
+
+		if (name_len == xattr_len &&
+		    !memcmp(name, cifs_native_xattr_names[i], name_len))
+			return CIFS_EA_NATIVE;
+	}
+
+	return CIFS_EA_USER;
+}
+
+static int cifs_build_ea_name(int xattr_flag, const char *name, char *ea_name,
+			      size_t ea_name_size)
+{
+	const char *prefix;
+	size_t name_len, prefix_len;
+
+	switch (xattr_flag) {
+	case XATTR_SECURITY:
+		prefix = XATTR_SECURITY_PREFIX;
+		prefix_len = XATTR_SECURITY_PREFIX_LEN;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	name_len = strlen(name);
+	if (ea_name_size <= prefix_len ||
+	    name_len > ea_name_size - prefix_len - 1)
+		return -ERANGE;
+
+	memcpy(ea_name, prefix, prefix_len);
+	memcpy(ea_name + prefix_len, name, name_len + 1);
+
+	if (cifs_ea_name_type(ea_name, prefix_len + name_len, true) !=
+	    CIFS_EA_NATIVE)
+		return -EOPNOTSUPP;
+	return 0;
+}
+
 static int cifs_attrib_set(unsigned int xid, struct cifs_tcon *pTcon,
 			   struct inode *inode, const char *full_path,
 			   const void *value, size_t size)
@@ -104,6 +155,9 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
 	struct cifs_tcon *pTcon;
 	const char *full_path;
 	void *page;
+	char ea_name[XATTR_NAME_MAX + 1];
+	const char *server_ea_name = name;
+	struct inode *lease_inode = NULL;
 
 	tlink = cifs_sb_tlink(cifs_sb);
 	if (IS_ERR(tlink))
@@ -131,8 +185,24 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
 	}
 
 	switch (handler->flags) {
+	case XATTR_SECURITY:
+		if (!pTcon->posix_extensions)
+			goto out;
+		rc = cifs_build_ea_name(handler->flags, name, ea_name,
+					sizeof(ea_name));
+		if (rc < 0)
+			goto out;
+		server_ea_name = ea_name;
+		lease_inode = inode;
+		cifs_dbg(FYI, "%s: setting security xattr %s\n",
+			 __func__, name);
+		goto set_ea;
+
 	case XATTR_USER:
 		cifs_dbg(FYI, "%s:setting user xattr %s\n", __func__, name);
+		if (cifs_ea_name_type(name, strlen(name),
+				      pTcon->posix_extensions) != CIFS_EA_USER)
+			goto out;
 		if ((strcmp(name, CIFS_XATTR_ATTRIB) == 0) ||
 		    (strcmp(name, SMB3_XATTR_ATTRIB) == 0)) {
 			rc = cifs_attrib_set(xid, pTcon, inode, full_path,
@@ -149,13 +219,14 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
 			break;
 		}
 
+set_ea:
 		if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_NO_XATTR)
 			goto out;
 
 		if (pTcon->ses->server->ops->set_EA) {
 			rc = pTcon->ses->server->ops->set_EA(xid, pTcon,
-				full_path, name, value, (__u16)size,
-				cifs_sb->local_nls, cifs_sb, NULL);
+				full_path, server_ea_name, value, (__u16)size,
+				cifs_sb->local_nls, cifs_sb, lease_inode);
 			if (rc == 0)
 				inode_set_ctime_current(inode);
 		}
@@ -280,6 +351,9 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
 	struct cifs_tcon *pTcon;
 	const char *full_path;
 	void *page;
+	char ea_name[XATTR_NAME_MAX + 1];
+	const char *server_ea_name = name;
+	struct inode *lease_inode = NULL;
 
 	tlink = cifs_sb_tlink(cifs_sb);
 	if (IS_ERR(tlink))
@@ -297,8 +371,24 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
 
 	/* return alt name if available as pseudo attr */
 	switch (handler->flags) {
+	case XATTR_SECURITY:
+		if (!pTcon->posix_extensions)
+			goto out;
+		rc = cifs_build_ea_name(handler->flags, name, ea_name,
+					sizeof(ea_name));
+		if (rc < 0)
+			goto out;
+		server_ea_name = ea_name;
+		lease_inode = inode;
+		cifs_dbg(FYI, "%s: querying security xattr %s\n",
+			 __func__, name);
+		goto query_ea;
+
 	case XATTR_USER:
 		cifs_dbg(FYI, "%s:querying user xattr %s\n", __func__, name);
+		if (cifs_ea_name_type(name, strlen(name),
+				      pTcon->posix_extensions) != CIFS_EA_USER)
+			goto out;
 		if ((strcmp(name, CIFS_XATTR_ATTRIB) == 0) ||
 		    (strcmp(name, SMB3_XATTR_ATTRIB) == 0)) {
 			rc = cifs_attrib_get(dentry, inode, value, size);
@@ -309,12 +399,14 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
 			break;
 		}
 
+query_ea:
 		if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_NO_XATTR)
 			goto out;
 
 		if (pTcon->ses->server->ops->query_all_EAs)
 			rc = pTcon->ses->server->ops->query_all_EAs(xid, pTcon,
-				full_path, name, value, size, cifs_sb, NULL);
+				full_path, server_ea_name, value, size, cifs_sb,
+				lease_inode);
 		break;
 
 	case XATTR_CIFS_ACL:
@@ -438,6 +530,13 @@ static const struct xattr_handler cifs_user_xattr_handler = {
 	.set = cifs_xattr_set,
 };
 
+static const struct xattr_handler cifs_security_xattr_handler = {
+	.prefix = XATTR_SECURITY_PREFIX,
+	.flags = XATTR_SECURITY,
+	.get = cifs_xattr_get,
+	.set = cifs_xattr_set,
+};
+
 /* os2.* attributes are treated like user.* attributes */
 static const struct xattr_handler cifs_os2_xattr_handler = {
 	.prefix = XATTR_OS2_PREFIX,
@@ -522,6 +621,7 @@ static const struct xattr_handler smb3_ntsd_full_xattr_handler = {
 
 const struct xattr_handler * const cifs_xattr_handlers[] = {
 	&cifs_user_xattr_handler,
+	&cifs_security_xattr_handler,
 	&cifs_os2_xattr_handler,
 	&cifs_cifs_acl_xattr_handler,
 	&smb3_acl_xattr_handler, /* alias for above since avoiding "cifs" */
-- 
2.43.0


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

* [PATCH 14/15] smb: client: support security.xfstests on POSIX mounts
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (12 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 13/15] smb: client: gate security.capability EA on POSIX mounts Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 10:40 ` [PATCH 15/15] smb: client: support trusted EAs " Ze Tan
  2026-07-24 11:49 ` [PATCH 00/15] smb: add native security and trusted xattrs Ralph Boehme
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Add security.xfstests to the exact native xattr table. The existing
security handler can then send and receive it without a user prefix on
POSIX mounts, reject the colliding user.security.xfstests spelling, and
preserve the native name in listxattr output.

The mapping can be checked from a POSIX-mounted client:

  $ CIFS_MNT=/path/to/cifs-mount
  $ file="$CIFS_MNT/security-xfstests"
  $ touch "$file"
  $ setfattr -n security.xfstests -v attr "$file"

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/client/xattr.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c
index f975bccf98a5..59f6655a1b34 100644
--- a/fs/smb/client/xattr.c
+++ b/fs/smb/client/xattr.c
@@ -43,6 +43,7 @@ enum { XATTR_USER, XATTR_SECURITY,
 
 static const char * const cifs_native_xattr_names[] = {
 	XATTR_NAME_CAPS,
+	XATTR_SECURITY_PREFIX "xfstests",
 };
 
 enum cifs_ea_name_type
-- 
2.43.0


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

* [PATCH 15/15] smb: client: support trusted EAs on POSIX mounts
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (13 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 14/15] smb: client: support security.xfstests " Ze Tan
@ 2026-07-24 10:40 ` Ze Tan
  2026-07-24 11:49 ` [PATCH 00/15] smb: add native security and trusted xattrs Ralph Boehme
  15 siblings, 0 replies; 17+ messages in thread
From: Ze Tan @ 2026-07-24 10:40 UTC (permalink / raw)
  To: sfrench, linkinjeon, slow, pc, jra, tom, chenxiaosong, linux-cifs; +Cc: tanze

Register the trusted xattr handler and add trusted.* to the native
prefix table. The handler sends native trusted names only on negotiated
SMB3 POSIX mounts; non-POSIX user.trusted.* EAs retain their existing
user namespace mapping.

Hide native trusted names from listxattr callers without CAP_SYS_ADMIN.
Reject the XFS trusted.SGI_ACL_* names before matching the trusted
prefix.

Run the following as root on a POSIX mount to check that a write clears
security.capability without removing trusted.name:

  $ CIFS_MNT=/path/to/cifs-mount
  $ file="$CIFS_MNT/trusted-preserve"
  $ touch "$file"
  $ setfattr -n trusted.name -v value "$file"
  $ printf 'data\n' >> "$file"

Signed-off-by: Ze Tan <tanze@kylinos.cn>
---
 fs/smb/client/cifsfs.h  |  1 +
 fs/smb/client/smb2ops.c |  3 +++
 fs/smb/client/xattr.c   | 51 +++++++++++++++++++++++++++++++++++++----
 3 files changed, 50 insertions(+), 5 deletions(-)

diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index 4ab1a203be3e..1c04dc07a2ff 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -136,6 +136,7 @@ int cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
 enum cifs_ea_name_type {
 	CIFS_EA_USER,
 	CIFS_EA_NATIVE,
+	CIFS_EA_HIDDEN,
 };
 
 enum cifs_ea_name_type
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index fd778d903e93..f38230e4c31d 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -1103,6 +1103,8 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
 
 			name_type = cifs_ea_name_type(name, name_len,
 						     posix_extensions);
+			if (name_type == CIFS_EA_HIDDEN)
+				goto next;
 			if (name_type == CIFS_EA_NATIVE)
 				user_name_len = name_len + 1;
 			else
@@ -1130,6 +1132,7 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
 			}
 		}
 
+next:
 		if (!src->next_entry_offset)
 			break;
 
diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c
index 59f6655a1b34..4b6736a6b375 100644
--- a/fs/smb/client/xattr.c
+++ b/fs/smb/client/xattr.c
@@ -7,6 +7,7 @@
  */
 
 #include <linux/fs.h>
+#include <linux/capability.h>
 #include <linux/posix_acl_xattr.h>
 #include <linux/slab.h>
 #include <linux/xattr.h>
@@ -36,7 +37,7 @@
 #define SMB3_XATTR_CIFS_NTSD_FULL "system.smb3_ntsd_full" /* owner/DACL/SACL */
 #define SMB3_XATTR_ATTRIB "smb3.dosattrib"  /* full name: user.smb3.dosattrib */
 #define SMB3_XATTR_CREATETIME "smb3.creationtime"  /* user.smb3.creationtime */
-enum { XATTR_USER, XATTR_SECURITY,
+enum { XATTR_USER, XATTR_TRUSTED, XATTR_SECURITY,
 	XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT,
 	XATTR_CIFS_NTSD_SACL, XATTR_CIFS_NTSD_OWNER,
 	XATTR_CIFS_NTSD, XATTR_CIFS_NTSD_FULL };
@@ -46,6 +47,15 @@ static const char * const cifs_native_xattr_names[] = {
 	XATTR_SECURITY_PREFIX "xfstests",
 };
 
+static const char * const cifs_native_xattr_prefixes[] = {
+	XATTR_TRUSTED_PREFIX,
+};
+
+static const char * const cifs_unsupported_xattr_names[] = {
+	XATTR_TRUSTED_PREFIX "SGI_ACL_FILE",
+	XATTR_TRUSTED_PREFIX "SGI_ACL_DEFAULT",
+};
+
 enum cifs_ea_name_type
 cifs_ea_name_type(const char *name, size_t name_len, bool posix_extensions)
 {
@@ -54,6 +64,14 @@ cifs_ea_name_type(const char *name, size_t name_len, bool posix_extensions)
 	if (!posix_extensions)
 		return CIFS_EA_USER;
 
+	for (i = 0; i < ARRAY_SIZE(cifs_unsupported_xattr_names); i++) {
+		size_t xattr_len = strlen(cifs_unsupported_xattr_names[i]);
+
+		if (name_len == xattr_len &&
+		    !memcmp(name, cifs_unsupported_xattr_names[i], name_len))
+			return CIFS_EA_HIDDEN;
+	}
+
 	for (i = 0; i < ARRAY_SIZE(cifs_native_xattr_names); i++) {
 		size_t xattr_len = strlen(cifs_native_xattr_names[i]);
 
@@ -62,6 +80,15 @@ cifs_ea_name_type(const char *name, size_t name_len, bool posix_extensions)
 			return CIFS_EA_NATIVE;
 	}
 
+	for (i = 0; i < ARRAY_SIZE(cifs_native_xattr_prefixes); i++) {
+		size_t prefix_len = strlen(cifs_native_xattr_prefixes[i]);
+
+		if (name_len > prefix_len &&
+		    !memcmp(name, cifs_native_xattr_prefixes[i], prefix_len))
+			return capable(CAP_SYS_ADMIN) ? CIFS_EA_NATIVE :
+						       CIFS_EA_HIDDEN;
+	}
+
 	return CIFS_EA_USER;
 }
 
@@ -76,6 +103,10 @@ static int cifs_build_ea_name(int xattr_flag, const char *name, char *ea_name,
 		prefix = XATTR_SECURITY_PREFIX;
 		prefix_len = XATTR_SECURITY_PREFIX_LEN;
 		break;
+	case XATTR_TRUSTED:
+		prefix = XATTR_TRUSTED_PREFIX;
+		prefix_len = XATTR_TRUSTED_PREFIX_LEN;
+		break;
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -186,6 +217,7 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
 	}
 
 	switch (handler->flags) {
+	case XATTR_TRUSTED:
 	case XATTR_SECURITY:
 		if (!pTcon->posix_extensions)
 			goto out;
@@ -195,8 +227,8 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
 			goto out;
 		server_ea_name = ea_name;
 		lease_inode = inode;
-		cifs_dbg(FYI, "%s: setting security xattr %s\n",
-			 __func__, name);
+		cifs_dbg(FYI, "%s: setting native xattr %s\n",
+			 __func__, server_ea_name);
 		goto set_ea;
 
 	case XATTR_USER:
@@ -372,6 +404,7 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
 
 	/* return alt name if available as pseudo attr */
 	switch (handler->flags) {
+	case XATTR_TRUSTED:
 	case XATTR_SECURITY:
 		if (!pTcon->posix_extensions)
 			goto out;
@@ -381,8 +414,8 @@ static int cifs_xattr_get(const struct xattr_handler *handler,
 			goto out;
 		server_ea_name = ea_name;
 		lease_inode = inode;
-		cifs_dbg(FYI, "%s: querying security xattr %s\n",
-			 __func__, name);
+		cifs_dbg(FYI, "%s: querying native xattr %s\n",
+			 __func__, server_ea_name);
 		goto query_ea;
 
 	case XATTR_USER:
@@ -531,6 +564,13 @@ static const struct xattr_handler cifs_user_xattr_handler = {
 	.set = cifs_xattr_set,
 };
 
+static const struct xattr_handler cifs_trusted_xattr_handler = {
+	.prefix = XATTR_TRUSTED_PREFIX,
+	.flags = XATTR_TRUSTED,
+	.get = cifs_xattr_get,
+	.set = cifs_xattr_set,
+};
+
 static const struct xattr_handler cifs_security_xattr_handler = {
 	.prefix = XATTR_SECURITY_PREFIX,
 	.flags = XATTR_SECURITY,
@@ -622,6 +662,7 @@ static const struct xattr_handler smb3_ntsd_full_xattr_handler = {
 
 const struct xattr_handler * const cifs_xattr_handlers[] = {
 	&cifs_user_xattr_handler,
+	&cifs_trusted_xattr_handler,
 	&cifs_security_xattr_handler,
 	&cifs_os2_xattr_handler,
 	&cifs_cifs_acl_xattr_handler,
-- 
2.43.0


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

* Re: [PATCH 00/15] smb: add native security and trusted xattrs
  2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
                   ` (14 preceding siblings ...)
  2026-07-24 10:40 ` [PATCH 15/15] smb: client: support trusted EAs " Ze Tan
@ 2026-07-24 11:49 ` Ralph Boehme
  15 siblings, 0 replies; 17+ messages in thread
From: Ralph Boehme @ 2026-07-24 11:49 UTC (permalink / raw)
  To: Ze Tan, sfrench, linkinjeon, pc, jra, tom, chenxiaosong,
	linux-cifs


[-- Attachment #1.1: Type: text/plain, Size: 500 bytes --]

On 7/24/26 12:39 PM, Ze Tan wrote:
> Native xattrs are returned without a prefix when EAs are listed. Their
> corresponding user.* backing xattrs are hidden in native mode.
sorry, but I don't really understand how this is supposed to work... :)

Maybe you can provide a simple table that shows the representation on 
server, client and protocol for the relevant namespaces?

 From your commit message alone, I couldn't tell how the user namespace 
is intended to be handled.

Thanks!



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 840 bytes --]

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

end of thread, other threads:[~2026-07-24 11:50 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-24 10:39 [PATCH 00/15] smb: add native security and trusted xattrs Ze Tan
2026-07-24 10:39 ` [PATCH 01/15] ksmbd: extract SMB EA backing xattr name mapping Ze Tan
2026-07-24 10:39 ` [PATCH 02/15] ksmbd: extract SMB EA response name handling Ze Tan
2026-07-24 10:39 ` [PATCH 03/15] ksmbd: match SMB2 EA names by exact length Ze Tan
2026-07-24 10:39 ` [PATCH 04/15] ksmbd: handle empty and disappearing EAs Ze Tan
2026-07-24 10:39 ` [PATCH 05/15] ksmbd: take mount write access for CREATE EAs Ze Tan
2026-07-24 10:40 ` [PATCH 06/15] ksmbd: bound SMB2 EA name debug output Ze Tan
2026-07-24 10:40 ` [PATCH 07/15] ksmbd: query security.capability on POSIX EA handles Ze Tan
2026-07-24 10:40 ` [PATCH 08/15] ksmbd: set " Ze Tan
2026-07-24 10:40 ` [PATCH 09/15] ksmbd: support security.xfstests " Ze Tan
2026-07-24 10:40 ` [PATCH 10/15] ksmbd: support trusted xattrs " Ze Tan
2026-07-24 10:40 ` [PATCH 11/15] smb: client: prepare EA opens for inode lease reuse Ze Tan
2026-07-24 10:40 ` [PATCH 12/15] smb: client: avoid batch oplocks for reentrant POSIX EAs Ze Tan
2026-07-24 10:40 ` [PATCH 13/15] smb: client: gate security.capability EA on POSIX mounts Ze Tan
2026-07-24 10:40 ` [PATCH 14/15] smb: client: support security.xfstests " Ze Tan
2026-07-24 10:40 ` [PATCH 15/15] smb: client: support trusted EAs " Ze Tan
2026-07-24 11:49 ` [PATCH 00/15] smb: add native security and trusted xattrs Ralph Boehme

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.