]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s3:smbd: let mkdir_internal() try VFS_RENAME_HOW_NO_REPLACE first
authorStefan Metzmacher <metze@samba.org>
Wed, 7 Aug 2024 15:01:53 +0000 (17:01 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 21 Aug 2024 08:02:30 +0000 (08:02 +0000)
With renameat2(RENAME_NOREPLACE) being available
it's even better, as we don't even have the short
window where the incomplete directory is visible
to others.

The flow will be this:

tmp_name = ".::TMPNAME:D:$PID:client_name"
mkdirat(tmp_name, mode=client_mode);
prepare_acls(tmp_name);
renameat2(tmp_name, client_name, NOREPLACE);
if (EEXIST) {
   unlinkat(tmp_name);
   return EEXIST;
}
if (EINVAL) {
   /* fallback if NOREPLACE is not supported */
   mkdirat(client_name, mode=0);
   if (EEXIST) {
      unlinkat(tmp_name);
      return EEXIST;
   }
   renameat(tmp_name, client_name);
}

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15693

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
source3/smbd/open.c

index cba43d1a1cd18084c52b93fc7246b308e9df7f7c..a1c1c259e5c0f14d17b010d37d493cb9eb238958 100644 (file)
@@ -4681,7 +4681,7 @@ static NTSTATUS mkdir_internal(connection_struct *conn,
        struct server_id_buf idbuf;
        char *idstr = server_id_str_buf_unique_ex(id, '%', &idbuf);
        struct vfs_open_how how = { .flags = O_RDONLY|O_DIRECTORY, };
-       struct vfs_rename_how rhow = { .flags = 0, };
+       struct vfs_rename_how rhow = { .flags = VFS_RENAME_HOW_NO_REPLACE, };
        int ret;
 
        if (!CAN_WRITE(conn) || (access_mask & ~(conn->share_access))) {
@@ -4896,7 +4896,27 @@ mkdir_first:
         */
        tmp_atname->st = smb_dname->st;
 
-       {
+       /*
+        * We first try VFS_RENAME_HOW_NO_REPLACE,
+        * if it's implemented in the kernel,
+        * we'll always get EEXIST if the target
+        * exist, as it's handled at the linux vfs
+        * layer. But if it doesn't exist we
+        * can still get EINVAL if the actual
+        * filesystem doesn't support RENAME_NOREPLACE.
+        *
+        * If the kernel doesn't support rename2()
+        * we get EINVAL instead of ENOSYS (this
+        * is mapped in the libreplace replacement
+        * (as well as the glibc replacement).
+        */
+       ret = SMB_VFS_RENAMEAT(conn,
+                              parent_dir_fname->fsp,
+                              tmp_atname,
+                              parent_dir_fname->fsp,
+                              smb_fname_atname,
+                              &rhow);
+       if (ret == -1 && errno == EINVAL) {
                /*
                 * This is the strategie we use without having
                 * renameat2(RENAME_NOREPLACE):
@@ -4916,6 +4936,8 @@ mkdir_first:
                 * the incomplete directory, which has a mode of 0.
                 */
 
+               rhow.flags &= ~VFS_RENAME_HOW_NO_REPLACE;
+
                DBG_DEBUG("MKDIRAT/RENAMEAT '%s' -> '%s'\n",
                          tmp_dname, orig_dname);