1
0
mirror of https://github.com/samba-team/samba.git synced 2025-03-11 16:58:40 +03:00

smbd: Remove non_widelink_open()

Better look at the final code, not at the patch. The idea is to call
filename_convert_dirfsp() from fd_openat() and just have one place to
follow symlinks.

Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>

Autobuild-User(master): Ralph Böhme <slow@samba.org>
Autobuild-Date(master): Tue Nov 12 19:21:11 UTC 2024 on atb-devel-224
This commit is contained in:
Volker Lendecke 2024-10-21 09:41:06 +02:00 committed by Ralph Boehme
parent 31eac22e08
commit c7839facdb

View File

@ -467,384 +467,6 @@ static NTSTATUS check_base_file_access(struct files_struct *fsp,
access_mask);
}
static NTSTATUS chdir_below_conn(
TALLOC_CTX *mem_ctx,
connection_struct *conn,
const char *connectpath,
size_t connectpath_len,
struct smb_filename *dir_fname,
struct smb_filename **_oldwd_fname)
{
struct smb_filename *oldwd_fname = NULL;
struct smb_filename *smb_fname_dot = NULL;
struct smb_filename *real_fname = NULL;
const char *relative = NULL;
NTSTATUS status;
int ret;
bool ok;
if (!ISDOT(dir_fname->base_name)) {
oldwd_fname = vfs_GetWd(talloc_tos(), conn);
if (oldwd_fname == NULL) {
status = map_nt_error_from_unix(errno);
goto out;
}
/* Pin parent directory in place. */
ret = vfs_ChDir(conn, dir_fname);
if (ret == -1) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("chdir to %s failed: %s\n",
dir_fname->base_name,
strerror(errno));
goto out;
}
}
smb_fname_dot = synthetic_smb_fname(
talloc_tos(),
".",
NULL,
NULL,
0,
dir_fname->flags);
if (smb_fname_dot == NULL) {
status = NT_STATUS_NO_MEMORY;
goto out;
}
real_fname = SMB_VFS_REALPATH(conn, talloc_tos(), smb_fname_dot);
if (real_fname == NULL) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("realpath in %s failed: %s\n",
dir_fname->base_name,
strerror(errno));
goto out;
}
TALLOC_FREE(smb_fname_dot);
ok = subdir_of(connectpath,
connectpath_len,
real_fname->base_name,
&relative);
if (ok) {
TALLOC_FREE(real_fname);
*_oldwd_fname = oldwd_fname;
return NT_STATUS_OK;
}
DBG_NOTICE("Bad access attempt: %s is a symlink "
"outside the share path\n"
"conn_rootdir =%s\n"
"resolved_name=%s\n",
dir_fname->base_name,
connectpath,
real_fname->base_name);
TALLOC_FREE(real_fname);
status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
out:
if (oldwd_fname != NULL) {
ret = vfs_ChDir(conn, oldwd_fname);
SMB_ASSERT(ret == 0);
TALLOC_FREE(oldwd_fname);
}
return status;
}
/*
* Get the symlink target of dirfsp/symlink_name, making sure the
* target is below connection_path.
*/
static NTSTATUS symlink_target_below_conn(
TALLOC_CTX *mem_ctx,
const char *connection_path,
struct files_struct *fsp,
struct files_struct *dirfsp,
struct smb_filename *symlink_name,
char **_target)
{
char *target = NULL;
char *absolute = NULL;
NTSTATUS status;
if (fsp_get_pathref_fd(fsp) != -1) {
/*
* fsp is an O_PATH open, Linux does a "freadlink"
* with an empty name argument to readlinkat
*/
status = readlink_talloc(talloc_tos(), fsp, NULL, &target);
} else {
status = readlink_talloc(
talloc_tos(), dirfsp, symlink_name, &target);
}
if (!NT_STATUS_IS_OK(status)) {
return status;
}
status = safe_symlink_target_path(talloc_tos(),
connection_path,
dirfsp->fsp_name->base_name,
target,
0,
&absolute);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("safe_symlink_target_path() failed: %s\n",
nt_errstr(status));
return status;
}
if (absolute[0] == '\0') {
/*
* special case symlink to share root: "." is our
* share root filename
*/
TALLOC_FREE(absolute);
absolute = talloc_strdup(talloc_tos(), ".");
if (absolute == NULL) {
return NT_STATUS_NO_MEMORY;
}
}
*_target = absolute;
return NT_STATUS_OK;
}
/****************************************************************************
Non-widelink open.
****************************************************************************/
static NTSTATUS non_widelink_open(const struct files_struct *dirfsp,
files_struct *fsp,
struct smb_filename *smb_fname,
const struct vfs_open_how *_how)
{
struct connection_struct *conn = fsp->conn;
const char *connpath = conn->connectpath;
size_t connpath_len;
NTSTATUS status = NT_STATUS_OK;
int fd = -1;
char *orig_smb_fname_base = smb_fname->base_name;
struct smb_filename *orig_fsp_name = fsp->fsp_name;
struct smb_filename *smb_fname_rel = NULL;
struct smb_filename *oldwd_fname = NULL;
struct smb_filename *parent_dir_fname = NULL;
struct vfs_open_how how = *_how;
char *target = NULL;
size_t link_depth = 0;
int ret;
SMB_ASSERT(!fsp_is_alternate_stream(fsp));
if (connpath == NULL) {
/*
* This can happen with shadow_copy2 if the snapshot
* path is not found
*/
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
connpath_len = strlen(connpath);
again:
if (smb_fname->base_name[0] == '/') {
int cmp = strcmp(connpath, smb_fname->base_name);
if (cmp == 0) {
SMB_ASSERT(dirfsp == conn->cwd_fsp);
smb_fname->base_name = talloc_strdup(smb_fname, ".");
if (smb_fname->base_name == NULL) {
status = NT_STATUS_NO_MEMORY;
goto out;
}
}
}
if (dirfsp == conn->cwd_fsp) {
status = SMB_VFS_PARENT_PATHNAME(fsp->conn,
talloc_tos(),
smb_fname,
&parent_dir_fname,
&smb_fname_rel);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
status = chdir_below_conn(
talloc_tos(),
conn,
connpath,
connpath_len,
parent_dir_fname,
&oldwd_fname);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
/* Setup fsp->fsp_name to be relative to cwd */
fsp->fsp_name = smb_fname_rel;
} else {
/*
* fsp->fsp_name is unchanged as it is already correctly
* relative to dirfsp.
*/
smb_fname_rel = smb_fname;
}
{
/*
* Assert nobody can step in with a symlink on the
* path, there is no path anymore and we'll use
* O_NOFOLLOW to open.
*/
char *slash = strchr_m(smb_fname_rel->base_name, '/');
SMB_ASSERT(slash == NULL);
}
how.flags |= O_NOFOLLOW;
fd = SMB_VFS_OPENAT(conn,
dirfsp,
smb_fname_rel,
fsp,
&how);
fsp_set_fd(fsp, fd); /* This preserves errno */
if (fd == -1) {
status = map_nt_error_from_unix(errno);
if (errno == ENOENT) {
goto out;
}
/*
* ENOENT makes it worthless retrying with a
* stat, we know for sure the file does not
* exist. For everything else we want to know
* what's there.
*/
ret = SMB_VFS_FSTATAT(
fsp->conn,
dirfsp,
smb_fname_rel,
&fsp->fsp_name->st,
AT_SYMLINK_NOFOLLOW);
if (ret == -1) {
/*
* Keep the original error. Otherwise we would
* mask for example EROFS for open(O_CREAT),
* turning it into ENOENT.
*/
goto out;
}
} else {
ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
}
if (ret == -1) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("fstat[at](%s) failed: %s\n",
smb_fname_str_dbg(smb_fname),
strerror(errno));
goto out;
}
fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
orig_fsp_name->st = fsp->fsp_name->st;
if (!S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
goto out;
}
/*
* Found a symlink to follow in user space
*/
if (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH) {
/* Never follow symlinks on posix open. */
status = NT_STATUS_STOPPED_ON_SYMLINK;
goto out;
}
if (!lp_follow_symlinks(SNUM(conn))) {
/* Explicitly no symlinks. */
status = NT_STATUS_STOPPED_ON_SYMLINK;
goto out;
}
link_depth += 1;
if (link_depth >= 40) {
status = NT_STATUS_STOPPED_ON_SYMLINK;
goto out;
}
fsp->fsp_name = orig_fsp_name;
status = symlink_target_below_conn(
talloc_tos(),
connpath,
fsp,
discard_const_p(files_struct, dirfsp),
smb_fname_rel,
&target);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("symlink_target_below_conn() failed: %s\n",
nt_errstr(status));
goto out;
}
/*
* Close what openat(O_PATH) potentially left behind
*/
fd_close(fsp);
if (smb_fname->base_name != orig_smb_fname_base) {
TALLOC_FREE(smb_fname->base_name);
}
smb_fname->base_name = target;
if (oldwd_fname != NULL) {
ret = vfs_ChDir(conn, oldwd_fname);
if (ret == -1) {
smb_panic("unable to get back to old directory\n");
}
TALLOC_FREE(oldwd_fname);
}
/*
* And do it all again... As smb_fname is not relative to the passed in
* dirfsp anymore, we pass conn->cwd_fsp as dirfsp to
* non_widelink_open() to trigger the chdir(parentdir) logic.
*/
dirfsp = conn->cwd_fsp;
goto again;
out:
fsp->fsp_name = orig_fsp_name;
smb_fname->base_name = orig_smb_fname_base;
TALLOC_FREE(parent_dir_fname);
if (!NT_STATUS_IS_OK(status)) {
fd_close(fsp);
}
if (oldwd_fname != NULL) {
ret = vfs_ChDir(conn, oldwd_fname);
if (ret == -1) {
smb_panic("unable to get back to old directory\n");
}
TALLOC_FREE(oldwd_fname);
}
return status;
}
/****************************************************************************
fd support routines - attempt to do a dos_open.
****************************************************************************/
@ -859,50 +481,119 @@ NTSTATUS fd_openat(const struct files_struct *dirfsp,
NTSTATUS status = NT_STATUS_OK;
bool fsp_is_stream = fsp_is_alternate_stream(fsp);
bool smb_fname_is_stream = is_named_stream(smb_fname);
struct files_struct *dirfsp_conv = NULL;
struct smb_filename *smb_fname_conv = NULL;
struct smb_filename *smb_fname_rel = NULL;
struct files_struct *root_fsp = NULL;
const char *name_in = smb_fname->base_name;
int fd;
SMB_ASSERT(fsp_is_stream == smb_fname_is_stream);
/*
* Never follow symlinks on a POSIX client. The
* client should be doing this.
*/
if (fsp->fsp_flags.posix_open || !lp_follow_symlinks(SNUM(conn))) {
how.flags |= O_NOFOLLOW;
}
if (fsp_is_stream) {
int fd;
fd = SMB_VFS_OPENAT(
conn,
NULL, /* stream open is relative to fsp->base_fsp */
NULL, /* stream open is relative to fsp->base_fsp */
smb_fname,
fsp,
&how);
if (fd == -1) {
status = map_nt_error_from_unix(errno);
}
fsp_set_fd(fsp, fd);
goto done;
}
if (fd != -1) {
how.flags |= O_NOFOLLOW; /* just to be sure */
if (strchr(smb_fname->base_name, '/') == NULL) {
/*
* With O_NOFOLLOW and no intermediate path components
* we can try directly.
*/
fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname, fsp, &how);
if (fd >= 0) {
fsp_set_fd(fsp, fd);
status = vfs_stat_fsp(fsp);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("vfs_stat_fsp failed: %s\n",
nt_errstr(status));
fd_close(fsp);
if (NT_STATUS_IS_OK(status) &&
!S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
smb_fname->st = fsp->fsp_name->st;
fsp->fsp_flags.is_directory = S_ISDIR(
fsp->fsp_name->st.st_ex_mode);
return NT_STATUS_OK;
}
}
/*
* We found a symlink in the lcomp via O_PATH,
* let filename_convert_dirfsp_rel follow
* it. This means we're going to open the
* symlink twice, but this is something to
* optimize when it becomes a problem.
*/
SMB_VFS_CLOSE(fsp);
fsp_set_fd(fsp, -1);
fd = -1;
}
}
if (name_in[0] == '/') {
/*
* filename_convert_dirfsp can't deal with absolute
* paths, make this relative to "/"
*/
name_in += 1;
status = open_rootdir_pathref_fsp(conn, &root_fsp);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
dirfsp = root_fsp;
}
if (ISDOT(name_in)) {
/*
* filename_convert_dirfsp does not like ".", use ""
*/
name_in += 1;
}
status = filename_convert_dirfsp_rel(
talloc_tos(),
conn,
discard_const_p(struct files_struct, dirfsp),
name_in,
UCF_POSIX_PATHNAMES, /* no case insensitive search */
smb_fname->twrp,
&dirfsp_conv,
&smb_fname_conv,
&smb_fname_rel);
dirfsp = NULL;
if (root_fsp != NULL) {
fd_close(root_fsp);
file_free(NULL, root_fsp);
root_fsp = NULL;
}
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("filename_convert_dirfsp_rel returned %s\n",
nt_errstr(status));
return status;
}
/*
* Only follow symlinks within a share
* definition.
*/
status = non_widelink_open(dirfsp, fsp, smb_fname, &how);
if (!NT_STATUS_IS_OK(status)) {
fd = SMB_VFS_OPENAT(conn, dirfsp_conv, smb_fname_rel, fsp, &how);
if (fd == -1) {
status = map_nt_error_from_unix(errno);
}
fd_close(dirfsp_conv);
file_free(NULL, dirfsp_conv);
dirfsp_conv = NULL;
TALLOC_FREE(smb_fname_conv);
done:
fsp_set_fd(fsp, fd); /* This preserves errno */
if (fd == -1) {
if (NT_STATUS_EQUAL(status, NT_STATUS_TOO_MANY_OPENED_FILES)) {
static time_t last_warned = 0L;
@ -922,8 +613,19 @@ NTSTATUS fd_openat(const struct files_struct *dirfsp,
fsp_get_pathref_fd(fsp),
nt_errstr(status));
return status;
} else {
status = vfs_stat_fsp(fsp);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("vfs_stat_fsp failed: %s\n",
nt_errstr(status));
fd_close(fsp);
return status;
}
}
smb_fname->st = fsp->fsp_name->st;
fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
DBG_DEBUG("name %s, flags = 0%o mode = 0%o, fd = %d\n",
smb_fname_str_dbg(smb_fname),
how.flags,