From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/modules/vfs_widelinks.c | 418 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 source3/modules/vfs_widelinks.c (limited to 'source3/modules/vfs_widelinks.c') diff --git a/source3/modules/vfs_widelinks.c b/source3/modules/vfs_widelinks.c new file mode 100644 index 0000000..c5b5084 --- /dev/null +++ b/source3/modules/vfs_widelinks.c @@ -0,0 +1,418 @@ +/* + * Widelinks VFS module. Causes smbd not to see symlinks. + * + * Copyright (C) Jeremy Allison, 2020 + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* + What does this module do ? It implements the explicitly insecure + "widelinks = yes" functionality that used to be in the core smbd + code. + + Now this is implemented here, the insecure share-escape code that + explicitly allows escape from an exported share path can be removed + from smbd, leaving it a cleaner and more maintainable code base. + + The smbd code can now always return ACCESS_DENIED if a path + leads outside a share. + + How does it do that ? There are 2 features. + + 1). When the upper layer code does a chdir() call to a pathname, + this module stores the requested pathname inside config->cwd. + + When the upper layer code does a getwd() or realpath(), we return + the absolute path of the value stored in config->cwd, *not* the + position on the underlying filesystem. + + This hides symlinks as if the chdir pathname contains a symlink, + normally doing a realpath call on it would return the real + position on the filesystem. For widelinks = yes, this isn't what + you want. You want the position you think is underneath the share + definition - the symlink path you used to go outside the share, + not the contents of the symlink itself. + + That way, the upper layer smbd code can strictly enforce paths + being underneath a share definition without the knowledge that + "widelinks = yes" has moved us outside the share definition. + + 1a). Note that when setting up a share, smbd may make calls such + as realpath and stat/lstat in order to set up the share definition. + These calls are made *before* smbd calls chdir() to move the working + directory below the exported share definition. In order to allow + this, all the vfs_widelinks functions are coded to just pass through + the vfs call to the next module in the chain if (a). The widelinks + module was loaded in error by an administrator and widelinks is + set to "no". This is the: + + if (!config->active) { + Module not active. + SMB_VFS_NEXT_XXXXX(...) + } + + idiom in the vfs functions. + + 1b). If the module was correctly active, but smbd has yet + to call chdir(), then config->cwd == NULL. In that case + the correct action (to match the previous widelinks behavior + in the code inside smbd) is to pass through the vfs call to + the next module in the chain. That way, any symlinks in the + pathname are still exposed to smbd, which will restrict them to + be under the exported share definition. This allows the module + to "fail safe" for any vfs call made when setting up the share + structure definition, rather than fail unsafe by hiding symlinks + before chdir is called. This is the: + + if (config->cwd == NULL) { + XXXXX syscall before chdir - see note 1b above. + return SMB_VFS_NEXT_XXXXX() + } + + idiom in the vfs functions. + + 2). The module hides the existence of symlinks by inside + lstat(), open(), and readdir() so long as it's not a POSIX + pathname request (those requests *must* be aware of symlinks + and the POSIX client has to follow them, it's expected that + a server will always fail to follow symlinks). + + It does this by: + + 2a). lstat -> stat + 2b). open removes any O_NOFOLLOW from flags. + 2c). The optimization in readdir that returns a stat + struct is removed as this could return a symlink mode + bit, causing smbd to always call stat/lstat itself on + a pathname (which we'll then use to hide symlinks). + +*/ + +#include "includes.h" +#include "smbd/smbd.h" +#include "lib/util_path.h" + +struct widelinks_config { + bool active; + bool is_dfs_share; + char *cwd; +}; + +static int widelinks_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) +{ + struct widelinks_config *config; + int ret; + + ret = SMB_VFS_NEXT_CONNECT(handle, + service, + user); + if (ret != 0) { + return ret; + } + + config = talloc_zero(handle->conn, + struct widelinks_config); + if (!config) { + SMB_VFS_NEXT_DISCONNECT(handle); + return -1; + } + config->active = lp_widelinks(SNUM(handle->conn)); + if (!config->active) { + DBG_ERR("vfs_widelinks module loaded with " + "widelinks = no\n"); + } + config->is_dfs_share = + (lp_host_msdfs() && lp_msdfs_root(SNUM(handle->conn))); + SMB_VFS_HANDLE_SET_DATA(handle, + config, + NULL, /* free_fn */ + struct widelinks_config, + return -1); + return 0; +} + +static int widelinks_chdir(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname) +{ + int ret = -1; + struct widelinks_config *config = NULL; + char *new_cwd = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return -1); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_CHDIR(handle, smb_fname); + } + + /* + * We know we never get a path containing + * DOT or DOTDOT. + */ + + if (smb_fname->base_name[0] == '/') { + /* Absolute path - replace. */ + new_cwd = talloc_strdup(config, + smb_fname->base_name); + } else { + if (config->cwd == NULL) { + /* + * Relative chdir before absolute one - + * see note 1b above. + */ + struct smb_filename *current_dir_fname = + SMB_VFS_NEXT_GETWD(handle, + config); + if (current_dir_fname == NULL) { + return -1; + } + /* Paranoia.. */ + if (current_dir_fname->base_name[0] != '/') { + DBG_ERR("SMB_VFS_NEXT_GETWD returned " + "non-absolute path |%s|\n", + current_dir_fname->base_name); + TALLOC_FREE(current_dir_fname); + return -1; + } + config->cwd = talloc_strdup(config, + current_dir_fname->base_name); + TALLOC_FREE(current_dir_fname); + if (config->cwd == NULL) { + return -1; + } + } + new_cwd = talloc_asprintf(config, + "%s/%s", + config->cwd, + smb_fname->base_name); + } + if (new_cwd == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname); + if (ret == -1) { + TALLOC_FREE(new_cwd); + return ret; + } + /* Replace the cache we use for realpath/getwd. */ + TALLOC_FREE(config->cwd); + config->cwd = new_cwd; + DBG_DEBUG("config->cwd now |%s|\n", config->cwd); + return 0; +} + +static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle, + TALLOC_CTX *ctx) +{ + struct widelinks_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return NULL); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_GETWD(handle, ctx); + } + if (config->cwd == NULL) { + /* getwd before chdir. See note 1b above. */ + return SMB_VFS_NEXT_GETWD(handle, ctx); + } + return synthetic_smb_fname(ctx, + config->cwd, + NULL, + NULL, + 0, + 0); +} + +static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *smb_fname_in) +{ + struct widelinks_config *config = NULL; + char *pathname = NULL; + char *resolved_pathname = NULL; + struct smb_filename *smb_fname; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return NULL); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_REALPATH(handle, + ctx, + smb_fname_in); + } + + if (config->cwd == NULL) { + /* realpath before chdir. See note 1b above. */ + return SMB_VFS_NEXT_REALPATH(handle, + ctx, + smb_fname_in); + } + + if (smb_fname_in->base_name[0] == '/') { + /* Absolute path - process as-is. */ + pathname = talloc_strdup(config, + smb_fname_in->base_name); + } else { + /* Relative path - most commonly "." */ + pathname = talloc_asprintf(config, + "%s/%s", + config->cwd, + smb_fname_in->base_name); + } + + SMB_ASSERT(pathname[0] == '/'); + + resolved_pathname = canonicalize_absolute_path(config, pathname); + if (resolved_pathname == NULL) { + TALLOC_FREE(pathname); + return NULL; + } + + DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n", + smb_fname_in->base_name, + pathname, + resolved_pathname); + + smb_fname = synthetic_smb_fname(ctx, + resolved_pathname, + NULL, + NULL, + 0, + 0); + TALLOC_FREE(pathname); + TALLOC_FREE(resolved_pathname); + return smb_fname; +} + +static int widelinks_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + struct widelinks_config *config = NULL; + + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return -1); + + if (!config->active) { + /* Module not active. */ + return SMB_VFS_NEXT_LSTAT(handle, + smb_fname); + } + + if (config->cwd == NULL) { + /* lstat before chdir. See note 1b above. */ + return SMB_VFS_NEXT_LSTAT(handle, + smb_fname); + } + + if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) { + /* POSIX sees symlinks. */ + return SMB_VFS_NEXT_LSTAT(handle, + smb_fname); + } + + /* Replace with STAT. */ + return SMB_VFS_NEXT_STAT(handle, smb_fname); +} + +static int widelinks_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname, + files_struct *fsp, + const struct vfs_open_how *_how) +{ + struct vfs_open_how how = *_how; + struct widelinks_config *config = NULL; + int ret; + SMB_VFS_HANDLE_GET_DATA(handle, + config, + struct widelinks_config, + return -1); + + if (config->active && + (config->cwd != NULL) && + !(smb_fname->flags & SMB_FILENAME_POSIX_PATH)) + { + /* + * Module active, openat after chdir (see note 1b above) and not + * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW. + */ + how.flags = (how.flags & ~O_NOFOLLOW); + } + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + &how); + if (config->is_dfs_share && ret == -1 && errno == ENOENT) { + struct smb_filename *full_fname = NULL; + int lstat_ret; + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + smb_fname); + if (full_fname == NULL) { + errno = ENOMEM; + return -1; + } + lstat_ret = SMB_VFS_NEXT_LSTAT(handle, + full_fname); + if (lstat_ret != -1 && + VALID_STAT(full_fname->st) && + S_ISLNK(full_fname->st.st_ex_mode)) { + fsp->fsp_name->st = full_fname->st; + } + TALLOC_FREE(full_fname); + errno = ELOOP; + } + return ret; +} + +static struct vfs_fn_pointers vfs_widelinks_fns = { + .connect_fn = widelinks_connect, + + .openat_fn = widelinks_openat, + .lstat_fn = widelinks_lstat, + /* + * NB. We don't need an lchown function as this + * is only called (a) on directory create and + * (b) on POSIX extensions names. + */ + .chdir_fn = widelinks_chdir, + .getwd_fn = widelinks_getwd, + .realpath_fn = widelinks_realpath, +}; + +static_decl_vfs; +NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "widelinks", + &vfs_widelinks_fns); +} -- cgit v1.2.3