From 8ff2fe33bdad605a2abfb5f3fac28b32f3211b96 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Thu, 14 Jul 2022 19:47:23 +0200 Subject: [PATCH] smbd: Userspace symlink eval in filename_convert_dirfsp() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This converts filename_convert_dirfsp to do symlink evaluation in user space. It uses openat_pathref_dirfsp_nosymlink() to open the dirpath and looks at the proper NT_STATUS_STOPPED_ON_SYMLINK response. Using this avoids filename_convert() and thus unix_convert() completely for the SMB2_CREATE case. The tests samba3.blackbox.smbclient_s3.NT1.plain.Recursive ls across MS-DFS links now correctly stop the symlink lookup recursion with NT_STATUS_OBJECT_PATH_NOT_FOUND. Previously we did not correcly pass up the ELOOP coming back from the stat-call. Signed-off-by: Volker Lendecke Reviewed-by: Ralph Boehme Autobuild-User(master): Ralph Böhme Autobuild-Date(master): Mon Jul 25 12:56:08 UTC 2022 on sn-devel-184 --- source3/script/tests/test_smbclient_s3.sh | 10 +- source3/smbd/filename.c | 222 ++++++++++++++++++++-- 2 files changed, 208 insertions(+), 24 deletions(-) diff --git a/source3/script/tests/test_smbclient_s3.sh b/source3/script/tests/test_smbclient_s3.sh index bc03f432411..33737b22273 100755 --- a/source3/script/tests/test_smbclient_s3.sh +++ b/source3/script/tests/test_smbclient_s3.sh @@ -407,7 +407,6 @@ EOF test_msdfs_recursive_dir() { tmpfile=$PREFIX/smbclient.in.$$ - error="NT_STATUS_OBJECT_PATH_NOT_FOUND" cat >$tmpfile </dev/null 2>&1 + echo "$out" | grep 'NT_STATUS_OBJECT_PATH_NOT_FOUND listing \widelinks\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\*' > /dev/null 2>&1 ret="$?" - if [ "$ret" -eq 0 ]; then - echo "$out" - echo "Listing \\msdfs-share recursively found $error" - return 1 + if [ "$ret" -ne 0 ]; then + echo "$out" + echo "Listing \\msdfs-share recursively did not properly end in symlink recursion" fi return 0 diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c index e50043f1171..7600fc2c25d 100644 --- a/source3/smbd/filename.c +++ b/source3/smbd/filename.c @@ -2612,14 +2612,85 @@ lookup: * Split up name_in as sent by the client into a directory pathref fsp * and a relative smb_filename. */ -NTSTATUS filename_convert_dirfsp( +static const char *previous_slash(const char *name_in, const char *slash) +{ + const char *prev = name_in; + + while (true) { + const char *next = strchr_m(prev, '/'); + + SMB_ASSERT(next != NULL); /* we have at least one slash */ + + if (next == slash) { + break; + } + + prev = next+1; + }; + + if (prev == name_in) { + /* no previous slash */ + return NULL; + } + + return prev; +} + +static char *symlink_target_path( + TALLOC_CTX *mem_ctx, + const char *name_in, + const char *substitute, + size_t unparsed) +{ + size_t name_in_len = strlen(name_in); + const char *p_unparsed = NULL; + const char *parent = NULL; + char *ret; + + SMB_ASSERT(unparsed <= name_in_len); + + p_unparsed = name_in + (name_in_len - unparsed); + + if (substitute[0] == '/') { + ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed); + return ret; + } + + if (unparsed == 0) { + parent = strrchr_m(name_in, '/'); + } else { + parent = previous_slash(name_in, p_unparsed); + } + + if (parent == NULL) { + /* no previous slash */ + parent = name_in; + } + + ret = talloc_asprintf( + mem_ctx, + "%.*s%s%s", + (int)(parent - name_in), + name_in, + substitute, + p_unparsed); + return ret; +} + +/* + * Split up name_in as sent by the client into a directory pathref fsp + * and a relative smb_filename. + */ +static NTSTATUS filename_convert_dirfsp_nosymlink( TALLOC_CTX *mem_ctx, connection_struct *conn, const char *name_in, uint32_t ucf_flags, NTTIME twrp, struct files_struct **_dirfsp, - struct smb_filename **_smb_fname) + struct smb_filename **_smb_fname, + char **_substitute, + size_t *_unparsed) { struct smb_filename *smb_dirname = NULL; struct smb_filename *smb_fname_rel = NULL; @@ -2704,16 +2775,45 @@ NTSTATUS filename_convert_dirfsp( } } - status = filename_convert( - mem_ctx, - conn, - dirname, - ucf_flags & ~UCF_PREP_CREATEFILE, - 0, - &smb_dirname); + if (dirname[0] == '\0') { + status = synthetic_pathref( + mem_ctx, + conn->cwd_fsp, + ".", + NULL, + NULL, + 0, + posix ? SMB_FILENAME_POSIX_PATH : 0, + &smb_dirname); + } else { + char *substitute = NULL; + size_t unparsed = 0; + + status = openat_pathref_dirfsp_nosymlink( + mem_ctx, + conn, + dirname, + 0, + &smb_dirname, + &unparsed, + &substitute); + + if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) { + + size_t name_in_len = strlen(name_in); + size_t dirname_len = strlen(dirname); + + SMB_ASSERT(name_in_len >= dirname_len); + + *_substitute = substitute; + *_unparsed = unparsed + (name_in_len - dirname_len); + + goto fail; + } + } if (!NT_STATUS_IS_OK(status)) { - DBG_DEBUG("filename_convert(%s) failed: %s\n", + DBG_DEBUG("opening directory %s failed: %s\n", dirname, nt_errstr(status)); TALLOC_FREE(dirname); @@ -2735,14 +2835,6 @@ NTSTATUS filename_convert_dirfsp( goto fail; } - if (twrp == 0) { - /* - * Might be SMB1 @GMT-style path. filename_convert() - * will have extracted the twrp. - */ - twrp = smb_dirname->twrp; - } - smb_fname_rel = synthetic_smb_fname( mem_ctx, fname_rel, @@ -2894,6 +2986,100 @@ fail: return status; } +NTSTATUS filename_convert_dirfsp( + TALLOC_CTX *mem_ctx, + connection_struct *conn, + const char *name_in, + uint32_t ucf_flags, + NTTIME twrp, + struct files_struct **_dirfsp, + struct smb_filename **_smb_fname) +{ + char *substitute = NULL; + size_t unparsed = 0; + NTSTATUS status; + char *target = NULL; + char *abs_target = NULL; + char *abs_target_canon = NULL; + size_t symlink_redirects = 0; + bool in_share; + +next: + if (symlink_redirects > 40) { + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + + status = filename_convert_dirfsp_nosymlink( + mem_ctx, + conn, + name_in, + ucf_flags, + twrp, + _dirfsp, + _smb_fname, + &substitute, + &unparsed); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) { + return status; + } + + if (!lp_follow_symlinks(SNUM(conn))) { + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + + if (ucf_flags & UCF_POSIX_PATHNAMES) { + /* + * SMB1 posix never traverses symlinks + */ + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + + target = symlink_target_path(mem_ctx, name_in, substitute, unparsed); + if (target == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("name_in: %s, substitute: %s, unparsed: %zu, target=%s\n", + name_in, + substitute, + unparsed, + target); + + if (target[0] == '/') { + abs_target = target; + } else { + abs_target = talloc_asprintf( + mem_ctx, "%s/%s", conn->connectpath, target); + if (abs_target == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + abs_target_canon = canonicalize_absolute_path(mem_ctx, abs_target); + if (abs_target_canon == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("abs_target_canon=%s\n", abs_target_canon); + + in_share = strncmp( + abs_target_canon, + conn->connectpath, + strlen(conn->connectpath)) == 0; + if (!in_share) { + DBG_DEBUG("wide link to %s\n", abs_target_canon); + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + } + + name_in = talloc_strdup( + mem_ctx, abs_target_canon + strlen(conn->connectpath) + 1); + + symlink_redirects += 1; + + goto next; +} + /* * Build the full path from a dirfsp and dirfsp relative name */ -- 2.47.3