]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
smbd: add openat_pathref_fsp()
authorRalph Boehme <slow@samba.org>
Tue, 29 Sep 2020 08:14:47 +0000 (10:14 +0200)
committerRalph Boehme <slow@samba.org>
Wed, 16 Dec 2020 09:08:30 +0000 (09:08 +0000)
open_pathref_fsp() opens an "embedded" fsp inside smb_fname as
smb_fname->fsp. We call such an fsp a "pathref" fsp.

On system that support O_PATH the low level openat() is done with O_PATH. On
systems that lack support for O_PATH, we impersonate the root user as a
fallback.

Setting "is_pathref" in the fsp_flags before calling fd_openat() is what
triggers the special low-level behaviour inside the VFS.

The use of pathref fsps allows updating all callers of path based VFS functions
like

  dos_mode(smb_fname)
  -> SMB_VFS_GET_DOS_ATTRIBUTES(smb_fname)
     -> SMB_VFS_GETXATTR(smb_fname)

to use the handle based VFS function like

  fdos_mode(smb_fname->fsp)
  -> SMB_VFS_FGET_DOS_ATTRIBUTES(fsp)
     -> SMB_VFS_FGETXATTR(fsp)

Signed-off-by: Ralph Boehme <slow@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
source3/include/vfs.h
source3/smbd/files.c
source3/smbd/proto.h

