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..ea4c7c2acaee 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 smb_sid sid_creator_owner = { + 1, 1, {0, 0, 0, 0, 0, 3}, {0} }; + +static const struct smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_ntsd *pntsd, + u32 acl_len, + struct inode *inode, + char **buf_out) +{ + struct smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_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 smb_ace *ace = (struct smb_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 smb_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 smb_acl *pdacl, + u32 dacl_ace_cnt, + u16 *pacl_size_out) +{ + u32 i, nsize = sizeof(struct smb_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 smb_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 smb_ntsd); + struct smb_ntsd *pnntsd = NULL; + struct smb_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 smb_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 smb_ntsd)); + dacl = (struct smb_acl *)(pnntsd + sizeof(struct smb_ntsd)); + dacl->size = cpu_to_le16(sizeof(struct smb_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 smb_ntsd **ppntsd_out, + u32 *acllen_out) +{ + int error; + u32 *zfsacl = (u32 *)aclbuf; + u32 control, acecnt, dacl_ace_cnt, secdesclen; + struct smb_ntsd *pnntsd = NULL; + struct smb_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 smb_ace); + secdesclen = max_t(u32, secdesclen, DEFAULT_SEC_DESC_LEN); + secdesclen += sizeof(struct smb_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 smb_ntsd)); + + dacl = (struct smb_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..263c4fb31115 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 smb_ntsd *pacl, + u32 acllen, + struct inode *inode, + char **buf_out); +int zfsacl_xattr_to_ntsd(char *aclbuf, + size_t size, + struct inode *inode, + struct smb_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..9b8f4db40e0a 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 smb_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 smb_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 };