[PATCH 13/15] smb: client: gate security.capability EA on POSIX mounts
Ze Tan <[email protected]> Fri, 24 Jul 2026 18:40:07 +0800
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <2d0f64ba1e8f0fa18b67696281b39eec93e908e2.1784888897.git.tanze@kylinos.cn> |
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 <[email protected]> --- 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