Jinoh Kang (@iamahuman) commented about server/fd.c:
+ } + + /* same dentry means a no-op */ + if (!strcmp( srcname, dstname )) goto ret; + + /* This is more complicated now, because in case of a casefold (+F) directory, the destination may very well be the same dentry, + * even if the name doesn't match (if it differs just by case), in which case unlinking it is wrong and can be dangerous. Instead, + * we first rename the source to a temporary filename in the same directory. If this is a casefold dir, this will also remove the + * destination, otherwise the destination still exists. We then create a hardlink from the destination to our temporary name, and + * finally, unlink the temporary. This still works if the directory is case sensitive, so it's not a problem in either case. */ + tmp_value += (current_time >> 16) + current_time; + for (i = 0; i < 0x8000; i++, tmp_value += 7777) + { + snprintf( tmpname, sizeof(tmpname), tmpname_fmt, tmp_value ); + if (fstatat( dirfd, tmpname, &st, 0 )) /* tmpname doesn't exist */ + break; If multiple wineservers were to attempt to rename the same hardlink, they may end up allocating the *same* tempname and race against each other.
This fixes the race without increasing the number of syscalls (untested): ```suggestion:-1+0 if (!is_dir) { /* to avoid TOCTTOU, probe existence of tmpname and link at the same time */ if (linkat( dirfd, srcname, dirfd, tmpname )) continue; if (unlinkat( dirfd, srcname )) { file_set_error(); unlinkat( dirfd, tmpname ); goto ret; } break; } /* directories can't have hardlinks, so just rename it to tmpname */ else if (!renameat( dirfd, srcname, dirfd, tmpname )) break; ``` (And remove the renameat srcname,tmpname below) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/6855#note_97918