index d46fe096aa1d6f8c597bafb1b5d762152ce9a540..ce971de8627f71539cab5ea94402125c74cd103e 100644 (file)
@@ -825,6 +825,20 @@ struct smb_filename {
        uint32_t flags;
        SMB_STRUCT_STAT st;
        NTTIME twrp;
+
+       /*
+        * Internal file handle, O_PATH based if available,
+        * otherwise O_RDONLY as root.
+        */
+       struct files_struct *fsp;
+
+       /*
+        * Link between the struct smb_filename and the above
+        * fsp. fsp_link is a talloc child of the fsp. Ensures a possible
+        * talloc_free(fsp) resets the smb_fname->fsp pointer to NULL in
+        * the is fsp_link talloc destructor.
+        */
+       struct fsp_smb_fname_link *fsp_link;
 };
 
 /*
index b53c5e924339306d0b37af994b51796a68a4f6b1..7315d7fb75a93b7972b6855e8a2e622eca6e219a 100644 (file)
@@ -260,6 +260,299 @@ NTSTATUS open_internal_dirfsp(connection_struct *conn,
        return NT_STATUS_OK;
 }
 
+/*
+ * The "link" in the name doesn't imply link in the filesystem
+ * sense. It's a object that "links" together an fsp and an smb_fname
+ * and the link allocated as talloc child of an fsp.
+ *
+ * The link is created for fsps that open_smbfname_fsp() returns in
+ * smb_fname->fsp. When this fsp is freed by fsp_free() by some caller
+ * somewhere, the destructor fsp_smb_fname_link_destructor() on the link object
+ * will use the link to reset the reference in smb_fname->fsp that is about to
+ * go away.
+ *
+ * This prevents smb_fname_internal_fsp_destructor() from seeing dangling fsp
+ * pointers.
+ */
+
+struct fsp_smb_fname_link {
+       struct fsp_smb_fname_link **smb_fname_link;
+       struct files_struct **smb_fname_fsp;
+};
+
+static int fsp_smb_fname_link_destructor(struct fsp_smb_fname_link *link)
+{
+       if (link->smb_fname_link == NULL) {
+               return 0;
+       }
+
+       *link->smb_fname_link = NULL;
+       *link->smb_fname_fsp = NULL;
+       return 0;
+}
+
+static NTSTATUS fsp_smb_fname_link(struct files_struct *fsp,
+                                  struct fsp_smb_fname_link **smb_fname_link,
+                                  struct files_struct **smb_fname_fsp)
+{
+       struct fsp_smb_fname_link *link = NULL;
+
+       link = talloc_zero(fsp, struct fsp_smb_fname_link);
+       if (link == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       link->smb_fname_link = smb_fname_link;
+       link->smb_fname_fsp = smb_fname_fsp;
+       *smb_fname_link = link;
+
+       talloc_set_destructor(link, fsp_smb_fname_link_destructor);
+       return NT_STATUS_OK;
+}
+
+/*
+ * Free a link, carefully avoiding to trigger the link destructor
+ */
+static void destroy_fsp_smb_fname_link(struct fsp_smb_fname_link **_link)
+{
+       struct fsp_smb_fname_link *link = *_link;
+
+       if (link == NULL) {
+               return;
+       }
+       talloc_set_destructor(link, NULL);
+       TALLOC_FREE(link);
+       *_link = NULL;
+}
+
+/*
+ * Talloc destructor set on an smb_fname set by openat_pathref_fsp() used to
+ * close the embedded smb_fname->fsp.
+ */
+static int smb_fname_fsp_destructor(struct smb_filename *smb_fname)
+{
+       struct files_struct *fsp = smb_fname->fsp;
+       NTSTATUS status;
+
+       destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+
+       if (fsp == NULL) {
+               return 0;
+       }
+
+       if (fsp->base_fsp != NULL) {
+               status = fd_close(fsp->base_fsp);
+               SMB_ASSERT(NT_STATUS_IS_OK(status));
+               file_free(NULL, fsp->base_fsp);
+               fsp->base_fsp = NULL;
+       }
+
+       status = fd_close(fsp);
+       SMB_ASSERT(NT_STATUS_IS_OK(status));
+       file_free(NULL, fsp);
+       smb_fname->fsp = NULL;
+
+       return 0;
+}
+
+/*
+ * For proper streams support, we have to open the base_fsp for pathref
+ * fsp's as well.
+ */
+static NTSTATUS open_pathref_base_fsp(const struct files_struct *dirfsp,
+                                     struct files_struct *fsp)
+{
+       struct smb_filename *smb_fname_base = NULL;
+       NTSTATUS status;
+
+       smb_fname_base = synthetic_smb_fname(talloc_tos(),
+                                            fsp->fsp_name->base_name,
+                                            NULL,
+                                            NULL,
+                                            fsp->fsp_name->twrp,
+                                            fsp->fsp_name->flags);
+       if (smb_fname_base == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       status = openat_pathref_fsp(dirfsp, smb_fname_base);
+       if (!NT_STATUS_IS_OK(status)) {
+               TALLOC_FREE(smb_fname_base);
+               if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+                       DBG_DEBUG("Opening base file failed: %s\n",
+                                 nt_errstr(status));
+                       return status;
+               }
+               return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+       }
+
+       fsp->base_fsp = smb_fname_base->fsp;
+       smb_fname_base->fsp = NULL;
+       talloc_set_destructor(smb_fname_base->fsp_link, NULL);
+       talloc_set_destructor(smb_fname_base, NULL);
+       TALLOC_FREE(smb_fname_base);
+
+       return NT_STATUS_OK;
+}
+
+/*
+ * Open an internal O_PATH based fsp for smb_fname. If O_PATH is not
+ * available, open O_RDONLY as root. Both is done in fd_open() ->
+ * non_widelink_open(), triggered by setting fsp->fsp_flags.is_pathref to
+ * true.
+ */
+NTSTATUS openat_pathref_fsp(const struct files_struct *dirfsp,
+                           struct smb_filename *smb_fname)
+{
+       connection_struct *conn = dirfsp->conn;
+       struct smb_filename *full_fname = NULL;
+       bool file_existed = VALID_STAT(smb_fname->st);
+       struct files_struct *fsp = NULL;
+       NTSTATUS status;
+
+       DBG_DEBUG("smb_fname [%s]\n", smb_fname_str_dbg(smb_fname));
+
+       if (file_existed && S_ISLNK(smb_fname->st.st_ex_mode)) {
+               return NT_STATUS_STOPPED_ON_SYMLINK;
+       }
+
+       status = fsp_new(conn, conn, &fsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       GetTimeOfDay(&fsp->open_time);
+       fsp_set_gen_id(fsp);
+       ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+       fsp->fsp_flags.is_pathref = true;
+
+       full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+                                                 dirfsp,
+                                                 smb_fname);
+       if (full_fname == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto fail;
+       }
+
+       fsp->fsp_name = talloc_move(fsp, &full_fname);
+
+       if (is_ntfs_default_stream_smb_fname(fsp->fsp_name)) {
+               fsp->fsp_name->stream_name = NULL;
+       }
+
+       status = file_name_hash(fsp->conn,
+                               smb_fname_str_dbg(fsp->fsp_name),
+                               &fsp->name_hash);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+           && is_ntfs_stream_smb_fname(fsp->fsp_name))
+       {
+               status = open_pathref_base_fsp(dirfsp, fsp);
+               if (!NT_STATUS_IS_OK(status)) {
+                       goto fail;
+               }
+       }
+
+       status = fd_openat(dirfsp, smb_fname, fsp, O_RDONLY, 0);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_DEBUG("Could not open fd for [%s]: %s\n",
+                         fsp_str_dbg(fsp),
+                         nt_errstr(status));
+
+               if (fsp->base_fsp != NULL) {
+                       file_free(NULL, fsp->base_fsp);
+                       fsp->base_fsp = NULL;
+               }
+               fd_close(fsp);
+               file_free(NULL, fsp);
+               fsp = NULL;
+
+               if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) ||
+                   NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND))
+               {
+                       /*
+                        * streams_xattr return NT_STATUS_NOT_FOUND for
+                        * opens of not yet exisiting streams.
+                        *
+                        * ELOOP maps to NT_STATUS_OBJECT_PATH_NOT_FOUND
+                        * and this will result from a open request from
+                        * a POSIX client on a symlink.
+                        *
+                        * NT_STATUS_OBJECT_NAME_NOT_FOUND is the simple
+                        * ENOENT case.
+                        */
+                       status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+               }
+               if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+                       goto fail;
+               }
+
+               return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+       }
+
+       if (file_existed &&
+           !check_same_dev_ino(&smb_fname->st, &fsp->fsp_name->st))
+       {
+               DBG_DEBUG("file [%s] - dev/ino mismatch. "
+                         "Old (dev=%ju, ino=%ju). "
+                         "New (dev=%ju, ino=%ju).\n",
+                         smb_fname_str_dbg(smb_fname),
+                         (uintmax_t)smb_fname->st.st_ex_dev,
+                         (uintmax_t)smb_fname->st.st_ex_ino,
+                         (uintmax_t)fsp->fsp_name->st.st_ex_dev,
+                         (uintmax_t)fsp->fsp_name->st.st_ex_ino);
+               status = NT_STATUS_ACCESS_DENIED;
+               goto fail;
+       }
+
+       fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+       if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+               fsp->fsp_flags.is_directory = true;
+       }
+
+       fsp->fsp_name->fsp = fsp;
+       smb_fname->fsp = fsp;
+
+       status = fsp_smb_fname_link(fsp,
+                                   &smb_fname->fsp_link,
+                                   &smb_fname->fsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       status = fsp_smb_fname_link(fsp,
+                                   &fsp->fsp_name->fsp_link,
+                                   &fsp->fsp_name->fsp);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
+       DBG_DEBUG("fsp [%s]: OK\n", fsp_str_dbg(fsp));
+
+       talloc_set_destructor(smb_fname, smb_fname_fsp_destructor);
+       return NT_STATUS_OK;
+
+fail:
+       DBG_DEBUG("Opening pathref for [%s] failed: %s\n",
+                 smb_fname_str_dbg(smb_fname),
+                 nt_errstr(status));
+
+       if (fsp == NULL) {
+               return status;
+       }
+       if (fsp->base_fsp != NULL) {
+               file_free(NULL, fsp->base_fsp);
+               fsp->base_fsp = NULL;
+       }
+       fd_close(fsp);
+       file_free(NULL, fsp);
+       return status;
+}
+
 /****************************************************************************
  Close all open files for a connection.
 ****************************************************************************/
index 4d7f0488ab4e053763bedfa2d682c51d2f4b6ae9..b37c6e36bde69a89a878f8ebed2a8534b04e19e8 100644 (file)
@@ -455,6 +455,9 @@ NTSTATUS open_internal_dirfsp(connection_struct *conn,
                              int open_flags,
                              struct files_struct **_fsp);
 
+NTSTATUS openat_pathref_fsp(const struct files_struct *dirfsp,
+                           struct smb_filename *smb_fname);
+
 /* The following definitions come from smbd/ipc.c  */
 
 NTSTATUS nt_status_np_pipe(NTSTATUS status);