From: "NeilBrown" <neilb@suse.de>
To: "Al Viro" <viro@zeniv.linux.org.uk>
Cc: "Miklos Szeredi" <mszeredi@redhat.com>,
"Xavier Roche" <xavier.roche@algolia.com>,
linux-fsdevel@vger.kernel.org
Subject: Re: [PATCH v2] vfs: fix link vs. rename race
Date: Wed, 14 Sep 2022 10:14:44 +1000 [thread overview]
Message-ID: <166311448437.20483.2299581036245030695@noble.neil.brown.name> (raw)
In-Reply-To: <YyAX2adCGec95qXn@ZenIV>
On Tue, 13 Sep 2022, Al Viro wrote:
> On Tue, Sep 13, 2022 at 06:20:10AM +0100, Al Viro wrote:
>
> > > Alternately, lock the "from" directory as well as the "to" directory.
> > > That would mean using lock_rename() and generally copying the structure
> > > of do_renameat2() into do_linkat()
> >
> > Ever done cp -al? Cross-directory renames are relatively rare; cross-directory
> > links can be fairly heavy on some payloads, and you'll get ->s_vfs_rename_mutex
> > held a _lot_.
> >
> > > I wonder if you could get a similar race trying to create a file in
> > > (empty directory) /tmp/foo while /tmp/bar was being renamed over it.
> >
> > Neil, no offense, but... if you have plans regarding changes in
> > directory locking, you might consider reading through the file called
> > Documentation/filesystems/directory-locking.rst
> >
> > Occasionally documentation is where one could expect to find it...
>
> ... and that "..." above should've been ";-)" - it was not intended as
> a dig, especially since locking in that area has subtle and badly
> underdocumented parts (in particular, anything related to fh_to_dentry(),
> rules regarding the stability of ->d_name and ->d_parent, mount vs. dentry
> invalidation and too many other things), but the basic stuff like that
> is actually covered.
>
> FWIW, the answer to your question is that the victim of overwriting
> rename is held locked by caller of ->rename(); combined with the lock
> held on directory by anyone who modifies it that prevents the race
> you are asking about.
>
> See
> if (!is_dir || (flags & RENAME_EXCHANGE))
> lock_two_nondirectories(source, target);
> else if (target)
> inode_lock(target);
> in vfs_rename().
>
I don't think those locks address the race I was thinking of.
Suppose a /tmp/shared-dir is an empty directory and one thread runs
rename("/tmp/tmp-dir", "/tmp/shared-dir")
while another thread runs
open("/tmp/shared-dir/Afile", O_CREAT | O_WRONLY)
If the first wins the file is created in what was tmp-dir.
If the second wins, the rename fails because shared-dir isn't empty.
But they can race. The open completes the lookup of /tmp/share-dir and
holds the dentry, but the rename succeeds with inode_lock(target) in the
code fragment you provided above before the open() can get the lock.
By the time open() does get the lock, the dentry it holds will be marked
S_DEAD and the __lookup_hash() will fail.
So the open() returns -ENOENT - unexpected.
Test code below, based on the test code for the link race.
I wonder if S_DEAD should result in -ESTALE rather than -ENOENT.
That would cause the various retry_estale() loops to retry the whole
operation. I suspect we'd actually need more subtlety than just that
simple change, but it might be worth exploring.
NeilBrown
/* Linux rename vs. create race condition.
* Rationale: both (1) moving a directory to an empty target and
* (2) creating a file in the target can race.
* The create should always succeed, but there should always be
* directory there, either old or new.
* The rename might fail if the target isn't empty.
* Sample file courtesy of Xavier Grand at Algolia
* g++ -pthread createbug.c -o createbug
*/
#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/shared-dir";
static const char* producedTmpDir = "/tmp/new-dir";
static const char* producedThreadFile = "/tmp/shared-dir/Afile";
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);
if (fdOut < 0)
return false;
assert(write(fdOut, "Foo", 4) == 4);
assert(close(fdOut) == 0);
return true;
}
void func()
{
int nbSuccess = 0;
// Loop producedThread create file in dir
while (true) {
if (createFile(producedThreadFile) != true) {
std::cout << "Failed after " << nbSuccess << " with " << strerror(errno) << std::endl;
exit(EXIT_FAILURE);
} else {
nbSuccess++;
}
unlink(producedThreadFile);
}
}
int main()
{
// Setup env
rmdir(producedTmpDir);
unlink(producedThreadFile);
rmdir(producedDir);
mkdir(producedDir, 0777);
// Async thread doing a hardlink and moving it
std::thread t(func);
// Loop creating a .tmp and moving it
while (true) {
assert(mkdir(producedTmpDir, 0777) == 0);
while (rename(producedTmpDir, producedDir) != 0)
unlink(producedThreadFile);
}
return 0;
}
next prev parent reply other threads:[~2022-09-14 0:14 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-02-21 8:20 [PATCH v2] vfs: fix link vs. rename race Miklos Szeredi
2022-02-21 8:56 ` Xavier Roche
2022-09-13 2:04 ` Al Viro
2022-09-13 4:29 ` Al Viro
2022-09-13 8:02 ` Amir Goldstein
2022-09-13 10:03 ` Miklos Szeredi
2022-09-13 4:41 ` NeilBrown
2022-09-13 5:20 ` Al Viro
2022-09-13 5:40 ` Al Viro
2022-09-14 0:14 ` NeilBrown [this message]
2022-09-14 1:30 ` Al Viro
2022-09-13 23:52 ` NeilBrown
2022-09-14 0:13 ` Al Viro
2022-09-16 6:13 ` [PATCH RFC] VFS: lock source directory for link to avoid " NeilBrown
2022-09-16 6:28 ` Miklos Szeredi
2022-09-16 6:45 ` NeilBrown
2022-09-16 6:49 ` Al Viro
2022-09-16 14:32 ` Amir Goldstein
2022-09-19 8:28 ` Christian Brauner
2022-09-19 22:56 ` NeilBrown
2022-09-23 3:02 ` [VFS] 3fb4ec6faa: ltp.linkat02.fail kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=166311448437.20483.2299581036245030695@noble.neil.brown.name \
--to=neilb@suse.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=mszeredi@redhat.com \
--cc=viro@zeniv.linux.org.uk \
--cc=xavier.roche@algolia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).