lkml.org 
[lkml]   [2022]   [Feb]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
From
Date
SubjectRe: race between vfs_rename and do_linkat (mv and link)
On Wed, 16 Feb 2022 at 14:18, Xavier Roche <xavier.roche@algolia.com> wrote:
>
> On Wed, Feb 16, 2022 at 11:28:18AM +0100, Miklos Szeredi wrote:
> > Something like this:
> > diff --git a/fs/namei.c b/fs/namei.c
> > index 3f1829b3ab5b..dd6908cee49d 100644
>
> Tested-by: Xavier Roche <xavier.roche@algolia.com>
>
> I confirm this completely fixes at least the specific race. Tested on a
> unpatched and then patched 5.16.5, with the trivial bash test, and then
> with a C++ torture test.

Thanks for testing.

One issue with the patch is nesting of lock_rename() calls in stacked
fs (rwsem is not allowed to recurse even for read locks).

So the lock needs to be per-sb, but then do_linkat() becomes more
complex due to not being able to use the filename_create() helper.
But it's still much simpler than the special lookup loop described by
Al.

Thanks,
Miklos
>
> Before:
> -------
>
> $ time ./linkbug
> Failed after 4 with No such file or directory
>
> real 0m0,004s
> user 0m0,000s
> sys 0m0,004s
>
> After:
> ------
>
> (no error after ten minutes of running the program)
>
> Torture test program:
> ---------------------
>
> /* Linux rename vs. linkat race condition.
> * Rationale: both (1) moving a file to a target and (2) linking the target to a file in parallel leads to a race
> * on Linux kernel.
> * Sample file courtesy of Xavier Grand at Algolia
> * g++ -pthread linkbug.c -o linkbug
> */
>
> #include <thread>
> #include <unistd.h>
> #include <assert.h>
> #include <sys/types.h>
> #include <sys/stat.h>
> #include <fcntl.h>
> #include <iostream>
> #include <string.h>
>
> static const char* producedDir = "/tmp";
> static const char* producedFile = "/tmp/file.txt";
> static const char* producedTmpFile = "/tmp/file.txt.tmp";
> static const char* producedThreadDir = "/tmp/tmp";
> static const char* producedThreadFile = "/tmp/file.txt.tmp.2";
>
> bool createFile(const char* path)
> {
> const int fdOut = open(path,
> O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | O_CLOEXEC,
> S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
> assert(fdOut != -1);
> assert(write(fdOut, "Foo", 4) == 4);
> assert(close(fdOut) == 0);
> return true;
> }
>
> void func()
> {
> int nbSuccess = 0;
> // Loop producedThread a hardlink of the file
> while (true) {
> if (link(producedFile, producedThreadFile) != 0) {
> std::cout << "Failed after " << nbSuccess << " with " << strerror(errno) << std::endl;
> exit(EXIT_FAILURE);
> } else {
> nbSuccess++;
> }
> assert(unlink(producedThreadFile) == 0);
> }
> }
>
> int main()
> {
> // Setup env
> unlink(producedTmpFile);
> unlink(producedFile);
> unlink(producedThreadFile);
> createFile(producedFile);
> mkdir(producedThreadDir, 0777);
>
> // Async thread doing a hardlink and moving it
> std::thread t(func);
> // Loop creating a .tmp and moving it
> while (true) {
> assert(createFile(producedTmpFile));
> assert(rename(producedTmpFile, producedFile) == 0);
> }
> return 0;
> }

\
 
 \ /
  Last update: 2022-02-16 14:38    [W:0.238 / U:0.068 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site