[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20230720060657.GB2607@sol.localdomain>
Date: Wed, 19 Jul 2023 23:06:57 -0700
From: Eric Biggers <ebiggers@...nel.org>
To: Gabriel Krisman Bertazi <krisman@...e.de>
Cc: viro@...iv.linux.org.uk, brauner@...nel.org, tytso@....edu,
jaegeuk@...nel.org, linux-fsdevel@...r.kernel.org,
linux-ext4@...r.kernel.org, linux-f2fs-devel@...ts.sourceforge.net,
Gabriel Krisman Bertazi <krisman@...labora.com>
Subject: Re: [PATCH v3 3/7] libfs: Validate negative dentries in
case-insensitive directories
On Wed, Jul 19, 2023 at 06:19:14PM -0400, Gabriel Krisman Bertazi wrote:
> +static inline int generic_ci_d_revalidate(struct dentry *dentry,
> + const struct qstr *name,
> + unsigned int flags)
This shouldn't be 'inline', since it can't be inlined into anywhere anyway.
> + if (dir && needs_casefold(dir)) {
> + /*
> + * Filesystems will call into d_revalidate without
> + * setting LOOKUP_ flags even for file creation(see
> + * lookup_one* variants). Reject negative dentries in
> + * this case, since we can't know for sure it won't be
> + * used for creation.
> + */
> + if (!flags)
> + return 0;
> +
> + /*
> + * Negative dentries created prior to turning the
> + * directory case-insensitive cannot be trusted, since
> + * they don't ensure any possible case version of the
> + * filename doesn't exist.
> + */
> + if (!d_is_casefold_lookup(dentry))
> + return 0;
> +
> + if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)) {
> + /*
> + * ->d_name won't change from under us in the
> + * creation path only, since d_revalidate during
> + * creation and renames is always called with
> + * the parent inode locked. It isn't the case
> + * for all lookup callpaths, so ->d_name must
> + * not be touched outside
> + * (LOOKUP_CREATE|LOOKUP_RENAME_TARGET) context.
> + */
> + if (dentry->d_name.len != name->len ||
> + memcmp(dentry->d_name.name, name->name, name->len))
> + return 0;
> + }
> + }
This would be easier to follow if the '!flags' and 'flags & (LOOKUP_CREATE |
LOOKUP_RENAME_TARGET)' sections were adjacent to each other. They group
together logically, since they both deal with the following problem: "when the
lookup might be for creation, invalidate the negative dentry if it might not be
a case-sensitive match". (And it would help if there was a comment explaining
that problem.) The d_is_casefold_lookup() check solves a different problem.
I'm also having trouble understanding exactly when ->d_name is stable here.
AFAICS, unfortunately the VFS has an edge case where a dentry can be moved
without its parent's ->i_rwsem being held. It happens when a subdirectory is
"found" under multiple names. The VFS doesn't support directory hard links, so
if it finds a second link to a directory, it just moves the whole dentry tree to
the new location. This can happen if a filesystem image is corrupted and
contains directory hard links. Coincidentally, it can also happen in an
encrypted directory due to the no-key name => normal name transition...
But, negative dentries are never moved, at all. (__d_move() even WARNs if you
ask it to move a negative dentry.) That feels like that would make everything
else irrelevant here, though your patchset doesn't mention this. I suppose the
problem would be what if the dentry is made positive concurrently.
I'm struggling to find an ideal solution here. Maybe ->d_lock should just be
taken for the name comparison. That is legal here, and it reliably synchronizes
with the dentry being moved, without having to consider anything else.
So, the following is probably what I'd do. I'd be interested to hear your
thoughts, though:
/*
* A negative dentry that was created before the
* directory became case-insensitive can't be trusted,
* since it doesn't ensure any possible case version of
* the filename doesn't exist.
*/
if (!d_is_casefold_lookup(dentry))
return 0;
/*
* If the lookup is for creation, then a negative dentry
* can only be reused if it's a case-sensitive match,
* not just a case-insensitive one. This is required to
* provide case-preserving semantics.
*
* In some cases (lookup_one_*()), the lookup intent is
* unknown, resulting in flags==0 here. So we have to
* assume that flags==0 is potentially a creation.
*/
if (flags == 0 ||
(flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))) {
bool match;
spin_lock(&dentry->d_lock);
match = (dentry->d_name.len == name->len &&
memcmp(dentry->d_name.name, name->name,
name->len) == 0);
spin_unlock(&dentry->d_lock);
if (!match)
return 0;
}
Powered by blists - more mailing lists