From: Stefan Metzmacher Date: Wed, 7 Aug 2024 15:01:53 +0000 (+0200) Subject: s3:smbd: let mkdir_internal() try VFS_RENAME_HOW_NO_REPLACE first X-Git-Tag: tdb-1.4.13~1310 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1bacaae5261b157f0dc4efb68fdc82d4cf387eb9;p=thirdparty%2Fsamba.git s3:smbd: let mkdir_internal() try VFS_RENAME_HOW_NO_REPLACE first 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 Reviewed-by: Ralph Boehme --- diff --git a/source3/smbd/open.c b/source3/smbd/open.c index cba43d1a1cd..a1c1c259e5c 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -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);