]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
smbd: Userspace symlink eval in filename_convert_dirfsp()
authorVolker Lendecke <vl@samba.org>
Thu, 14 Jul 2022 17:47:23 +0000 (19:47 +0200)
committerRalph Boehme <slow@samba.org>
Mon, 25 Jul 2022 12:56:08 +0000 (12:56 +0000)
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 <vl@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
Autobuild-User(master): Ralph Böhme <slow@samba.org>
Autobuild-Date(master): Mon Jul 25 12:56:08 UTC 2022 on sn-devel-184

source3/script/tests/test_smbclient_s3.sh
source3/smbd/filename.c

index bc03f432411897e8805912dfba9eac94f3941174..33737b22273b415a6486d55a83cd7fe0a86e6f5e 100755 (executable)
@@ -407,7 +407,6 @@ EOF
 test_msdfs_recursive_dir()
 {
        tmpfile=$PREFIX/smbclient.in.$$
-       error="NT_STATUS_OBJECT_PATH_NOT_FOUND"
 
        cat >$tmpfile <<EOF
 recurse
@@ -425,13 +424,12 @@ EOF
                return 1
        fi
 
-       echo "$out" | grep "$error" >/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
index e50043f1171f9156f390c0ae4c9f0b5cfafb0249..7600fc2c25d470c31b6ddc13fc9199a9131253cd 100644 (file)
@@ -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
  */