From a0e2062ce339435e77334e2bf31e392142ce62c5 Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Fri, 29 Dec 2023 08:03:17 -0800 Subject: [PATCH] NAS-122619 / 24.04/ fs/cifs - add ZFS ACL support to SMB client (#144) Add an xattr handler with same namespace as ZFS ACL xattr handler to allow userspace utilities to easily preserve and convert contents of SMB Security Descriptor DACL into native ZFS ACL when ingesting data during migration via SMB client (for example using rsync with the explicit option to preserve the xattr in question). This PR also adds a new procfs endpoint: /proc/fs/cifs/zfsacl_configuration_options that can be used to control error handling for cases where we can't convert SID into a Unix ID, and currently also whether we allow setting NT ACL via xattr writes on remote SMB server (disabled by default). --- fs/smb/client/cifs_debug.c | 90 +++ fs/smb/client/cifsacl.c | 1139 ++++++++++++++++++++++++++++++++++ fs/smb/client/cifsglob.h | 4 + fs/smb/client/cifsproto.h | 12 + fs/smb/client/nfs41acl_xdr.h | 151 +++++ fs/smb/client/xattr.c | 129 +++- 6 files changed, 1524 insertions(+), 1 deletion(-) create mode 100644 fs/smb/client/nfs41acl_xdr.h diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index e03c890de0a0..edb9f476f2e2 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -26,6 +26,9 @@ #include "smbdirect.h" #endif #include "cifs_swn.h" +#ifdef CONFIG_TRUENAS +#include "nfs41acl_xdr.h" +#endif void cifs_dump_mem(char *label, void *data, int length) @@ -845,6 +848,10 @@ static const struct proc_ops cifs_security_flags_proc_ops; static const struct proc_ops cifs_linux_ext_proc_ops; static const struct proc_ops cifs_mount_params_proc_ops; +#ifdef CONFIG_TRUENAS +static const struct proc_ops cifs_zfsacl_flags_proc_ops; +#endif + void cifs_proc_init(void) { @@ -870,6 +877,11 @@ cifs_proc_init(void) proc_create("mount_params", 0444, proc_fs_cifs, &cifs_mount_params_proc_ops); +#ifdef CONFIG_TRUENAS + proc_create("zfsacl_configuration_flags", 0644, proc_fs_cifs, + &cifs_zfsacl_flags_proc_ops); +#endif + #ifdef CONFIG_CIFS_DFS_UPCALL proc_create("dfscache", 0644, proc_fs_cifs, &dfscache_proc_ops); #endif @@ -910,9 +922,14 @@ cifs_proc_clean(void) remove_proc_entry("LookupCacheEnabled", proc_fs_cifs); remove_proc_entry("mount_params", proc_fs_cifs); +#ifdef CONFIG_TRUENAS + remove_proc_entry("zfsacl_configuration_flags", proc_fs_cifs); +#endif + #ifdef CONFIG_CIFS_DFS_UPCALL remove_proc_entry("dfscache", proc_fs_cifs); #endif + #ifdef CONFIG_CIFS_SMB_DIRECT remove_proc_entry("rdma_readwrite_threshold", proc_fs_cifs); remove_proc_entry("smbd_max_frmr_depth", proc_fs_cifs); @@ -1161,6 +1178,79 @@ static const struct proc_ops cifs_security_flags_proc_ops = { .proc_write = cifs_security_flags_proc_write, }; +#ifdef CONFIG_TRUENAS +static int cifs_zfsacl_flags_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "0x%x\n", global_zfsaclflags); + return 0; +} + +static int cifs_zfsacl_flags_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, cifs_zfsacl_flags_proc_show, NULL); +} + +static ssize_t cifs_zfsacl_flags_proc_write(struct file *file, + const char __user *buffer, + size_t count, + loff_t *ppos) +{ + int rc; + unsigned int flags, idmap_flags; + char flags_string[6] = { 0 }; + + if (count >= sizeof(flags_string)) + return -EINVAL; + + if (copy_from_user(flags_string, buffer, count)) + return -EFAULT; + + rc = kstrtouint(flags_string, 0, &flags); + if (rc) { + cifs_dbg(VFS, "failed to convert flags [%s] to int\n", + flags_string); + return rc; + } + + if (flags & ~MODFLAG_ALL) { + cifs_dbg(VFS, "Invalid flags: 0x%08x\n", flags & ~MODFLAG_ALL); + return -EINVAL; + } + + idmap_flags = flags & MODFLAG_ALL_IDMAP; + + if (idmap_flags == 0) { + cifs_dbg(VFS, "At least one idmap-related flag must be set"); + return -EINVAL; + } + + if ((idmap_flags == MODFLAG_ALL_IDMAP) || + (idmap_flags == (MODFLAG_FAIL_UNKNOWN_SID | MODFLAG_SKIP_UNKNOWN_SID)) || + (idmap_flags == (MODFLAG_FAIL_UNKNOWN_SID | MODFLAG_MAP_UNKNOWN_SID)) || + (idmap_flags == (MODFLAG_SKIP_UNKNOWN_SID | MODFLAG_MAP_UNKNOWN_SID))) { + cifs_dbg(VFS, "Only one idmap-related flag may be set. Current settings: " + "fail_unknown_sid: %s, skip_unknown_sid: %s, map_unknown_sid: %s, " + "raw: 0x%08x\n", + idmap_flags & MODFLAG_FAIL_UNKNOWN_SID ? "true" : "false", + idmap_flags & MODFLAG_SKIP_UNKNOWN_SID ? "true" : "false", + idmap_flags & MODFLAG_MAP_UNKNOWN_SID ? "true" : "false", + idmap_flags); + return -EINVAL; + } + + global_zfsaclflags = flags; + return count; +} + +static const struct proc_ops cifs_zfsacl_flags_proc_ops = { + .proc_open = cifs_zfsacl_flags_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, + .proc_write = cifs_zfsacl_flags_proc_write, +}; +#endif + /* To make it easier to debug, can help to show mount params */ static int cifs_mount_params_proc_show(struct seq_file *m, void *v) { diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 1d294d53f662..f8d7d4c47440 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -25,10 +25,20 @@ #include "fs_context.h" #include "cifs_fs_sb.h" #include "cifs_unicode.h" +#ifdef CONFIG_TRUENAS +#include "nfs41acl_xdr.h" +#endif /* security id for everyone/world system group */ static const struct smb_sid sid_everyone = { 1, 1, {0, 0, 0, 0, 0, 1}, {0} }; +#ifdef CONFIG_TRUENAS +static const struct cifs_sid sid_creator_owner = { + 1, 1, {0, 0, 0, 0, 0, 3}, {0} }; + +static const struct cifs_sid sid_creator_group = { + 1, 1, {0, 0, 0, 0, 0, 3}, {cpu_to_le32(1)} }; +#endif /* CONFIG_TRUENAS */ /* security id for Authenticated Users system group */ static const struct smb_sid sid_authusers = { 1, 1, {0, 0, 0, 0, 0, 5}, {cpu_to_le32(11)} }; @@ -1809,3 +1819,1132 @@ int cifs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, return -EOPNOTSUPP; #endif } + +#ifdef CONFIG_TRUENAS +enum account_special_sid_type { + ACCOUNT_SID_UNKNOWN, + ACCOUNT_SID_UNIX_USER, + ACCOUNT_SID_UNIX_GROUP, + ACCOUNT_SID_NFS_USER, + ACCOUNT_SID_NFS_GROUP, +}; + +unsigned int global_zfsaclflags = MODFLAG_DEFAULTS; + +static const struct { + u32 nfs_perm; + u32 smb_perm; +} nfsperm2smb[] = { + { ACE4_READ_DATA, FILE_READ_DATA}, + { ACE4_WRITE_DATA, FILE_WRITE_DATA}, + { ACE4_APPEND_DATA, FILE_APPEND_DATA}, + { ACE4_READ_NAMED_ATTRS, FILE_READ_EA}, + { ACE4_WRITE_NAMED_ATTRS, FILE_WRITE_EA}, + { ACE4_EXECUTE, FILE_EXECUTE}, + { ACE4_DELETE_CHILD, FILE_DELETE_CHILD}, + { ACE4_READ_ATTRIBUTES, FILE_READ_ATTRIBUTES}, + { ACE4_WRITE_ATTRIBUTES, FILE_WRITE_ATTRIBUTES}, + { ACE4_DELETE, DELETE}, + { ACE4_READ_ACL, READ_CONTROL}, + { ACE4_WRITE_ACL, WRITE_DAC}, + { ACE4_WRITE_OWNER, WRITE_OWNER}, + { ACE4_SYNCHRONIZE, SYNCHRONIZE}, +}; + +static const struct { + u32 nfs_flag; + u8 smb_flag; +} nfsflag2smb[] = { + { ACE4_FILE_INHERIT_ACE, OBJECT_INHERIT_ACE}, + { ACE4_DIRECTORY_INHERIT_ACE, CONTAINER_INHERIT_ACE}, + { ACE4_NO_PROPAGATE_INHERIT_ACE, NO_PROPAGATE_INHERIT_ACE}, + { ACE4_INHERIT_ONLY_ACE, INHERIT_ONLY_ACE}, + { ACE4_INHERITED_ACE, INHERITED_ACE}, +}; + +static int +set_xdr_ace(u32 *acep, + u32 who_iflag, + u32 who_id, + u32 ace_type, + u32 access_mask, + u32 flags) +{ + /* Audit and Alarm are not currently supported */ + if (ace_type > ACE4_ACCESS_DENIED_ACE_TYPE) + return -EINVAL; + + *acep++ = htonl(ace_type); + *acep++ = htonl(flags); + *acep++ = htonl(who_iflag); + *acep++ = htonl(access_mask); + *acep++ = htonl(who_id); + + return 0; +} + +static enum account_special_sid_type +get_account_special_sid_type(struct cifs_sid *psid) +{ + if (psid->num_subauth == 2) { + if (psid->sub_auth[0] == sid_unix_groups.sub_auth[0]) { + return ACCOUNT_SID_UNIX_GROUP; + } else if (psid->sub_auth[0] == sid_unix_users.sub_auth[0]) { + return ACCOUNT_SID_UNIX_USER; + } + } else if (psid->num_subauth == 3) { + // S-1-5-88-1- - NFS user + // S-1-5-88-2- - NFS group + if (psid->sub_auth[0] != sid_unix_NFS_groups.sub_auth[0]) { + // First subauth doesn't match, not an NFS SID + return ACCOUNT_SID_UNKNOWN; + } + if (psid->sub_auth[1] == sid_unix_NFS_groups.sub_auth[1]) { + return ACCOUNT_SID_NFS_GROUP; + } else if (psid->sub_auth[1] == sid_unix_NFS_groups.sub_auth[1]) { + return ACCOUNT_SID_NFS_USER; + } + } + + return ACCOUNT_SID_UNKNOWN; +} + +/* + * Per Microsoft Win32 documentation, an empty DACL (i.e. one that + * is properly initialized and contains no ACEs) grants no access to the + * object it is assigned to. We can't set an empty ACL on ZFS, and so the + * best we can do is create an ACL with a single entry granting file owner + * owner@ no rights. Note that in Windows and Unix the file owner is able + * to override the ACL. + */ +static int +generate_empty_zfsacl(char **buf_out) +{ + u32 *xdrbuf = NULL, *zfsacl; + xdrbuf = kzalloc(ACES_TO_XDRSIZE(1), GFP_KERNEL); + if (!xdrbuf) + return -ENOMEM; + + zfsacl = xdrbuf; + *zfsacl++ = 0; /* acl_flags */ + *zfsacl++ = htonl(1); /* acl count */ + + set_xdr_ace(zfsacl, ACEI4_SPECIAL_WHO, ACE4_SPECIAL_OWNER, + ACE4_ACCESS_ALLOWED_ACE_TYPE, 0, 0); + + *buf_out = (char *)xdrbuf; + return ACES_TO_XDRSIZE(1); +} + +/* + * Per Microsoft Win32 documentation, a NULL DACL grants full access to + * any user that requests it; normal security checking is not performed + * with respect to the object. We can't set a NULL ZFS ACL and so + * the best we can do is set one granting everyone@ full control. + */ +static int +generate_null_zfsacl(char **buf_out) +{ + u32 *xdrbuf = NULL, *zfsacl; + + xdrbuf = kzalloc(ACES_TO_XDRSIZE(1), GFP_KERNEL); + if (!xdrbuf) + return -ENOMEM; + + zfsacl = xdrbuf; + *zfsacl++ = 0; /* acl_flags */ + *zfsacl++ = htonl(1); /* acl count */ + + set_xdr_ace(zfsacl, ACEI4_SPECIAL_WHO, ACE4_SPECIAL_EVERYONE, + ACE4_ACCESS_ALLOWED_ACE_TYPE, ACE4_ALL_PERMS, 0); + + *buf_out = (char *)xdrbuf; + return ACES_TO_XDRSIZE(1); +} + +/* + * Convert generic access into NFSv4 perms + */ +static u32 +generic_to_nfs(u32 generic_access) +{ + u32 out = 0; + if (generic_access == GENERIC_ALL) { + return ACE4_ALL_PERMS; + } + + if (generic_access & GENERIC_READ) { + out |= ACE4_READ_PERMS; + } + + if (generic_access & GENERIC_EXECUTE) { + out |= ACE4_EXECUTE; + } + + if (generic_access & GENERIC_WRITE) { + out |= (ACE4_WRITE_PERMS|ACE4_DELETE); + } + + return out; +} + +static int +convert_smb_access_to_nfs(u32 smbaccess, u32 *nfs_access_out) +{ + int i; + u32 perms = generic_to_nfs(smbaccess); + + if (perms == ACE4_ALL_PERMS) { + *nfs_access_out = perms; + return 0; + } + + for (i = 0; i < (sizeof(nfsperm2smb) / sizeof(nfsperm2smb[0])); i++) { + if (smbaccess & nfsperm2smb[i].smb_perm) + perms |= nfsperm2smb[i].nfs_perm; + } + + *nfs_access_out = perms; + return 0; +} + +static int +convert_smb_flags_to_nfs(u8 smbflags, u32 *nfs_flags_out) +{ + int i; + u32 flags = 0; + + if (smbflags & (SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG)) { + cifs_dbg(VFS, "%s: ACE contains unsupported flags 0x%04x\n", + __func__, smbflags); + return -EINVAL; + } + + for (i = 0; i < (sizeof(nfsflag2smb) / sizeof(nfsflag2smb[0])); i++) { + if (smbflags & nfsflag2smb[i].smb_flag) + flags |= nfsflag2smb[i].nfs_flag; + } + + *nfs_flags_out = flags; + return 0; +} + +static int +convert_smb_ace_type_to_nfs(u8 smbacetype, u32 *nfs_ace_type_out) +{ + switch (smbacetype) { + case ACCESS_ALLOWED_ACE_TYPE: + *nfs_ace_type_out = ACE4_ACCESS_ALLOWED_ACE_TYPE; + break; + case ACCESS_DENIED_ACE_TYPE: + *nfs_ace_type_out = ACE4_ACCESS_DENIED_ACE_TYPE; + break; + default: + cifs_dbg(VFS, "%s: ACE contains unsupported ace type 0x%04x\n", + __func__, smbacetype); + return -EINVAL; + } + + return 0; +} + +/* + * Convert SID into NFS4 ID and type. Try to map BUILTIN / SPECIAL sids + * directly to NFS4 special type where possible (to avoid upcall to winbindd). + * + * Since the existing idmap key implementation does support return of both + * users and groups in one call, we first try to retrieve group (because this + * is in real life much more likely). If retrieving as group fails, we retry + * as user. + */ +static int +convert_smb_sid_to_nfs_who_special(struct cifs_sid *psid, + u32 *iflag, + u32 *who_id, + u32 *flags) +{ + /* + * Check for direct mapping of owner@, group@, and everyone@ + */ + if (psid->num_subauth > 3) { + return -ENOENT; + } + + if (compare_sids(psid, &sid_everyone) == 0) { + *iflag = ACEI4_SPECIAL_WHO; + *who_id = ACE4_SPECIAL_EVERYONE; + return 0; + } + + if (compare_sids(psid, &sid_creator_owner) == 0) { + *iflag = ACEI4_SPECIAL_WHO; + *who_id = ACE4_SPECIAL_OWNER; + return 0; + } + + if (compare_sids(psid, &sid_creator_group) == 0) { + *iflag = ACEI4_SPECIAL_WHO; + *who_id = ACE4_SPECIAL_GROUP; + *flags |= ACE4_IDENTIFIER_GROUP; + return 0; + } + + /* + * SID communicating Unix mode can be safely skipped since we will + * get permissions info from other ACL entries + */ + if (compare_sids(psid, &sid_unix_NFS_mode) == 0) + return -EAGAIN; + + /* + * SID may directly encode a Unix uid or gid. + */ + switch (get_account_special_sid_type(psid)) { + case ACCOUNT_SID_UNIX_GROUP: + *flags |= ACE4_IDENTIFIER_GROUP; + *who_id = le32_to_cpu(psid->sub_auth[1]); + return 0; + case ACCOUNT_SID_UNIX_USER: + *who_id = le32_to_cpu(psid->sub_auth[1]); + return 0; + case ACCOUNT_SID_NFS_GROUP: + *flags |= ACE4_IDENTIFIER_GROUP; + *who_id = le32_to_cpu(psid->sub_auth[2]); + return 0; + case ACCOUNT_SID_NFS_USER: + *who_id = le32_to_cpu(psid->sub_auth[2]); + return 0; + case ACCOUNT_SID_UNKNOWN: + // This SID most likely is for a user or group + // which means we must make an upcall + break; + } + + return -ENOENT; +} + +static int +convert_smb_sid_to_nfs_who(struct cifs_sid *psid, u32 *iflag, u32 *who_id, u32 *flags) +{ + char *sidstr; + const struct cred *saved_cred; + struct key *sidkey; + uint sidtype = SIDGROUP; + int rc; + + if (unlikely(psid->num_subauth > SID_MAX_SUB_AUTHORITIES)) { + cifs_dbg(FYI, "%s: subauthority count [%u] exceeds " + "maxiumum possible value.\n", + __func__, psid->num_subauth); + return -EINVAL; + } + + rc = convert_smb_sid_to_nfs_who_special(psid, iflag, who_id, flags); + switch (rc) { + case -ENOENT: + // We need to perform a lookup; + break; + case -EAGAIN: + case 0: + // EAGAIN means skip this entry + // zero means that it was converted + return rc; + } + + saved_cred = override_creds(root_cred); + +try_upcall_to_get_id: + sidstr = sid_to_key_str(psid, sidtype); + if (!sidstr) { + revert_creds(saved_cred); + return -ENOMEM; + } + sidkey = request_key(&cifs_idmap_key_type, sidstr, ""); + if (IS_ERR(sidkey)) { + if (sidkey == NULL) { + revert_creds(saved_cred); + kfree(sidstr); + return -ENOMEM; + } + + if ((PTR_ERR(sidkey) == -ENOKEY) && + (sidtype == SIDGROUP)) { + /* + * No group, retry as SIDOWNER + */ + kfree(sidstr); + sidtype = SIDOWNER; + goto try_upcall_to_get_id; + } + + cifs_dbg(FYI, "%s: Can't map SID %s to a %cid\n", + __func__, sidstr, sidtype == SIDOWNER ? 'u' : 'g'); + + kfree(sidstr); + revert_creds(saved_cred); + return PTR_ERR(sidkey); + } + + BUILD_BUG_ON(sizeof(uid_t) != sizeof(gid_t)); + if (sidkey->datalen != sizeof(uid_t)) { + cifs_dbg(FYI, "%s: Downcall for sid [%s] contained malformed " + "key (datalen=%hu)\n", + __func__, sidstr, sidkey->datalen); + key_invalidate(sidkey); + key_put(sidkey); + revert_creds(saved_cred); + kfree(sidstr); + return -ENOKEY; + } + + if (sidtype == SIDGROUP) { + *flags |= ACE4_IDENTIFIER_GROUP; + } + + memcpy(who_id, &sidkey->payload.data[0], sizeof(uid_t)); + key_put(sidkey); + revert_creds(saved_cred); + kfree(sidstr); + + return 0; +} + +static int +do_ace_conversion(struct cifs_ace *pace, + u32 *p_perms, + u32 *p_iflag, + u32 *p_who_id, + u32 *p_flags, + u32 *p_ace_type) +{ + int error; + char *sid_str; + + if (le16_to_cpu(pace->size) < 16) { + cifs_dbg(VFS, "%s: NT ACE size is invalid %d\n", + __func__, le16_to_cpu(pace->size)); + return -E2BIG; + } + + error = convert_smb_ace_type_to_nfs(pace->type, p_ace_type); + if (error) { + return error; + } + + error = convert_smb_access_to_nfs(pace->access_req, p_perms); + if (error) { + return error; + } + + error = convert_smb_flags_to_nfs(pace->flags, p_flags); + if (error) { + return error; + } + + error = convert_smb_sid_to_nfs_who(&pace->sid, p_iflag, p_who_id, p_flags); + if (error == -ENOKEY) { + if (*p_ace_type == ACE4_ACCESS_DENIED_ACE_TYPE) { + sid_str = sid_to_key_str(&pace->sid, SIDOWNER); + if (sid_str == NULL) { + return -ENOMEM; + } + + cifs_dbg(VFS, + "%s: [%s] unable to convert SID into a local " + "ID for a DENY ACL entry. Since omission or " + "alteration of the ACL entry would increase " + "access to the file, this error may not be " + "overriden via client configuration change. " + "Administrative action will be required to " + "either remove the ACL entry from the remote " + "server or map the unknown SID to a local " + "Unix ID on this client\n", __func__, sid_str); + kfree(sid_str); + return -ENOKEY; + } + if (global_zfsaclflags & MODFLAG_SKIP_UNKNOWN_SID) { + return -EAGAIN; + } else if (global_zfsaclflags & MODFLAG_MAP_UNKNOWN_SID) { + *p_who_id = from_kuid(&init_user_ns, current_fsuid()); + return 0; + } + } + + return error; +} + +static bool +combine_with_next(struct cifs_ace *pace, + u32 *p_perms, + u32 *p_iflag, + u32 *p_who_id, + u32 *p_flags, + u32 *p_ace_type) +{ + u32 perms = 0, iflag = 0, who_id = 0, flags = 0, ace_type = 0; + int error; + + error = do_ace_conversion(pace, + &perms, + &iflag, + &who_id, + &flags, + &ace_type); + + /* + * If an error is encountered here, it will also + * be picked up when we formally parse next ACE + * and so we'll handle the error there. + */ + if (error) { + return false; + } + + if (perms != *p_perms) { + return false; + } + + if (ace_type != *p_ace_type) { + return false; + } + + if ((flags & ACE4_INHERIT_ONLY_ACE) == 0) { + return false; + } + + if (iflag != ACEI4_SPECIAL_WHO) { + return false; + } + + *p_iflag = iflag; + *p_who_id = who_id; + *p_flags = (flags & ~ACE4_INHERIT_ONLY_ACE); + return true; +} + +/* + * There are various situations where admin may want to just skip + * certain aces in case of conversion failure. A primary example + * is if ACL contains an ACE for a local user on the remote server. + * In this case (as long as the ACE is ALLOW rather than DENY) it + * is safe (although perhaps incorrect) to simply skip the entry. + * + * Currently this function on success returns number of good ACEs + * added to the acl. + * + * On error return -errno. + */ +static int +convert_smbace_to_nfsace(struct cifs_ace *pace, + u32 *zfsacl, + bool isdir, + uid_t owner, + uid_t group, + bool islast, + bool *pskip_next) +{ + u32 *zace = zfsacl; + u32 perms = 0, iflag = 0, who_id = 0, flags = 0, ace_type = 0; + uid_t to_check; + int error; + + error = do_ace_conversion(pace, + &perms, + &iflag, + &who_id, + &flags, + &ace_type); + if (error) { + return error; + } + + to_check = flags & ACE4_IDENTIFIER_GROUP ? group : owner; + + /* + * This is a Samba server implementation detail for NFS4 ACL. + * S-1-3-0 and S-1-3-1 are only valid with INHERIT_ONLY set + * whereas owner@ and group@ in NFS4 ACL carry no such restriction. + * Therefore the server will split owner@ into two separate aces: + * one with S-1-3-0 (or S-1-3-1 in case of group@) and INHERIT_ONLY + * and the other as a normal non-special entry for the ID of the user + * or group with no inheritance flags set. + * + * The SMB server will always present the next ACE as the second of + * the pair and so we peek ahead here. If both halves of pair are + * present, then we combine into a single owner@ or group@ entry. + */ + if ((iflag == 0) && + (who_id == to_check) && + ((flags & ~(ACE4_INHERITED_ACE | ACE4_IDENTIFIER_GROUP)) == 0)) { + struct cifs_ace *next; + if (!isdir) { + iflag = ACEI4_SPECIAL_WHO; + if (flags & ACE4_IDENTIFIER_GROUP) { + who_id = ACE4_SPECIAL_GROUP; + + } else { + who_id = ACE4_SPECIAL_OWNER; + } + + } else if (!islast) { + next = (struct cifs_ace *)((char *)pace + + le16_to_cpu(pace->size)); + *pskip_next = combine_with_next(next, + &perms, + &iflag, + &who_id, + &flags, + &ace_type); + } + } + + error = set_xdr_ace(zace, iflag, who_id, ace_type, perms, flags); + if (error) { + return error; + } + + return 1; +} + +static int +convert_dacl_to_zfsacl(struct cifs_acl *dacl_ptr, + char *end, + struct inode *inode, + char **buf_out) +{ + int good_aces = 0, aces_set; + char *acl_base; + u32 *xdr_base, *zfsacl, num_aces, i; + struct cifs_ace *pace; + bool skip_next = false; + bool isdir = S_ISDIR(inode->i_mode); + uid_t owner, group; + + num_aces = le32_to_cpu(dacl_ptr->num_aces); + if (num_aces > NFS41ACL_MAX_ENTRIES) + return -E2BIG; + + if (num_aces == 0) + return generate_empty_zfsacl(buf_out); + + if (end < (char *)dacl_ptr + le16_to_cpu(dacl_ptr->size)) { + cifs_dbg(VFS, "%s: ACL size [%u] encoded in NT DACL " + "is invalid.\n", + __func__, le16_to_cpu(dacl_ptr->size)); + return -EINVAL; + } + + xdr_base = kzalloc(ACES_TO_XDRSIZE(num_aces), GFP_KERNEL); + if (!xdr_base) + return -ENOMEM; + + zfsacl = (u32 *)xdr_base + NACL_OFFSET; + acl_base = (char *)dacl_ptr + sizeof(struct cifs_acl); + + owner = from_kuid(&init_user_ns, inode->i_uid); + group = from_kgid(&init_user_ns, inode->i_gid); + + for (i = 0; i < num_aces; i++) { + pace = (struct cifs_ace *)(acl_base); + acl_base += pace->size; + + if (end < (char *)acl_base) { + cifs_dbg(VFS, "%s: ACL entry %d in NT DACL has a size " + "[%u] that would exceed the buffer size " + "allocated for DACL.", + __func__, i, pace->size); + kfree(xdr_base); + return -EINVAL; + } + + if (parse_sid(&pace->sid, end)) { + kfree(xdr_base); + return -EINVAL; + } + + if (skip_next) { + skip_next = false; + continue; + } + + aces_set = convert_smbace_to_nfsace(pace, zfsacl, isdir, owner, + group, i == (num_aces -1), &skip_next); + if (aces_set < 0) { + switch (aces_set) { + case -EAGAIN: + // Entry should be skipped + aces_set = 0; + break; + default: + cifs_dbg(VFS, "%s: conversion of ACE %d in " + "DACL could not be converted into " + "local ZFS ACE format: %d\n", + __func__, i, aces_set); + kfree(xdr_base); + return aces_set; + } + } + + good_aces += aces_set; + zfsacl += (aces_set * NACE41_LEN); + } + + xdr_base[0] = htonl(isdir ? ACL4_ISDIR : 0); + xdr_base[1] = htonl(good_aces); + + *buf_out = (char *)xdr_base; + return ACES_TO_XDRSIZE(good_aces); +} + +int ntsd_to_zfsacl_xattr(struct cifs_ntsd *pntsd, + u32 acl_len, + struct inode *inode, + char **buf_out) +{ + struct cifs_acl *dacl_ptr; /* no need for SACL ptr */ + char *end_of_acl = ((char *)pntsd) + acl_len; + __u32 dacloffset; + + if (pntsd == NULL) + return -EIO; + + dacloffset = le32_to_cpu(pntsd->dacloffset); + if (!dacloffset) { + return generate_null_zfsacl(buf_out); + } + + dacl_ptr = (struct cifs_acl *)((char *)pntsd + dacloffset); + if (dacl_ptr == NULL) { + return generate_null_zfsacl(buf_out); + } + + return convert_dacl_to_zfsacl(dacl_ptr, end_of_acl, inode, buf_out); +} + +/* + * Creator-owner and creator-owner-group SIDs are only valid if flags are + * set to INHERIT_ONLY. This means other ones will need to be split into two + * separate entries. + */ +static int calculate_ntsd_acecnt(u32 *zfsacl, u32 acecnt, struct inode *inode, u32 *cnt) +{ + u32 *ace = zfsacl; + u32 i, cnt_out = 0; + u32 flag, iflag, who_id; + bool isdir = S_ISDIR(inode->i_mode); + + for (i = 0; i < acecnt; i++) { + flag = ntohl(*(ace + NA_FLAG_OFFSET)); + iflag = ntohl(*(ace + NA_IFLAG_OFFSET)); + who_id = ntohl(*(ace + NA_WHO_OFFSET)); + + if (!isdir && (flag & DIR_ONLY_FLAGS)) { + /* Not all flags are valid for files */ + return -EINVAL; + } + + if ((flag & ACE4_INHERIT_ONLY_ACE) && + ((flag & (ACE4_DIRECTORY_INHERIT_ACE | \ + ACE4_FILE_INHERIT_ACE)) == 0)) { + /* INHERIT_ONLY without some inherit flags is invalid */ + return -EINVAL; + } + + if (isdir && (iflag == ACEI4_SPECIAL_WHO) && + ((flag & ACE4_INHERIT_ONLY_ACE) == 0) && + ((who_id == ACE4_SPECIAL_OWNER) || (who_id == ACE4_SPECIAL_GROUP))) { + cnt_out += 1; + } + + cnt_out += 1; + ace += NACE41_LEN; + } + + *cnt = cnt_out; + + return 0; +} + +static int +convert_zfsperm_to_ntperm(u32 zfsperms, struct cifs_ace *ace) +{ + u32 access_mask = 0; + int i; + + for (i = 0; i < (sizeof(nfsperm2smb) / sizeof(nfsperm2smb[0])); i++) { + if (zfsperms & nfsperm2smb[i].nfs_perm) { + access_mask |= nfsperm2smb[i].smb_perm; + } + } + + ace->access_req = cpu_to_le32(access_mask); + + return 0; +} + +static int +convert_zfsflag_to_ntflag(u32 zfsflags, struct cifs_ace *ace) +{ + u8 flags = 0; + int i; + + for (i = 0; i < (sizeof(nfsflag2smb) / sizeof(nfsflag2smb[0])); i++) { + + if (zfsflags & nfsflag2smb[i].nfs_flag) { + flags |= nfsflag2smb[i].smb_flag; + } + } + + ace->flags = flags; + return 0; +} + +static int +convert_zfstype_to_nttype(u32 ace_type, struct cifs_ace *ace) +{ + switch (ace_type) { + case ACE4_ACCESS_ALLOWED_ACE_TYPE: + ace->type = ACCESS_ALLOWED_ACE_TYPE; + break; + case ACE4_ACCESS_DENIED_ACE_TYPE: + ace->type = ACCESS_DENIED_ACE_TYPE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +convert_zfswho_to_ntsid(u32 iflag, u32 who_id, struct inode *inode, u32 flags, struct cifs_ace *ace) +{ + + uint sidtype = flags & ACE4_IDENTIFIER_GROUP; + uid_t id; + + if ((iflag & ACEI4_SPECIAL_WHO) == 0) { + /* + * This is not a special entry (owner@, group@, everyone@) + * and so we need to make go through normal conversion + */ + return id_to_sid(who_id, sidtype, &ace->sid); + } + + switch (who_id) { + case ACE4_SPECIAL_EVERYONE: + cifs_copy_sid(&ace->sid, &sid_everyone); + return 0; + break; + case ACE4_SPECIAL_OWNER: + id = from_kuid(&init_user_ns, inode->i_uid); + + if (flags & ACE4_INHERIT_ONLY_ACE) { + cifs_copy_sid(&ace->sid, &sid_creator_owner); + return 0; + } else { + return id_to_sid(id, SIDOWNER, &ace->sid); + } + break; + case ACE4_SPECIAL_GROUP: + id = from_kgid(&init_user_ns, inode->i_gid); + if (flags & ACE4_INHERIT_ONLY_ACE) { + cifs_copy_sid(&ace->sid, &sid_creator_group); + return 0; + } else { + return id_to_sid(id, SIDGROUP, &ace->sid); + } + break; + } + return -EINVAL; +} + +#define BASE_ACE_SIZE (1 + 1 + 2 + 4) /* struct cifs_ace: type, flags, size, access_req */ +#define CIFS_ACE_SIZE(cnt) (BASE_ACE_SIZE + (CIFS_SID_BASE_SIZE + (cnt * 4))) + +static int +convert_zfsace_to_cifs_aces(u32 *zfsace, char *acl_base, struct inode *inode, u16 *size) +{ + u32 perms, flags, iflag, who_id, ace_type; + int error; + u16 out_sz = 0, ace_sz; + struct cifs_ace *ace = (struct cifs_ace *)acl_base; + + ace_type = ntohl(*(zfsace + NA_TYPE_OFFSET)); + flags = ntohl(*(zfsace + NA_FLAG_OFFSET)); + iflag = ntohl(*(zfsace + NA_IFLAG_OFFSET)); + perms = ntohl(*(zfsace + NA_ACCESS_MASK_OFFSET)); + who_id = ntohl(*(zfsace + NA_WHO_OFFSET)); + + /* + * Creator-owner and Creator-owner-group SIDS are only valid + * for ACES with INHERIT_ONLY set. This means that we split + * inheriting owner@ and group@ entries into two separate ACEs with + * an identical access mask. One is non-inheriting for the inode owner + * or group, and the other is inherit-only with the special SID value. + */ + if ((iflag & ACEI4_SPECIAL_WHO) && (who_id != ACE4_SPECIAL_EVERYONE) && + S_ISDIR(inode->i_mode) && ((flags & ACE4_INHERIT_ONLY_ACE) == 0)) { + convert_zfsperm_to_ntperm(perms, ace); + convert_zfsflag_to_ntflag(flags | ACE4_INHERIT_ONLY_ACE, ace); + error = convert_zfstype_to_nttype(ace_type, ace); + if (error) { + return error; + } + + error = convert_zfswho_to_ntsid(iflag, + who_id, + inode, + flags | ACE4_INHERIT_ONLY_ACE, + ace); + if (error) { + return error; + } + + ace_sz = CIFS_ACE_SIZE(ace->sid.num_subauth); + ace->size = cpu_to_le16(ace_sz); + out_sz += ace_sz; + + /* skip forward to next ACE slot */ + ace = (struct cifs_ace *)(acl_base + ace_sz); + flags &= ~DIR_ONLY_FLAGS; + + convert_zfsperm_to_ntperm(perms, ace); + convert_zfsflag_to_ntflag(flags, ace); + error = convert_zfstype_to_nttype(ace_type, ace); + if (error) { + return error; + } + + error = convert_zfswho_to_ntsid(iflag, + who_id, + inode, + flags, + ace); + if (error) { + return error; + } + + ace_sz = CIFS_ACE_SIZE(ace->sid.num_subauth); + ace->size = cpu_to_le16(ace_sz); + out_sz += ace_sz; + } else { + convert_zfsperm_to_ntperm(perms, ace); + convert_zfsflag_to_ntflag(flags, ace); + error = convert_zfstype_to_nttype(ace_type, ace); + if (error) { + return error; + } + error = convert_zfswho_to_ntsid(iflag, + who_id, + inode, + flags, + ace); + if (error) { + return error; + } + + ace_sz = CIFS_ACE_SIZE(ace->sid.num_subauth); + ace->size = cpu_to_le16(ace_sz); + out_sz += ace_sz; + } + + if (error) { + return error; + } + + *size = out_sz; + + return 0; +} + +static int +convert_zfsacl_to_cifsacl(u32 *aclbuf, + u32 acecnt, + struct inode *inode, + struct cifs_acl *pdacl, + u32 dacl_ace_cnt, + u16 *pacl_size_out) +{ + u32 i, nsize = sizeof(struct cifs_acl); + char *acl_base = (char *)pdacl; + u16 size; + int error; + + for (i = 0; i < acecnt; i++) { + u32 *zfsace = aclbuf + (i * NACE41_LEN); + error = convert_zfsace_to_cifs_aces(zfsace, acl_base + nsize, inode, &size); + if (error) + return error; + + nsize += size; + } + + *pacl_size_out = nsize; + pdacl->size = cpu_to_le16(nsize); + pdacl->revision = cpu_to_le16(ACL_REVISION); + pdacl->num_aces = cpu_to_le32(dacl_ace_cnt); + + return 0; +} + +static void +force_smb3_dacl_info(struct smb3_sd *sd, u32 acl_flag) +{ + u16 control = ACL_CONTROL_SR | ACL_CONTROL_DP; + + if (acl_flag & ACL4_PROTECTED) { + control |= ACL_CONTROL_PD; + } + + /* + * kzalloc call zero-initialized + * sd->Sbz1, which is correct since we are not + * using resource manager + */ + sd->Revision = 1; + sd->Control = cpu_to_le16(control); +} + +/* + * This is special handling for either NULL or empty ACLs. + * Returns 0 if ACL is generated, -EAGAIN if regular parsing + * required, and otherwise -errno. + */ +static int +parse_single_ace(u32 *zfsace, struct cifs_ntsd **ppntsd_out, u32 *acllen_out) +{ + u32 perms, flags, iflag, who_id, ace_type; + bool dacl_is_null = false, dacl_is_empty = false; + u32 secdesclen = sizeof(struct cifs_ntsd); + struct cifs_ntsd *pnntsd = NULL; + struct cifs_acl *dacl = NULL; + + perms = ntohl(*(zfsace + NA_ACCESS_MASK_OFFSET)); + flags = ntohl(*(zfsace + NA_FLAG_OFFSET)); + iflag = ntohl(*(zfsace + NA_IFLAG_OFFSET)); + who_id = ntohl(*(zfsace + NA_WHO_OFFSET)); + ace_type = ntohl(*(zfsace + NA_TYPE_OFFSET)); + + if ((iflag == ACEI4_SPECIAL_WHO) && (who_id == ACE4_SPECIAL_EVERYONE) && + (perms == ACE4_ALL_PERMS) && (flags == 0) && + (ace_type == ACE4_ACCESS_ALLOWED_ACE_TYPE)) { + dacl_is_null = true; + } + + if ((iflag == ACEI4_SPECIAL_WHO) && (who_id == ACE4_SPECIAL_OWNER) && + (perms == 0) && (flags == 0) && + (ace_type == ACE4_ACCESS_ALLOWED_ACE_TYPE)) { + dacl_is_empty = true; + secdesclen += sizeof(struct cifs_acl); + } + + if (!dacl_is_null && !dacl_is_empty) { + return -EAGAIN; + } + + pnntsd = kzalloc(secdesclen, GFP_KERNEL); + if (pnntsd == NULL) { + return -ENOMEM; + } + + force_smb3_dacl_info((struct smb3_sd *)pnntsd, 0); + + *ppntsd_out = pnntsd; + *acllen_out = secdesclen; + + if (dacl_is_null) { + return 0; + } + + /* dacl_is_empty */ + pnntsd->dacloffset = cpu_to_le32(sizeof(struct cifs_ntsd)); + dacl = (struct cifs_acl *)(pnntsd + sizeof(struct cifs_ntsd)); + dacl->size = cpu_to_le16(sizeof(struct cifs_acl)); + dacl->revision = cpu_to_le16(ACL_REVISION); + return 0; +} + +/* + * This method converts ZFS ACL format into a Security Descriptor. The + * resulting SD only contains a DACL and is limited to only ALLOW and DENY + * entries. + */ +int zfsacl_xattr_to_ntsd(char *aclbuf, + size_t size, + struct inode *inode, + struct cifs_ntsd **ppntsd_out, + u32 *acllen_out) +{ + int error; + u32 *zfsacl = (u32 *)aclbuf; + u32 control, acecnt, dacl_ace_cnt, secdesclen; + struct cifs_ntsd *pnntsd = NULL; + struct cifs_acl *dacl = NULL; + u16 acl_size_out = 0; + + if (!XDRSIZE_IS_VALID(size)) { + return -EINVAL; + } + + control = ntohl(*(zfsacl++)); + acecnt = ntohl(*(zfsacl++)); + + /* + * C.f. notes about S-1-3-0 and S-1-3-1 above. There are some + * circumstances when one ZFS ACL entry may need to expand to two + * SMB DACL entries. + */ + error = calculate_ntsd_acecnt(zfsacl, acecnt, inode, &dacl_ace_cnt); + if (error) { + return error; + } + + /* + * Special handling for NULL or empty DACL. A single ACL entry + * is unusual and so we first check to see whether it's a NULL or empty + * DACL, if it isn't then the function returns -EAGAIN so that we + * fall back to normal parsing. + */ + if (dacl_ace_cnt == 1) { + error = parse_single_ace(zfsacl, ppntsd_out, acllen_out); + if ((error == 0) || (error != -EAGAIN)) { + return error; + } + } + + secdesclen = dacl_ace_cnt * sizeof(struct cifs_ace); + secdesclen = max_t(u32, secdesclen, DEFAULT_SEC_DESC_LEN); + secdesclen += sizeof(struct cifs_ntsd); + + pnntsd = kzalloc(secdesclen, GFP_KERNEL); + if (pnntsd == NULL) { + return -ENOMEM; + } + + /* + * Format of Security Descriptor has changed over time. We require + * support for setting ACL-wide control bits and so this method + * is gated on whether connection is SMB3+. Hence, we are safe in + * assuming we can recast as an smb3_sd for setting our control bits. + */ + force_smb3_dacl_info((struct smb3_sd *)pnntsd, control); + + pnntsd->dacloffset = cpu_to_le32(sizeof(struct cifs_ntsd)); + + dacl = (struct cifs_acl *)((char*)pnntsd + pnntsd->dacloffset); + error = convert_zfsacl_to_cifsacl(zfsacl, acecnt, inode, dacl, + dacl_ace_cnt, &acl_size_out); + + dacl->size = cpu_to_le16(acl_size_out); + if (error) { + kfree(pnntsd); + return error; + } + + *ppntsd_out = pnntsd; + *acllen_out = secdesclen; + + return 0; +} +#endif /* CONFIG_TRUENAS */ diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 5041b1ffc244..6d58f53be9c5 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -2060,6 +2060,10 @@ extern unsigned int dir_cache_timeout; /* max time for directory lease caching o extern bool disable_legacy_dialects; /* forbid vers=1.0 and vers=2.0 mounts */ extern atomic_t mid_count; +#ifdef CONFIG_TRUENAS +extern unsigned int global_zfsaclflags; +#endif + void cifs_oplock_break(struct work_struct *work); void cifs_queue_oplock_break(struct cifsFileInfo *cfile); void smb2_deferred_work_close(struct work_struct *work); diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 1d3470bca45e..d5a26374295f 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -675,6 +675,18 @@ int cifs_sfu_make_node(unsigned int xid, struct inode *inode, struct dentry *dentry, struct cifs_tcon *tcon, const char *full_path, umode_t mode, dev_t dev); +#ifdef CONFIG_TRUENAS +int ntsd_to_zfsacl_xattr(struct cifs_ntsd *pacl, + u32 acllen, + struct inode *inode, + char **buf_out); +int zfsacl_xattr_to_ntsd(char *aclbuf, + size_t size, + struct inode *inode, + struct cifs_ntsd **ppntsd_out, + u32 *acllen_out); +#endif + #ifdef CONFIG_CIFS_DFS_UPCALL static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, const char *old_path, diff --git a/fs/smb/client/nfs41acl_xdr.h b/fs/smb/client/nfs41acl_xdr.h new file mode 100644 index 000000000000..b9f0093efacf --- /dev/null +++ b/fs/smb/client/nfs41acl_xdr.h @@ -0,0 +1,151 @@ +#ifndef _NFS41ACL_H +#define _NFS41ACL_H + +/* + * Native ZFS NFSv41-style ACL is packed (using network byte order) in xattr as + * follows: + * + * struct nfsace4i { + * uint32_t type; RFC 5661 Section 6.2.1.1 + * uint32_t flag; RFC 5661 Section 6.2.1.4 + * uint32_t iflag; + * uint32_t access_mask; RFC 5661 Section 6.2.1.3 + * uint32_t who_id; + * }; + * + * struct nfsacl4 { + * uint32_t acl_flags; RFC 5661 Section 6.4.3.2 + * uint32_t ace_count; + * struct nfsace4i aces<>; + * }; + * + * iflag and who_id combined are sufficent for NFS server to convert into ACE + * who (RFC 5661 Section 6.2.1.5). + */ + +#define NA41_NAME "system.nfs4_acl_xdr" +#define NA_TYPE_OFFSET 0 +#define NA_FLAG_OFFSET 1 +#define NA_IFLAG_OFFSET 2 +#define NA_ACCESS_MASK_OFFSET 3 +#define NA_WHO_OFFSET 4 + +/* + * Following are defined in RFC 5661 Section 6.2.1.3 ACE Access Mask + */ +#define ACE4_READ_DATA 0x00000001 +#define ACE4_WRITE_DATA 0x00000002 +#define ACE4_APPEND_DATA 0x00000004 +#define ACE4_READ_NAMED_ATTRS 0x00000008 +#define ACE4_WRITE_NAMED_ATTRS 0x00000010 +#define ACE4_EXECUTE 0x00000020 +#define ACE4_DELETE_CHILD 0x00000040 +#define ACE4_READ_ATTRIBUTES 0x00000080 +#define ACE4_WRITE_ATTRIBUTES 0x00000100 +#define ACE4_DELETE 0x00010000 +#define ACE4_READ_ACL 0x00020000 +#define ACE4_WRITE_ACL 0x00040000 +#define ACE4_WRITE_OWNER 0x00080000 +#define ACE4_SYNCHRONIZE 0x00100000 + +#define ACE4_READ_PERMS (ACE4_READ_DATA|ACE4_READ_ACL|ACE4_READ_ATTRIBUTES| \ + ACE4_READ_NAMED_ATTRS) + +#define ACE4_WRITE_PERMS (ACE4_WRITE_DATA|ACE4_APPEND_DATA|ACE4_WRITE_ATTRIBUTES| \ + ACE4_WRITE_NAMED_ATTRS) + +#define ACE4_MODIFY_PERMS (ACE4_READ_PERMS|ACE4_WRITE_PERMS|ACE4_SYNCHRONIZE| \ + ACE4_EXECUTE|ACE4_DELETE_CHILD|ACE4_DELETE) + +#define ACE4_ALL_PERMS (ACE4_MODIFY_PERMS|ACE4_WRITE_ACL|ACE4_WRITE_OWNER) + +/* + * Following are defined in RFC 5661 Section 6.2.1.4 ACE flags + */ +#define ACE4_FILE_INHERIT_ACE 0x00000001 +#define ACE4_DIRECTORY_INHERIT_ACE 0x00000002 +#define ACE4_NO_PROPAGATE_INHERIT_ACE 0x00000004 +#define ACE4_INHERIT_ONLY_ACE 0x00000008 +#define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010 +#define ACE4_FAILED_ACCESS_ACE_FLAG 0x00000020 +#define ACE4_IDENTIFIER_GROUP 0x00000040 +#define ACE4_INHERITED_ACE 0x00000080 +#define NFS41_FLAGS (ACE4_DIRECTORY_INHERIT_ACE| \ + ACE4_FILE_INHERIT_ACE| \ + ACE4_NO_PROPAGATE_INHERIT_ACE| \ + ACE4_INHERIT_ONLY_ACE| \ + ACE4_INHERITED_ACE| \ + ACE4_IDENTIFIER_GROUP) +#define DIR_ONLY_FLAGS (ACE4_DIRECTORY_INHERIT_ACE| \ + ACE4_FILE_INHERIT_ACE| \ + ACE4_NO_PROPAGATE_INHERIT_ACE| \ + ACE4_INHERIT_ONLY_ACE) + +#define ACEI4_SPECIAL_WHO 0x00000001 +#define ACE4_SPECIAL_OWNER 1 +#define ACE4_SPECIAL_GROUP 2 +#define ACE4_SPECIAL_EVERYONE 3 +#define NACE41_LEN 5 +#define NACL_OFFSET 2 + +/* + * Follow ACL flags are defined in RFC 5661 Section 6.4.3.2 and are mapped to + * NT Security Descriptor control bits (MS-DTYP Section 2.4.6) on an as-needed + * basis. From a practical standpoint the primary concern is preserving the + * DACL Protected bit as this alters Windows SMB client auto-inheritance + * behavior when propagating ACL changes recursively. + */ +#define ACL4_AUTO_INHERIT 0x00000001 +#define ACL4_PROTECTED 0x00000002 +#define ACL4_DEFAULTED 0x00000004 + +/* Non-RFC ZFS flag indicating that ACL is a directory */ +#define ACL4_ISDIR 0x00020000 + +/* + * Following are defined in RFC 5661 Section 6.2.1.1 + */ +#define ACE4_ACCESS_ALLOWED_ACE_TYPE 0x0000 +#define ACE4_ACCESS_DENIED_ACE_TYPE 0x0001 +#define ACE4_SYSTEM_AUDIT_ACE_TYPE 0x0002 +#define ACE4_SYSTEM_ALARM_ACE_TYPE 0x0003 + +/* + * Macros for sanity checks related to XDR and ACL buffer sizes + */ +#define NFS41ACL_MAX_ENTRIES 128 +#define ACE4SIZE (NACE41_LEN * sizeof(u32)) +#define XDRBASE (2 * sizeof (u32)) + +#define ACES_TO_SIZE(x, y) (x + (y * ACE4SIZE)) +#define SIZE_IS_VALID(x, y) ((x >= ACES_TO_SIZE(y, 0)) && \ + (((x - y) % ACE4SIZE) == 0)) + +#define ACES_TO_XDRSIZE(x) (ACES_TO_SIZE(XDRBASE, x)) +#define XDRSIZE_IS_VALID(x) (SIZE_IS_VALID(x, XDRBASE)) + +/* + * Supported flags for /proc/fs/cifs/zfsacl_configuration_flags + */ +#define MODFLAG_UNDEFINED 0x00000000 + +/* if SID is unknown map it to current fsuid */ +#define MODFLAG_MAP_UNKNOWN_SID 0x00000001 + +/* if SID is unknown, skip it */ +#define MODFLAG_SKIP_UNKNOWN_SID 0x00000002 + +/* if SID is unknown, fail the operation */ +#define MODFLAG_FAIL_UNKNOWN_SID 0x00000004 + +/* Allow writing ACL through xattr (off by default) */ +#define MODFLAG_ALLOW_ACL_WRITE 0x00000008 + +#define MODFLAG_ALL_IDMAP (MODFLAG_FAIL_UNKNOWN_SID | MODFLAG_MAP_UNKNOWN_SID |\ + MODFLAG_SKIP_UNKNOWN_SID) + +#define MODFLAG_ALL (MODFLAG_ALL_IDMAP | MODFLAG_ALLOW_ACL_WRITE) + +#define MODFLAG_DEFAULTS (MODFLAG_FAIL_UNKNOWN_SID) + +#endif /* !_NFS41ACL_H */ diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c index 58a584f0b27e..39292e4a60a9 100644 --- a/fs/smb/client/xattr.c +++ b/fs/smb/client/xattr.c @@ -18,6 +18,9 @@ #include "cifs_fs_sb.h" #include "cifs_unicode.h" #include "cifs_ioctl.h" +#ifdef CONFIG_TRUENAS +#include "nfs41acl_xdr.h" +#endif #define MAX_EA_VALUE_SIZE CIFSMaxBufSize #define CIFS_XATTR_CIFS_ACL "system.cifs_acl" /* DACL only */ @@ -37,8 +40,14 @@ #define SMB3_XATTR_CREATETIME "smb3.creationtime" /* user.smb3.creationtime */ /* BB need to add server (Samba e.g) support for security and trusted prefix */ +#ifdef CONFIG_TRUENAS +#define CIFS_XATTR_ZFS_ACL NA41_NAME /* DACL only */ +enum { XATTR_USER, XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT, + XATTR_CIFS_NTSD, XATTR_CIFS_NTSD_FULL, XATTR_ZFSACL }; +#else enum { XATTR_USER, XATTR_CIFS_ACL, XATTR_ACL_ACCESS, XATTR_ACL_DEFAULT, XATTR_CIFS_NTSD, XATTR_CIFS_NTSD_FULL }; +#endif /* CONFIG_TRUENAS */ static int cifs_attrib_set(unsigned int xid, struct cifs_tcon *pTcon, struct inode *inode, const char *full_path, @@ -203,6 +212,44 @@ static int cifs_xattr_set(const struct xattr_handler *handler, } break; } + +#ifdef CONFIG_TRUENAS + case XATTR_ZFSACL: { + u32 final; + struct cifs_ntsd *pacl = NULL; + + if (pTcon->ses->server->ops->set_acl == NULL) + goto out; /* rc already EOPNOTSUPP */ + + if ((global_zfsaclflags & MODFLAG_ALLOW_ACL_WRITE) == 0) { + cifs_dbg(VFS, "ZFSACL write support is disabled\n"); + rc = -EPERM; + goto out; + } + + /* + * Force SMB3 so that we have support for DACL control bit + */ + if (pTcon->ses->server->dialect < SMB30_PROT_ID) { + cifs_dbg(VFS, "ZFS ACL is not supported on this protocol version, " + "use 3.0 or above\n"); + goto out; /* rc already EOPNOTSUPP */ + } + + rc = zfsacl_xattr_to_ntsd((char *)value, size, inode, &pacl, &final); + if (rc == 0) { + rc = pTcon->ses->server->ops->set_acl(pacl, + final, inode, full_path, CIFS_ACL_DACL); + + if (rc == 0) /* force revalidate of the inode */ + CIFS_I(inode)->time = 0; + + kfree(pacl); + } + + break; + } +#endif /* CONFIG_TRUENAS */ } out: @@ -343,6 +390,50 @@ static int cifs_xattr_get(const struct xattr_handler *handler, } break; } +#ifdef CONFIG_TRUENAS + case XATTR_ZFSACL: { + struct cifs_ntsd *pacl; + char *zfsacl; + u32 acllen; + + if (pTcon->ses->server->ops->get_acl == NULL) { + goto out; /* rc already EOPNOTSUPP */ + } + + /* + * Force SMB3 so that we have support for DACL control bit + */ + if (pTcon->ses->server->dialect < SMB30_PROT_ID) { + cifs_dbg(VFS, "ZFS ACL is not supported on this " + "protocol version, use 3.0 or above\n"); + goto out; /* rc already EOPNOTSUPP */ + } + + pacl = pTcon->ses->server->ops->get_acl(cifs_sb, + inode, full_path, &acllen, 0); + if (IS_ERR(pacl)) { + rc = PTR_ERR(pacl); + cifs_dbg(VFS, "%s: error %zd getting sec desc\n", + __func__, rc); + } else { + rc = ntsd_to_zfsacl_xattr(pacl, acllen, inode, &zfsacl); + kfree(pacl); + } + + if (rc > 0) { + // zero size means return size of buffer needed to get xattr + if (size != 0) { + if (rc > size) + rc = -ERANGE; + else + memcpy(value, zfsacl, rc); + } + + kfree(zfsacl); + } + break; + } +#endif /* CONFIG_TRUENAS */ } /* We could add an additional check for streams ie @@ -396,9 +487,33 @@ ssize_t cifs_listxattr(struct dentry *direntry, char *data, size_t buf_size) search server for EAs or streams to returns as xattrs */ - if (pTcon->ses->server->ops->query_all_EAs) + 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); + if (rc < 0) goto list_ea_exit; + } + +#ifdef CONFIG_TRUENAS + if ((pTcon->ses->server->ops->get_acl) && + (pTcon->ses->server->dialect >= SMB30_PROT_ID)) { + /* + * If OS/2 style EA support is disabled we still + * want to return our special ACL xattr in the list. + */ + rc = rc > 0 ? rc : 0; + if (buf_size) { + if ((rc + sizeof(CIFS_XATTR_ZFS_ACL)) > buf_size) { + rc = -ERANGE; + goto list_ea_exit; + } + memcpy(data + rc, CIFS_XATTR_ZFS_ACL, + sizeof(CIFS_XATTR_ZFS_ACL)); + } + + rc += sizeof(CIFS_XATTR_ZFS_ACL); + } +#endif /* CONFIG_TRUENAS */ + list_ea_exit: free_dentry_path(page); free_xid(xid); @@ -448,6 +563,15 @@ static const struct xattr_handler cifs_cifs_ntsd_xattr_handler = { .set = cifs_xattr_set, }; +#ifdef CONFIG_TRUENAS +static const struct xattr_handler zfsacl_xattr_handler = { + .name = CIFS_XATTR_ZFS_ACL, + .flags = XATTR_ZFSACL, + .get = cifs_xattr_get, + .set = cifs_xattr_set, +}; +#endif + /* * Although this is just an alias for the above, need to move away from * confusing users and using the 20 year old term 'cifs' when it is no @@ -490,5 +614,8 @@ const struct xattr_handler * const cifs_xattr_handlers[] = { &smb3_ntsd_xattr_handler, /* alias for above since avoiding "cifs" */ &cifs_cifs_ntsd_full_xattr_handler, &smb3_ntsd_full_xattr_handler, /* alias for above since avoiding "cifs" */ +#ifdef CONFIG_TRUENAS + &zfsacl_xattr_handler, +#endif NULL };