[RFC PATCH v2 1/9] smb: client: support security.capability over EAs

From: Ze Tan

Date: Thu Jul 16 2026 - 23:43:23 EST


generic/093 uses setcap and getcap to check that a write clears the file
capability. The CIFS client does not have a security xattr handler, so
these commands cannot set or read security.capability through SMB EAs.

Register a security xattr handler and build the complete EA name for
SET_INFO and QUERY_INFO. Keep security.capability unchanged in EA lists.
Reject other security xattrs.

Signed-off-by: Ze Tan <tanze@xxxxxxxxxx>
---
fs/smb/client/smb2ops.c | 26 ++++++++++++---
fs/smb/client/xattr.c | 70 ++++++++++++++++++++++++++++++++++++++---
2 files changed, 87 insertions(+), 9 deletions(-)

diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index cbd51a08e97e..320742ec3ed8 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -14,6 +14,7 @@
#include <crypto/aead.h>
#include <linux/fiemap.h>
#include <linux/folio_queue.h>
+#include <linux/xattr.h>
#include <uapi/linux/magic.h>
#include "cifsfs.h"
#include "cifsglob.h"
@@ -1043,6 +1044,15 @@ static int smb2_query_file_info(const unsigned int xid, struct cifs_tcon *tcon,
}

#ifdef CONFIG_CIFS_XATTR
+static bool cifs_passthrough(const char *name, size_t name_len)
+{
+ if (name_len == sizeof(XATTR_NAME_CAPS) - 1 &&
+ !memcmp(name, XATTR_NAME_CAPS, name_len))
+ return true;
+
+ return false;
+}
+
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,
@@ -1085,16 +1095,24 @@ 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;
+ bool passthrough_name;
+
+ passthrough_name = cifs_passthrough(name, name_len);
+ if (passthrough_name)
+ 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 (!passthrough_name) {
+ memcpy(dst, "user.", 5);
+ dst += 5;
+ }
memcpy(dst, src->ea_data, name_len);
dst += name_len;
*dst = 0;
diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c
index 5091f6c0d7fe..8fa40593bc93 100644
--- a/fs/smb/client/xattr.c
+++ b/fs/smb/client/xattr.c
@@ -36,12 +36,38 @@
#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 int cifs_build_ea_name(int xattr_flag, const char *name, char *ea_name,
+ size_t ea_name_size)
+{
+ size_t name_len;
+
+ switch (xattr_flag) {
+ case XATTR_SECURITY:
+ /*
+ * Only security.capability has native Linux security xattr
+ * semantics on POSIX-capable servers.
+ */
+ if (strcmp(name, XATTR_CAPS_SUFFIX))
+ return -EOPNOTSUPP;
+ name_len = strlen(name);
+ if (ea_name_size <= XATTR_SECURITY_PREFIX_LEN ||
+ name_len > ea_name_size - XATTR_SECURITY_PREFIX_LEN - 1)
+ return -ERANGE;
+ memcpy(ea_name, XATTR_SECURITY_PREFIX,
+ XATTR_SECURITY_PREFIX_LEN);
+ memcpy(ea_name + XATTR_SECURITY_PREFIX_LEN, name,
+ name_len + 1);
+ return XATTR_SECURITY_PREFIX_LEN + name_len;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
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 +130,8 @@ 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;

tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink))
@@ -131,6 +159,16 @@ static int cifs_xattr_set(const struct xattr_handler *handler,
}

switch (handler->flags) {
+ case XATTR_SECURITY:
+ rc = cifs_build_ea_name(handler->flags, name, ea_name,
+ sizeof(ea_name));
+ if (rc < 0)
+ goto out;
+ server_ea_name = ea_name;
+ 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 ((strcmp(name, CIFS_XATTR_ATTRIB) == 0) ||
@@ -149,12 +187,13 @@ 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,
+ full_path, server_ea_name, value, (__u16)size,
cifs_sb->local_nls, cifs_sb);
if (rc == 0)
inode_set_ctime_current(inode);
@@ -280,6 +319,8 @@ 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;

tlink = cifs_sb_tlink(cifs_sb);
if (IS_ERR(tlink))
@@ -297,6 +338,16 @@ static int cifs_xattr_get(const struct xattr_handler *handler,

/* return alt name if available as pseudo attr */
switch (handler->flags) {
+ case XATTR_SECURITY:
+ rc = cifs_build_ea_name(handler->flags, name, ea_name,
+ sizeof(ea_name));
+ if (rc < 0)
+ goto out;
+ server_ea_name = ea_name;
+ 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 ((strcmp(name, CIFS_XATTR_ATTRIB) == 0) ||
@@ -309,12 +360,13 @@ 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);
+ full_path, server_ea_name, value, size, cifs_sb);
break;

case XATTR_CIFS_ACL:
@@ -438,6 +490,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 +581,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