>From af9194efbc69200cd7a0b45144d1902d81c325f3 Mon Sep 17 00:00:00 2001 From: Mikhail Skorzhinskiy Date: Mon, 16 Jan 2017 16:48:34 +0300 Subject: [PATCH] modules/worm: wraped more system calls, added read_only period 1. Wraps open, ntimes, chmod, chmod_acl, rename, setxattr system calls. It should block all types of write access from all initiator types. 2. Carefully choose from what time we should start counting WORM protection. 3. Added new parameter "readonly_period". It's an upper bound for WORM protection. 4. Minor change in timespec max/min functions. Get rid from unnecessary copying. BUG: https://bugzilla.samba.org/show_bug.cgi?id=10430 Signed-off-by: Mikhail Skorzhinskiy --- lib/util/time.c | 23 +++- lib/util/time.h | 4 +- source3/modules/vfs_worm.c | 258 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 249 insertions(+), 36 deletions(-) diff --git a/lib/util/time.c b/lib/util/time.c index 8c01627e2a1..d6a26fbf0b6 100644 --- a/lib/util/time.c +++ b/lib/util/time.c @@ -855,13 +855,26 @@ _PUBLIC_ struct timespec timespec_current(void) Return the lesser of two timespecs. ****************************************************************************/ -struct timespec timespec_min(const struct timespec *ts1, +struct timespec* timespec_min(const struct timespec *ts1, const struct timespec *ts2) { - if (ts1->tv_sec < ts2->tv_sec) return *ts1; - if (ts1->tv_sec > ts2->tv_sec) return *ts2; - if (ts1->tv_nsec < ts2->tv_nsec) return *ts1; - return *ts2; + if (ts1->tv_sec < ts2->tv_sec) return ts1; + if (ts1->tv_sec > ts2->tv_sec) return ts2; + if (ts1->tv_nsec < ts2->tv_nsec) return ts1; + return ts2; +} + +/**************************************************************************** + Return the greater of two timespecs. +****************************************************************************/ + +struct timespec* timespec_max(const struct timespec *ts1, + const struct timespec *ts2) +{ + if (ts1->tv_sec < ts2->tv_sec) return ts2; + if (ts1->tv_sec > ts2->tv_sec) return ts1; + if (ts1->tv_nsec < ts2->tv_nsec) return ts2; + return ts2; } /**************************************************************************** diff --git a/lib/util/time.h b/lib/util/time.h index 42d23865b82..9b8b3b9c7e2 100644 --- a/lib/util/time.h +++ b/lib/util/time.h @@ -313,7 +313,9 @@ bool null_timespec(struct timespec ts); struct timespec convert_timeval_to_timespec(const struct timeval tv); struct timeval convert_timespec_to_timeval(const struct timespec ts); struct timespec timespec_current(void); -struct timespec timespec_min(const struct timespec *ts1, +struct timespec* timespec_min(const struct timespec *ts1, + const struct timespec *ts2); +struct timespec* timespec_max(const struct timespec *ts1, const struct timespec *ts2); int timespec_compare(const struct timespec *ts1, const struct timespec *ts2); void round_timespec_to_sec(struct timespec *ts); diff --git a/source3/modules/vfs_worm.c b/source3/modules/vfs_worm.c index 9638d960534..26956537abf 100644 --- a/source3/modules/vfs_worm.c +++ b/source3/modules/vfs_worm.c @@ -22,6 +22,107 @@ #include "system/filesys.h" #include "libcli/security/security.h" +/* Definitions & helpers */ +const uint32_t write_access_flags = + FILE_WRITE_DATA + | FILE_APPEND_DATA + | FILE_WRITE_ATTRIBUTES + | DELETE_ACCESS + | WRITE_DAC_ACCESS + | WRITE_OWNER_ACCESS; + +/* Exactly one second */ +#define DEFAULT_GRACE_PERIOD 3600 + +/* Exactly 31 day */ +#define DEFAULT_RO_PERIOD 157680000 + +/* Fetch smb_fname structure from UNIX file path */ +static NTSTATUS smb_fname_from_path(vfs_handle_struct *handle, + const char *file_path, + struct smb_filename **smb_fname) +{ + NTSTATUS status; + TALLOC_CTX *ctx = talloc_tos(); + char *name = talloc_strdup(ctx, file_path); + + if (!name) + goto err; + + unix_format(name); + name = unix_clean_name(ctx, name); + if (!name) + goto err; + + status = unix_convert(ctx, handle->conn, name, smb_fname, 0); + if (!NT_STATUS_IS_OK(status)) + goto err; + + return NT_STATUS_OK; + err: + return NT_STATUS_NO_MEMORY; +} + +/* Main checker */ +static bool is_readonly(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + struct timepsec *ts; + double age; + int grace_period; + int readonly_period; + + if (!VALID_STAT(smb_fname->st)) + goto out; + + if (smb_fname->st.st_ex_size == 0 && !S_ISDIR(smb_fname->st.st_ex_mode)) + goto out; + + /* Time checker + + We should decide from what point we count our 'readonly'. + + (*) If we always start count from mtime, then NT client can't modify + file after copying it. (NT doesn't change mtime after copy). + + (*) If we always start count from btime, then Mac client cant copy + file at all ― in the end of copying file it make 'chmod' and if this + call fails it will try to unlink this file. + + So we count from newest timespec to resolve this problems. + */ + ts = timespec_max(&smb_fname->st.st_ex_mtime, &smb_fname->st.st_ex_btime); + ts = timespec_max(ts, &smb_fname->st.st_ex_ctime); + + age = timespec_elapsed(&ts); + grace_period = lp_parm_int(SNUM(handle->conn), "worm", + "grace_period", DEFAULT_GRACE_PERIOD); + readonly_period = lp_parm_int(SNUM(handle->conn), "worm", + "readonle_period", DEFAULT_RO_PERIOD); + + if (age > grace_period && age < readonly_period) + return true; + + out: + return false; +} + +/* VFS handlers */ +static int vfs_worm_ntimes(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct smb_file_time *ft) +{ + + bool readonly = is_readonly(handle, smb_fname); + + if (readonly) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft); +} + static NTSTATUS vfs_worm_create_file(vfs_handle_struct *handle, struct smb_request *req, uint16_t root_dir_fid, @@ -42,48 +143,145 @@ static NTSTATUS vfs_worm_create_file(vfs_handle_struct *handle, const struct smb2_create_blobs *in_context_blobs, struct smb2_create_blobs *out_context_blobs) { - bool readonly = false; - const uint32_t write_access_flags = - FILE_WRITE_DATA | FILE_APPEND_DATA | - FILE_WRITE_ATTRIBUTES | DELETE_ACCESS | - WRITE_DAC_ACCESS | WRITE_OWNER_ACCESS; - NTSTATUS status; - - if (VALID_STAT(smb_fname->st)) { - double age; - age = timespec_elapsed(&smb_fname->st.st_ex_ctime); - if (age > lp_parm_int(SNUM(handle->conn), "worm", - "grace_period", 3600)) { - readonly = true; - } - } - - if (readonly && (access_mask & write_access_flags)) { + if (is_readonly(handle, smb_fname) && (access_mask & write_access_flags)) return NT_STATUS_ACCESS_DENIED; - } - status = SMB_VFS_NEXT_CREATE_FILE( + return SMB_VFS_NEXT_CREATE_FILE( handle, req, root_dir_fid, smb_fname, access_mask, share_access, create_disposition, create_options, file_attributes, oplock_request, lease, allocation_size, private_flags, sd, ea_list, result, pinfo, in_context_blobs, out_context_blobs); - if (!NT_STATUS_IS_OK(status)) { - return status; - } +} - /* - * Access via MAXIMUM_ALLOWED_ACCESS? - */ - if (readonly && ((*result)->access_mask & write_access_flags)) { - close_file(req, *result, NORMAL_CLOSE); - return NT_STATUS_ACCESS_DENIED; - } - return NT_STATUS_OK; +static int vfs_worm_open(vfs_handle_struct *handle, + struct smb_filename *smb_fname, + files_struct *fsp, int flags, mode_t mode) +{ + if (is_readonly(handle, smb_fname) && (fsp->access_mask & write_access_flags)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); +} + +static int vfs_worm_chmod(vfs_handle_struct *handle, const char *file_name, mode_t mode) +{ + struct smb_filename *smb_fname; + NTSTATUS status = smb_fname_from_path(handle, file_name, &smb_fname); + + bool readonly = 1; + + if (NT_STATUS_IS_OK(status)) { + readonly = is_readonly(handle, smb_fname); + } + + if (readonly) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_CHMOD(handle, file_name, mode); } +static int vfs_worm_chmod_acl(vfs_handle_struct *handle, const char *file_name, mode_t mode) +{ + struct smb_filename *smb_fname; + NTSTATUS status = smb_fname_from_path(handle, file_name, &smb_fname); + + bool readonly = 1; + + if (NT_STATUS_IS_OK(status)) { + readonly = is_readonly(handle, smb_fname); + } + + if (readonly) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_CHMOD_ACL(handle, file_name, mode); +} + +static int vfs_worm_rename(vfs_handle_struct *handle, + const struct smb_filename *smb_fname_src, + const struct smb_filename *smb_fname_dst) +{ + if (is_readonly(handle, smb_fname_src)) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst); +} + +static NTSTATUS vfs_worm_fset_nt_acl(vfs_handle_struct *handle, + files_struct *fsp, + uint32_t security_info_sent, + const struct security_descriptor *psd) +{ + if (is_readonly(handle, fsp->fsp_name)) { + errno = EACCES; + return NT_STATUS_ACCESS_DENIED; + } + + return SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd); +} + +static int vfs_worm_setxattr(struct vfs_handle_struct *handle, + const char *path, + const char *name, + const void *value, + size_t size, + int flags) +{ + struct smb_filename *smb_fname; + NTSTATUS status = smb_fname_from_path(handle, path, &smb_fname); + + bool readonly = 1; + + if (NT_STATUS_IS_OK(status)) { + readonly = is_readonly(handle, smb_fname); + } + + if (readonly) { + errno = EACCES; + return -1; + } + + return SMB_VFS_NEXT_SETXATTR(handle, path, name, value, size, flags); +} + +/* Wrappers table and VDS module init function */ + static struct vfs_fn_pointers vfs_worm_fns = { .create_file_fn = vfs_worm_create_file, + + /* Wrappers for *NIX initiators + + For some reason, if you try open files from Mac or Linux initiator + you will go beside from .create_file_fn. So we should wrap *NIX VFS + calls too. + + Without that wraps Linux and Mac clients have possibility to make + appending (without open_fn) and unlink (without ntimes). + */ + .open_fn = vfs_worm_open, + .ntimes_fn = vfs_worm_ntimes, + + /* Without that wraps every client have possibility to make chmod, ACL + chmod or rename directory. + */ + .chmod_fn = vfs_worm_chmod, + .chmod_acl_fn = vfs_worm_chmod_acl, + .rename_fn = vfs_worm_rename, + + /* If allow to change extended attributes, some initiators on failed + unlink will mark file as "deleted" which leads that another + initiators will consider such initiators as "hidden". + */ + .setxattr_fn = vfs_worm_setxattr }; NTSTATUS vfs_worm_init(void); -- 2.11.0