[PATCH 07/15] ksmbd: query security.capability on POSIX EA handles

Ze Tan <[email protected]> Fri, 24 Jul 2026 18:40:01 +0800
Newsgroups org.kernel.vger.linux-cifs
Message-ID <e24e14bf49f1749d8caa863980263b7307441f85.1784888897.git.tanze@kylinos.cn>
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 <[email protected]>
---
 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