From 88f58160226d0d46a4e4c95401b976c29690d8a9 Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Wed, 2 Oct 2024 14:32:57 -0600 Subject: [PATCH] NAS-131445 / Fix handling for NULL and Empty DACLs in vfs_ixnas (#415) In some cases SMB clients may explicitly set a security descriptor with a NULL DACL or one that contains no entries. Per Microsoft documentation a NULL DACL grants full access to any user that requests it; normal secuurty checking is not performed with respect to the file. An empty DACL is a properly allocated and initialized DACL that contains no access control entries. An empty DACL grants no access to the object it is assigned to. Both of these DACL variants are edge-case scenarios that can't be handled by Samba's generic NFSv4 ACL backend since the way in which these situations are handled is filesystem-specific. ZFS does not permit setting either empty or null ACLs and so this commit adds special behavior ACL entries to simulate both situations. NULL DACL: a NULL DACL is written as an ACL contianing a single entry granting everyone@ full control. Inheritance is disabled. Empty DACL: an empty DACL is written as an ACL containing a single entry granting everyone@ _no_ permissions. Ineritance is disabled. The single entry can be used for these purposes because historically vfs_ixnas has always inserted an additional hidden ACE to lock down chmod behavior, which means that all normally formed ACLs contain at least two access control entries. --- source3/include/vfs.h | 8 +- source3/modules/vfs_ixnas.c | 646 +++++++++++++++++------------------- source3/smbd/open.c | 11 + 3 files changed, 321 insertions(+), 344 deletions(-) diff --git a/source3/include/vfs.h b/source3/include/vfs.h index 210a726bcf7..94875728367 100644 --- a/source3/include/vfs.h +++ b/source3/include/vfs.h @@ -410,10 +410,10 @@ typedef union unid_t { } unid_t; enum acl_brand { - SMB_ACL_BRAND_POSIX, - SMB_ACL_BRAND_NFS40, - SMB_ACL_BRAND_NFS41, - SMB_ACL_BRAND_NONE, + TRUENAS_ACL_BRAND_UNKNOWN, + TRUENAS_ACL_BRAND_POSIX, + TRUENAS_ACL_BRAND_NFS4, + TRUENAS_ACL_BRAND_NONE, }; struct fd_handle; diff --git a/source3/modules/vfs_ixnas.c b/source3/modules/vfs_ixnas.c index d1b1efb9a21..100bd27d9ba 100644 --- a/source3/modules/vfs_ixnas.c +++ b/source3/modules/vfs_ixnas.c @@ -1,8 +1,5 @@ /* * Unix SMB/CIFS implementation. - * A dumping ground for FreeBSD-specific VFS functions. For testing case - * of reducing number enabled VFS modules to bare minimum by creating - * single large VFS module. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,9 +15,12 @@ * along with this program; if not, see . */ #include "includes.h" +#include "libcli/security/dom_sid.h" #include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" #include "smbd/smbd.h" #include "system/filesys.h" +#include "passdb/lookup_sid.h" #include "nfs4_acls.h" #include "zfsacl.h" @@ -37,7 +37,12 @@ struct ixnas_config_data { bool zfs_acl_chmod_enabled; }; -#ifndef FREEBSD +enum ixnas_dacl_type { + IXNAS_NULL_DACL, + IXNAS_EMPTY_DACL, + IXNAS_NORMAL_DACL +}; + #define UF_READONLY 0x0000000100000000ull #define UF_HIDDEN 0x0000000200000000ull #define UF_SYSTEM 0x0000000400000000ull @@ -46,20 +51,11 @@ struct ixnas_config_data { #define UF_OFFLINE 0x0000100000000000ull #define UF_SPARSE 0x0000200000000000ull -#define ACL_BRAND_UNKNOWN 0 -#define ACL_BRAND_POSIX 1 -#define ACL_BRAND_NFS4 2 -#define ACL_BRAND_NONE 3 - - #define ACL4_XATTR "system.nfs4_acl_xdr" #define ACL_XATTR "system.posix_acl_access" #define ZFS_IOC_GETDOSFLAGS _IOR(0x83, 1, uint64_t) #define ZFS_IOC_SETDOSFLAGS _IOW(0x83, 2, uint64_t) -#else -#define ACL_BRAND_NONE 3 -#endif /* FREEBSD */ static const struct { uint32_t dosmode; @@ -98,7 +94,10 @@ static void _dump_acl_info(zfsacl_t theacl, const char *fn) } #define dump_acl_info(x) _dump_acl_info(x, __func__) -#ifndef FREEBSD +/* + * This function converts a file opened via O_PATH to one opened under + * different flags by using procfs. + */ static int ixnas_pathref_reopen(const files_struct *fsp, int flags) { int fd_out = -1; @@ -117,13 +116,9 @@ static int ixnas_pathref_reopen(const files_struct *fsp, int flags) } return fd_out; } -#endif static bool ixnas_get_native_dosmode(struct files_struct *fsp, uint64_t *_dosmode) { -#if defined (FREEBSD) - *_dosmode = fsp->fsp_name->st.st_ex_flags & KERN_DOSMODES; -#else int err; if (!fsp->fsp_flags.is_pathref) { err = ioctl(fsp_get_io_fd(fsp), ZFS_IOC_GETDOSFLAGS, _dosmode); @@ -151,16 +146,12 @@ static bool ixnas_get_native_dosmode(struct files_struct *fsp, uint64_t *_dosmod fsp_str_dbg(fsp), strerror(errno)); return false; } -#endif /* FREEBSD */ return true; } static bool ixnas_set_native_dosmode(struct files_struct *fsp, uint64_t dosmode) { int err; -#if defined (FREEBSD) - err = SMB_VFS_FCHFLAGS(fsp, dosmode); -#else if (!fsp->fsp_flags.is_pathref) { err = ioctl(fsp_get_io_fd(fsp), ZFS_IOC_SETDOSFLAGS, &dosmode); } else { @@ -175,7 +166,6 @@ static bool ixnas_set_native_dosmode(struct files_struct *fsp, uint64_t dosmode) close(fd); } -#endif /* FREEBSD */ if (err) { if ((errno != EACCES) && (errno != EPERM)) { DBG_WARNING("Setting dosmode failed for %s: %s\n", @@ -196,23 +186,17 @@ static NTSTATUS ixnas_fget_dos_attributes(struct vfs_handle_struct *handle, struct ixnas_config_data *config = NULL; int i; bool ok; - uint64_t kern_dosmodes = 0; + uint64_t kern_dosmode = 0; + uint32_t xattr_dosmode = 0; + NTSTATUS status; SMB_VFS_HANDLE_GET_DATA(handle, config, struct ixnas_config_data, return NT_STATUS_INTERNAL_ERROR); -#if defined (FREEBSD) - if (config->dosattrib_xattr) { - return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, - fsp, - dosmode); - } -#else - NTSTATUS status; status = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, - dosmode); + &xattr_dosmode); if (config->dosattrib_xattr) { return status; @@ -227,33 +211,35 @@ static NTSTATUS ixnas_fget_dos_attributes(struct vfs_handle_struct *handle, } } -#endif /* FREEBSD */ - if (is_named_stream(fsp->fsp_name)) { // Streams don't have separate dos attribute metadata - ok = ixnas_get_native_dosmode(fsp->base_fsp, &kern_dosmodes); + ok = ixnas_get_native_dosmode(fsp->base_fsp, &kern_dosmode); } else { - ok = ixnas_get_native_dosmode(fsp, &kern_dosmodes); + ok = ixnas_get_native_dosmode(fsp, &kern_dosmode); } if (!ok) { return map_nt_error_from_unix(errno); } + *dosmode = xattr_dosmode; + for (i = 0; i < ARRAY_SIZE(dosmode2flag); i++) { - if (kern_dosmodes & dosmode2flag[i].flag) { + if (kern_dosmode & dosmode2flag[i].flag) { *dosmode |= dosmode2flag[i].dosmode; } } - if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) { /* * Windows default behavior appears to be that the archive bit - * on a directory is only explicitly set by clients. FreeBSD + * on a directory is only explicitly set by clients. ZFS * sets this bit when the directory's contents are modified. - * This is a temporary hack until we can make OS behavior - * configurable + * + * This means that we _must_ rely on the xattr-encoded dosmode + * to provide guidance as to whether it is set for the file. */ + if (S_ISDIR(fsp->fsp_name->st.st_ex_mode) && + ((xattr_dosmode & FILE_ATTRIBUTE_ARCHIVE) == 0)) { *dosmode &= ~FILE_ATTRIBUTE_ARCHIVE; } @@ -336,18 +322,9 @@ static NTSTATUS ixnas_fset_dos_attributes(struct vfs_handle_struct *handle, } out: -#if defined (FREEBSD) - return NT_STATUS_OK; -#else - /* - * On Linux need to pass through - * so that we can set synthetic timestamps - * and file id. - */ return SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode); -#endif /* FREEBSD */ } static zfsacl_t fsp_get_zfsacl(files_struct *fsp) @@ -393,33 +370,26 @@ static bool fsp_set_zfsacl(files_struct *fsp, zfsacl_t zfsacl) return zfsacl_set_file(proc_fd_path, zfsacl); } +/* + * fsp_get_aclbrand() and path_get_aclbrand() both get the ACL brand on the + * underlying filesystem. In almost all cases we rely on the ACL brand we + * detected on the initial SMB tree connect, which is preferable to detecting + * on a per-file basis. Unfortunately, certain types of users disregard UI + * warnings and alerts and nest datasets with different ACL properties (or + * mount random filesystems via the unix shell) and so we need to handle the + * unexpected if initial attempt to read the file's ACL fails. + */ static int fsp_get_acl_brand(files_struct *fsp) { -#if defined (FREEBSD) - int saved_errno; - saved_errno = errno; - long ret; - - ret = fpathconf(fsp_get_pathref_fd(fsp), _PC_ACL_NFS4); - if (ret == -1) { - if (saved_errno == errno) { - return ACL_BRAND_POSIX; - } - DBG_ERR("%s: fpathconf failed: %s\n", - fsp_str_dbg(fsp), strerror(errno)); - errno = saved_errno; - return ACL_BRAND_UNKNOWN; - } -#else ssize_t rv; rv = SMB_VFS_FGETXATTR(fsp, ACL_XATTR, NULL, 0); if (rv == -1) { if (errno == ENODATA) { - return ACL_BRAND_POSIX; + return TRUENAS_ACL_BRAND_POSIX; } else if (errno != EOPNOTSUPP) { DBG_ERR("%s: fgetxattr() for %s failed: %s\n", fsp_str_dbg(fsp), ACL_XATTR, strerror(errno)); - return ACL_BRAND_UNKNOWN; + return TRUENAS_ACL_BRAND_UNKNOWN; } } @@ -427,47 +397,30 @@ static int fsp_get_acl_brand(files_struct *fsp) if (rv == -1) { if (errno == ENODATA) { /* probably need to add disabled */ - return ACL_BRAND_UNKNOWN; + return TRUENAS_ACL_BRAND_UNKNOWN; } else if (errno == EOPNOTSUPP) { /* Neither NFSv4 nor POSIX acls are supported */ - return ACL_BRAND_NONE; + return TRUENAS_ACL_BRAND_NONE; } DBG_ERR("%s: fgetxattr() for %s failed: %s\n", fsp_str_dbg(fsp), ACL4_XATTR, strerror(errno)); - return ACL_BRAND_UNKNOWN; + return TRUENAS_ACL_BRAND_UNKNOWN; } -#endif /* FREEBSD */ - return ACL_BRAND_NFS4; + return TRUENAS_ACL_BRAND_NFS4; } static int path_get_aclbrand(const char *path) { -#if defined (FREEBSD) - int saved_errno; - saved_errno = errno; - long ret; - - ret = pathconf(path, _PC_ACL_NFS4); - if (ret == -1) { - if (saved_errno == errno) { - return ACL_BRAND_POSIX; - } - DBG_ERR("%s: pathconf failed: %s\n", - path, strerror(errno)); - errno = saved_errno; - return ACL_BRAND_UNKNOWN; - } -#else /* LINUX */ ssize_t rv; rv = getxattr(path, ACL_XATTR, NULL, 0); if (rv == -1) { if (errno == ENODATA) { - return ACL_BRAND_POSIX; + return TRUENAS_ACL_BRAND_POSIX; } else if (errno != EOPNOTSUPP) { DBG_ERR("%s: getxattr() for %s failed: %s\n", path, ACL_XATTR, strerror(errno)); - return ACL_BRAND_UNKNOWN; + return TRUENAS_ACL_BRAND_UNKNOWN; } } @@ -475,17 +428,17 @@ static int path_get_aclbrand(const char *path) if (rv == -1) { if (errno == ENODATA) { /* probably need to add disabled */ - return ACL_BRAND_UNKNOWN; + return TRUENAS_ACL_BRAND_UNKNOWN; } DBG_ERR("%s: getxattr() for %s failed: %s\n", path, ACL4_XATTR, strerror(errno)); - return ACL_BRAND_UNKNOWN; + return TRUENAS_ACL_BRAND_UNKNOWN; } -#endif /* FREEBSD */ - return ACL_BRAND_NFS4; + return TRUENAS_ACL_BRAND_NFS4; } +/* Convert the native ZFS ACE format to the generic Samba NFSv4 format */ static bool zfsentry2smbace(zfsacl_entry_t ae, SMB_ACE4PROP_T *aceprop) { int i; @@ -564,6 +517,7 @@ static bool zfsentry2smbace(zfsacl_entry_t ae, SMB_ACE4PROP_T *aceprop) return true; } +/* convert Samba's generic NFSv4 ACE format to the native ZFS format */ static bool smbace2zfsentry(zfsacl_t zfsacl, SMB_ACE4PROP_T *aceprop) { bool ok; @@ -638,6 +592,85 @@ static bool smbace2zfsentry(zfsacl_t zfsacl, SMB_ACE4PROP_T *aceprop) return true; } +/* + * Determine the DACL type (for generating an NT security descriptor) from + * the native ZFS ACL format. If the ZFS ACL encodes a NULL DACL then + * dtype_out will be set to IXNAS_NULL_DACL, if it encodes an empty dacl + * then dtype_out will be set to IXNAS_EMPTY_DACL, otherwise it will + * be set to IXNAS_NORMAL_DACL. + * + * @param[in] zfacl Native ZFS ACL + * @param[out] dtype_out DACL type (on success) + * @return boolean - true on success + * + * NOTE: this may fail if we get a malformed ACL from ZFS. + */ +static bool ixnas_zfsacl_get_dacl_type(zfsacl_t zfsacl, + enum ixnas_dacl_type *dtype_out) +{ + bool ok; + uint cnt; + enum ixnas_dacl_type dtype = IXNAS_NORMAL_DACL; + zfsacl_entry_t ae = NULL; + zfsace_permset_t perms = 0; + zfsace_flagset_t flags = 0; + zfsace_entry_type_t entry_type = 0; + zfsace_id_t who_id = ZFSACL_UNDEFINED_ID; + zfsace_who_t who_type = ZFSACL_UNDEFINED_TAG; + + ok = zfsacl_get_acecnt(zfsacl, &cnt); + if (!ok) { + DBG_ERR("zfsacl_get_acecnt() failed: %s\n", strerror(errno)); + return false; + } + + ok = zfsacl_get_aclentry(zfsacl, 0, &ae); + if (!ok) { + DBG_ERR("zfsacl_get_aclentry() failed: %s\n", strerror(errno)); + return false; + } + + ok = zfsace_get_permset(ae, &perms); + if (!ok) { + DBG_ERR("zfsace_get_permset() failed: %s\n", strerror(errno)); + return false; + } + + ok = zfsace_get_flagset(ae, &flags); + if (!ok) { + DBG_ERR("zfsace_get_flagset() failed: %s\n", strerror(errno)); + return false; + } + + ok = zfsace_get_who(ae, &who_type, &who_id); + if (!ok) { + DBG_ERR("zfsace_get_who() failed: %s\n", strerror(errno)); + return false; + } + + ok = zfsace_get_entry_type(ae, &entry_type); + if (!ok) { + DBG_ERR("zfsace_get_entry_type() failed: %s\n", strerror(errno)); + return false; + } + + // our special DACL types have precisely one entry; + if ((cnt == 1) && + (entry_type == ZFSACL_ENTRY_TYPE_ALLOW) && + (who_type == ZFSACL_EVERYONE) && + (who_id == ZFSACL_UNDEFINED_ID) && + (flags == 0)) { + if (perms == ZFSACE_FULL_SET) { + dtype = IXNAS_NULL_DACL; + } else if (perms == 0) { + dtype = IXNAS_EMPTY_DACL; + } + } + + *dtype_out = dtype; + return true; +} + static NTSTATUS ixnas_get_nt_acl_nfs4_common(struct connection_struct *conn, TALLOC_CTX *mem_ctx, files_struct *fsp, @@ -775,6 +808,59 @@ static zfsacl_t fsp_get_zfsacl_from_mode(struct files_struct *fsp) return zfsacl; } +static NTSTATUS ixnas_generate_special_dacl_sd(struct vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + enum ixnas_dacl_type dtype, + struct security_descriptor **ppdesc) +{ + NTSTATUS status; + struct dom_sid sid_owner, sid_group; + size_t sd_size = 0; + struct security_ace *nt_ace_list = NULL; + struct security_acl *psa = NULL; + uint16_t controlflags = SEC_DESC_SELF_RELATIVE | SEC_DESC_DACL_PROTECTED | SEC_DESC_DACL_PRESENT; + + SMB_ASSERT((dtype == IXNAS_EMPTY_DACL) || (dtype == IXNAS_NULL_DACL)); + + if (!VALID_STAT(fsp->fsp_name->st)) { + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + uid_to_sid(&sid_owner, fsp->fsp_name->st.st_ex_uid); + gid_to_sid(&sid_group, fsp->fsp_name->st.st_ex_gid); + + if (dtype == IXNAS_EMPTY_DACL) { + psa = make_sec_acl(mem_ctx, NT4_ACL_REVISION, 0, NULL); + if (psa == NULL) { + DBG_ERR("make_sec_acl failed\n"); + return NT_STATUS_NO_MEMORY; + } + } + + *ppdesc = make_sec_desc( + mem_ctx, SD_REVISION, controlflags, + (security_info & SECINFO_OWNER) ? &sid_owner : NULL, + (security_info & SECINFO_GROUP) ? &sid_group : NULL, + NULL, psa, &sd_size); + + TALLOC_FREE(psa); + + if (*ppdesc==NULL) { + DBG_ERR("make_sec_desc failed\n"); + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("smb_get_nt_acl_nfs4_common successfully exited with " + "sd_size %d\n", (int)ndr_size_security_descriptor(*ppdesc, 0)); + + return NT_STATUS_OK; +} + static NTSTATUS ixnas_fget_nt_acl(struct vfs_handle_struct *handle, struct files_struct *fsp, uint32_t security_info, @@ -786,6 +872,8 @@ static NTSTATUS ixnas_fget_nt_acl(struct vfs_handle_struct *handle, struct files_struct *to_check = NULL; NTSTATUS status; zfsacl_t zfsacl; + bool ok; + enum ixnas_dacl_type dtype = IXNAS_NORMAL_DACL; struct ixnas_config_data *config = NULL; SMB_VFS_HANDLE_GET_DATA(handle, config, @@ -801,14 +889,14 @@ static NTSTATUS ixnas_fget_nt_acl(struct vfs_handle_struct *handle, if (zfsacl == NULL) { if ((errno == EINVAL) || (errno == EOPNOTSUPP)) { switch (fsp_get_acl_brand(fsp)) { - case ACL_BRAND_POSIX: - case ACL_BRAND_UNKNOWN: + case TRUENAS_ACL_BRAND_POSIX: + case TRUENAS_ACL_BRAND_UNKNOWN: status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, mem_ctx, ppdesc); if (NT_STATUS_IS_OK(status)) { (*ppdesc)->type |= SEC_DESC_DACL_PROTECTED; } return status; - case ACL_BRAND_NONE: + case TRUENAS_ACL_BRAND_NONE: zfsacl = fsp_get_zfsacl_from_mode(fsp); if (zfsacl == NULL) { return map_nt_error_from_unix(errno); @@ -823,6 +911,25 @@ static NTSTATUS ixnas_fget_nt_acl(struct vfs_handle_struct *handle, } dump_acl_info(zfsacl); + ok = ixnas_zfsacl_get_dacl_type(zfsacl, &dtype); + if (!ok) { + return map_nt_error_from_unix(errno); + } + switch (dtype) { + case IXNAS_NULL_DACL: + case IXNAS_EMPTY_DACL: + return ixnas_generate_special_dacl_sd(handle, + fsp, + security_info, + mem_ctx, + dtype, + ppdesc); + case IXNAS_NORMAL_DACL: + break; + default: + smb_panic("Unexpected ixnas_dacl_type"); + }; + frame = talloc_stackframe(); status = ixnas_get_nt_acl_nfs4_common(handle->conn, frame, @@ -851,7 +958,6 @@ static bool ixnas_add_hidden_entry(zfsacl_t zfsacl, uint acecnt; zfsacl_entry_t hidden_entry = NULL; - if (!has_inheritable) { ok = zfsacl_get_acecnt(zfsacl, &acecnt); if (!ok) { @@ -965,6 +1071,95 @@ static bool ixnas_process_smbacl(vfs_handle_struct *handle, return true; } +static NTSTATUS ixnas_fset_special_dacl(vfs_handle_struct *handle, + files_struct *fsp, + enum ixnas_dacl_type dtype) +{ + /* + * A null DACL grants full access to any user that requests it; normal + * security checking is not performed with respect to the object. + * + * Example use case: + * Adobe applications may use these for locking files. + * + * For our purposes we indicate this special ACL type with the following + * characteristics: + * 1. A single ACL entry granting everyone@ full control + * 2. No additional hidden locking ACE + * 3. No additional ACL control flags + * + * An empty DACL grants no access to the object it is assigned to. + * 1. A single ACL entry granting everyone@ no access at all + * 2. No additional hidden locking ACE + * 3. No additional ACL control flags + * + * Example use case: + * Adobe applications may use an empty DACL on some files while in + * protected mode to prevent anyone other than the file owner or users + * with elevated system privileges that allow ignoring DACL from + * accessing the files. + * + * SDDL sample: O:G:D:PAI + * c.f https://learn.microsoft.com/en-us/windows/win32/secauthz/null-dacls-and-empty-dacls + */ + + zfsacl_t zfsacl; + struct SMB4ACE_T *smbace = NULL; + zfsacl_entry_t placeholder_entry = NULL; + bool ok; + zfsace_permset_t perms; + + switch (dtype) { + case IXNAS_NULL_DACL: + perms = ZFSACE_FULL_SET; + break; + case IXNAS_EMPTY_DACL: + perms = 0; + break; + default: + smb_panic("unexpected ixnas_dacl_type"); + }; + + zfsacl = zfsacl_init(1, ZFSACL_BRAND_NFSV4); + if (zfsacl == NULL) { + DBG_ERR("%s: acl_init failed: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + return NT_STATUS_ACCESS_DENIED; + } + + ok = zfsacl_create_aclentry(zfsacl, ZFSACL_APPEND_ENTRY, &placeholder_entry); + if (!ok) { + DBG_ERR("zfsacl_create_aclentry() failed: %s\n", strerror(errno)); + zfsacl_free(&zfsacl); + return NT_STATUS_ACCESS_DENIED; + } + + ok = zfsace_set_permset(placeholder_entry, perms); + if (!ok) { + DBG_ERR("zfsacl_set_permset() failed: %s\n", strerror(errno)); + zfsacl_free(&zfsacl); + return NT_STATUS_ACCESS_DENIED; + } + + ok = zfsace_set_who(placeholder_entry, ZFSACL_EVERYONE, ZFSACL_UNDEFINED_ID); + if (!ok) { + DBG_ERR("zfsacl_set_who() failed: %s\n", strerror(errno)); + zfsacl_free(&zfsacl); + return NT_STATUS_ACCESS_DENIED; + } + + dump_acl_info(zfsacl); + if (!fsp_set_zfsacl(fsp, zfsacl)) { + DBG_ERR("%s: failed to set acl: %s\n", + fsp_str_dbg(fsp), strerror(errno)); + zfsacl_free(&zfsacl); + return map_nt_error_from_unix(errno); + } + + zfsacl_free(&zfsacl); + return NT_STATUS_OK; +} + static NTSTATUS ixnas_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, @@ -976,6 +1171,14 @@ static NTSTATUS ixnas_fset_nt_acl(vfs_handle_struct *handle, struct ixnas_config_data, return NT_STATUS_INTERNAL_ERROR); + if ((security_info_sent & SECINFO_DACL) && + (psd->dacl == NULL)) { + return ixnas_fset_special_dacl(handle, fsp, IXNAS_NULL_DACL); + } else if ((security_info_sent & SECINFO_DACL) && + ((psd->type & SEC_DESC_DACL_PRESENT) == 0)) { + return ixnas_fset_special_dacl(handle, fsp, IXNAS_EMPTY_DACL); + } + return smb_set_nt_acl_nfs4(handle, fsp, &config->nfs4_params, @@ -1015,7 +1218,7 @@ static int ixnas_fail__sys_acl_blob_get_fd(vfs_handle_struct *handle, /******************************************************************** Convert chmod() requests into an appropriate non-inheriting ACL - entry. We don't rely on FreeBSD kernel behavior in this case, + entry. We don't rely on ZFS behavior in this case, because it strips some bits that we actually care about (WRITE_ATTRIBUTES, DELETE, etc.). If DELETE is stripped, then users will no longer be able to rename files. @@ -1412,214 +1615,6 @@ static int ixnas_fchmod(vfs_handle_struct *handle, return -1; } -#if 0 /*pending work on FILE IDs from FreeBSD */ -#if defined (FREEBSD) -static struct file_id ixnas_file_id_create(struct vfs_handle_struct *handle, - const SMB_STRUCT_STAT *sbuf) -{ - struct file_id key = (struct file_id) { - .devid = sbuf->st_ex_dev, - .inode = sbuf->st_ex_ino, - .extid = sbuf->st_ex_gen, - }; - - return key; -} - -static inline uint64_t gen_id_comp(uint64_t p) { - uint64_t out = (p & UINT32_MAX) ^ (p >> 32); - return out; -}; - -#endif - -static uint64_t ixnas_fs_file_id(struct vfs_handle_struct *handle, - const SMB_STRUCT_STAT *psbuf); - -static int ixnas_renameat(vfs_handle_struct *handle, - files_struct *srcfsp, - const struct smb_filename *smb_fname_src, - files_struct *dstfsp, - const struct smb_filename *smb_fname_dst) -{ - int result = 1; - struct ixnas_config_data *config = NULL; - char *tmp_base_name = NULL; - uint64_t srcid, dstid; - - SMB_VFS_HANDLE_GET_DATA(handle, config, - struct ixnas_config_data, - return -1); - - if (config->props->casesens != SMBZFS_INSENSITIVE) { - return SMB_VFS_NEXT_RENAMEAT(handle, - srcfsp, - smb_fname_src, - dstfsp, - smb_fname_dst); - } - - srcid = ixnas_fs_file_id(handle, &srcfsp->fsp_name->st); - dstid = ixnas_fs_file_id(handle, &dstfsp->fsp_name->st); - - if (srcid == dstid) { - result = strcasecmp_m(smb_fname_src->base_name, - smb_fname_dst->base_name); - } - if (result != 0) { - return SMB_VFS_NEXT_RENAMEAT(handle, - srcfsp, - smb_fname_src, - dstfsp, - smb_fname_dst); - } - - dstid = ixnas_fs_file_id(handle, &smb_fname_src->st); - tmp_base_name = talloc_asprintf(talloc_tos(), "%s_%lu", - smb_fname_src->base_name, dstid); - if (tmp_base_name == NULL) { - errno = ENOMEM; - return -1; - } - result = renameat( - fsp_get_pathref_fd(srcfsp), smb_fname_src->base_name, - fsp_get_pathref_fd(dstfsp), tmp_base_name - ); - if (result != 0) { - DBG_ERR("Failed to rename %s to intermediate name %s\n", - smb_fname_src->base_name, tmp_base_name); - TALLOC_FREE(tmp_base_name); - return result; - } - result = renameat( - fsp_get_pathref_fd(dstfsp), tmp_base_name, - fsp_get_pathref_fd(srcfsp), smb_fname_dst->base_name - ); - TALLOC_FREE(tmp_base_name); - return result; -} - -static struct file_id ixnas_file_id_create(struct vfs_handle_struct *handle, - const SMB_STRUCT_STAT *sbuf) -{ - struct file_id key = (struct file_id) { - .devid = sbuf->st_ex_dev, - .inode = sbuf->st_ex_ino, - .extid = sbuf->st_ex_gen, - }; - - return key; -} - -static inline uint64_t gen_id_comp(uint64_t p) { - uint64_t out = (p & UINT32_MAX) ^ (p >> 32); - return out; -}; - -static uint64_t ixnas_fs_file_id(struct vfs_handle_struct *handle, - const SMB_STRUCT_STAT *psbuf) -{ - uint64_t file_id; - if (!(psbuf->st_ex_iflags & ST_EX_IFLAG_CALCULATED_FILE_ID)) { - return psbuf->st_ex_file_id; - } - - file_id = gen_id_comp(psbuf->st_ex_ino); - file_id |= gen_id_comp(psbuf->st_ex_gen) << 32; - return file_id; -} - -static int fsp_set_times(files_struct *fsp, struct timespec *times, bool set_btime) -{ - int flag = set_btime ? AT_UTIMENSAT_BTIME : 0; - if (fsp->fsp_flags.have_proc_fds) { - int fd = fsp_get_pathref_fd(fsp); - const char *p = NULL; - struct sys_proc_fd_path_buf buf; - - p = sys_proc_fd_path(fd, &buf); - if (p != NULL) { - return utimensat(AT_FDCWD, p, times, 0); - } - - return -1; - } - - /* fallback to path-based call */ - return utimensat(AT_FDCWD, fsp->fsp_name->base_name, times, 0); -} - -static int ixnas_ntimes(vfs_handle_struct *handle, - files_struct *fsp, - struct smb_file_time *ft) -{ - int result = -1; - struct ixnas_config_data *config = NULL; - struct timespec ts[2], *times = NULL; - - if (is_named_stream(fsp->fsp_name)) { - errno = ENOENT; - return result; - } - - SMB_VFS_HANDLE_GET_DATA(handle, config, - struct ixnas_config_data, - return -1); - - if (config->dosattrib_xattr) { - return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); - } - - /* - * man utimensat(2) - * If times is non-NULL, it is assumed to point to an array of two - * timespec structures. The access time is set to the value of the - * second element. For filesystems that support file birth (creation) times, - * the birth time will be set to the value of the second element if the - * second element is older than the currently set birthtime. To set both - * a birth time and a modification tie, two calls are required. The first - * to set the birth time and the second to set the (presumabley newer). - */ - if (ft != NULL) { - if (is_robocopy_init(ft)) { - return 0; - } - if (is_omit_timespec(&ft->atime)) { - ft->atime= fsp->fsp_name->st.st_ex_atime; - } - if (is_omit_timespec(&ft->mtime)) { - ft->mtime = fsp->fsp_name->st.st_ex_mtime; - } - /* mtime and atime are unchanged */ - if ((timespec_compare(&ft->atime, - &fsp->fsp_name->st.st_ex_atime) == 0) && - (timespec_compare(&ft->mtime, - &fsp->fsp_name->st.st_ex_mtime) == 0)) { - return 0; - } - /* - * Perform two utimensat() calls if needed to set the specified - * timestamps. - */ - if (is_omit_timespec(&ft->create_time)) { - ft->create_time = ft->mtime; - } - ts[0] = ft->atime; - ts[1] = ft->create_time; - result = fsp_set_times(fsp, ts); - if (timespec_compare(&ft->mtime, &ft->create_time) != 0) { - ts[1] = ft->mtime; - result = fsp_set_times(fsp, ts); - } - } - - if (result != 0) { - DBG_ERR("utimensat failed: %s \n", strerror(errno)); - } - return result; -} -#endif /* FREEBSD */ - static bool set_acl_parameters(struct vfs_handle_struct *handle, struct ixnas_config_data *config) { @@ -1660,7 +1655,8 @@ static bool set_acl_parameters(struct vfs_handle_struct *handle, * middleware will probably not let the user get this far, but it's better to * be somewhat safer. */ - if (path_get_aclbrand(handle->conn->connectpath) != ACL_BRAND_NFS4) { + handle->conn->aclbrand = path_get_aclbrand(handle->conn->connectpath); + if (handle->conn->aclbrand != TRUENAS_ACL_BRAND_NFS4) { DBG_ERR("Connectpath does not support NFSv4 ACLs. Disabling ZFS ACL handling.\n"); config->zfs_acl_enabled = false; } @@ -1708,32 +1704,6 @@ static int ixnas_connect(struct vfs_handle_struct *handle, config->dosattrib_xattr = lp_parm_bool(SNUM(handle->conn), "ixnas", "dosattrib_xattr", false); -#if defined (FREEBSD) - if (!config->dosattrib_xattr) { - if ((lp_map_readonly(SNUM(handle->conn))) == MAP_READONLY_YES) { - DBG_INFO("ixnas:dosmode to file flag mapping enabled," - "disabling 'map readonly'\n"); - lp_do_parameter(SNUM(handle->conn), "map readonly", - "no"); - } - - if (lp_map_archive(SNUM(handle->conn))) { - DBG_INFO("ixnas:dosmode to file flag mapping enabled," - "disabling 'map archive'\n"); - lp_do_parameter(SNUM(handle->conn), "map archive", - "no"); - } - - if (lp_store_dos_attributes(SNUM(handle->conn))){ - DBG_INFO("ixnas:dosmode to file flag mapping enabled," - "disabling 'store dos attributes'\n"); - lp_do_parameter(SNUM(handle->conn), "store dos attributes", - "no"); - } - lp_do_parameter(SNUM(handle->conn), "kernel dosmodes", "yes"); - } -#endif - ok = set_acl_parameters(handle, config); if (!ok) { TALLOC_FREE(config); @@ -1754,10 +1724,6 @@ static struct vfs_fn_pointers ixnas_fns = { .fset_dos_attributes_fn = ixnas_fset_dos_attributes, /* zfs_acl_enabled = true */ .fchmod_fn = ixnas_fchmod, -#if defined (FREEBSD) - .fntimes_fn = ixnas_ntimes, - .file_id_create_fn = ixnas_file_id_create, -#endif .fget_nt_acl_fn = ixnas_fget_nt_acl, .fset_nt_acl_fn = ixnas_fset_nt_acl, .sys_acl_get_fd_fn = ixnas_fail__sys_acl_get_fd, diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 4e4359b3d06..e95fccafeeb 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -172,6 +172,17 @@ static NTSTATUS smbd_check_access_rights_sd( goto access_denied; } + /* + * A null DACL grants full access to any user that requests it; normal + * security checking is not performed with respect to the object. + * + * c.f. https://learn.microsoft.com/en-us/windows/win32/secauthz/null-dacls-and-empty-dacls + */ + if ((conn->aclbrand == TRUENAS_ACL_BRAND_NFS4) && + (sd->type & SEC_DESC_DACL_PRESENT) && (sd->dacl == NULL)) { + return NT_STATUS_OK; + } + status = se_file_access_check(sd, get_current_nttok(conn), use_privs,