mbox series

[v3,0/5] Store overlay real upper file in ovl_file

Message ID 20241007141925.327055-1-amir73il@gmail.com (mailing list archive)
Headers show
Series Store overlay real upper file in ovl_file | expand

Message

Amir Goldstein Oct. 7, 2024, 2:19 p.m. UTC
Hi all,

This is v3 of the code to avoid temporary backing file opens in
overlayfs, taking into account Al's and Miklos' comments on v2 [1].

If no further comments, this is going for overlayfs-next.

Thanks,
Amir.

Changes since v2:
- Simplifications to flow (Al)
- Loose backing_file stash in favor of ovl_file (Miklos)

Changes since v1:
- Use helpers ovl_real_file() and ovl_upper_file() to express that
  ovl_real_file() cannot return NULL
- Fix readability and bug is code to select and store stashed upperfile

[1] https://lore.kernel.org/linux-unionfs/20241006082359.263755-1-amir73il@gmail.com/

Amir Goldstein (5):
  ovl: do not open non-data lower file for fsync
  ovl: allocate a container struct ovl_file for ovl private context
  ovl: store upper real file in ovl_file struct
  ovl: convert ovl_real_fdget_path() callers to ovl_real_file_path()
  ovl: convert ovl_real_fdget() callers to ovl_real_file()

 fs/overlayfs/file.c | 301 +++++++++++++++++++++++++-------------------
 1 file changed, 173 insertions(+), 128 deletions(-)

Comments

Al Viro Oct. 17, 2024, 4:52 a.m. UTC | #1
On Mon, Oct 07, 2024 at 04:19:20PM +0200, Amir Goldstein wrote:
> Hi all,
> 
> This is v3 of the code to avoid temporary backing file opens in
> overlayfs, taking into account Al's and Miklos' comments on v2 [1].
> 
> If no further comments, this is going for overlayfs-next.

BTW, looking through the vicinity of that stuff:

int ovl_cleanup(struct ovl_fs *ofs, struct inode *wdir, struct dentry *wdentry)
{
        int err;

        dget(wdentry);
        if (d_is_dir(wdentry))
                err = ovl_do_rmdir(ofs, wdir, wdentry);
        else
                err = ovl_do_unlink(ofs, wdir, wdentry);
        dput(wdentry);

        if (err) {
                pr_err("cleanup of '%pd2' failed (%i)\n",
                       wdentry, err);
        }

        return err;
}

What the hell are those dget()/dput() doing there?  Not to mention an
access after dput(), both vfs_rmdir() and vfs_unlink() expect the
reference to dentry argument to be held by the caller and leave it
for the caller to dispose of.  What am I missing here?
Amir Goldstein Oct. 17, 2024, 8:18 a.m. UTC | #2
On Thu, Oct 17, 2024 at 6:52 AM Al Viro <viro@zeniv.linux.org.uk> wrote:
>
> On Mon, Oct 07, 2024 at 04:19:20PM +0200, Amir Goldstein wrote:
> > Hi all,
> >
> > This is v3 of the code to avoid temporary backing file opens in
> > overlayfs, taking into account Al's and Miklos' comments on v2 [1].
> >
> > If no further comments, this is going for overlayfs-next.
>
> BTW, looking through the vicinity of that stuff:
>
> int ovl_cleanup(struct ovl_fs *ofs, struct inode *wdir, struct dentry *wdentry)
> {
>         int err;
>
>         dget(wdentry);
>         if (d_is_dir(wdentry))
>                 err = ovl_do_rmdir(ofs, wdir, wdentry);
>         else
>                 err = ovl_do_unlink(ofs, wdir, wdentry);
>         dput(wdentry);
>
>         if (err) {
>                 pr_err("cleanup of '%pd2' failed (%i)\n",
>                        wdentry, err);
>         }
>
>         return err;
> }
>
> What the hell are those dget()/dput() doing there?  Not to mention an
> access after dput(), both vfs_rmdir() and vfs_unlink() expect the
> reference to dentry argument to be held by the caller and leave it
> for the caller to dispose of.  What am I missing here?

It has been like that since the first upstream version.
My guess is that it is an attempt to avoid turning wdentry
into a negative dentry, which is not expected to be useful in
ovl_clear_empty() situations, but this is just a guess.

Miklos?

Thanks,
Amir.
Miklos Szeredi Oct. 17, 2024, 1:52 p.m. UTC | #3
On Thu, 17 Oct 2024 at 10:18, Amir Goldstein <amir73il@gmail.com> wrote:

> It has been like that since the first upstream version.
> My guess is that it is an attempt to avoid turning wdentry
> into a negative dentry, which is not expected to be useful in
> ovl_clear_empty() situations, but this is just a guess.

Yes.  This was discussed in a private thread before merging overlayfs upstream.

Copying relevant parts here:

----------
From: Al Viro <viro@zeniv.linux.org.uk>
Date: Sat, 18 Oct 2014 at 10:18
To: Miklos Szeredi <miklos@szeredi.hu>
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
<mszeredi@suse.cz>, David Howells <dhowells@redhat.com>, Sage Weil
<sage@inktank.com>


[Cc to Sage due to interesting ceph bit that has shown up from grepping -
see the very end]

On Sat, Oct 18, 2014 at 06:01:53AM +0100, Al Viro wrote:

First of all, I've just fixed a dumb braino in ovl_clear_empty(); assignment
to upper needed to be moved up to the added test.  Force-pushed to the same
branch - vfs.git#ovl-experimental.

> As for the "what filesystems are we OK with", I wonder if looking into the
> sucker's ->s_d_op (or ->d_op of root of lower tree, for that matter) would
> be a good approximation.  I really think that ->d_{weak_,}revalidate() in
> there is complete no-go, ditto for ->d_manage() and ->d_automount() and
> I would consider ->d_compare() or ->d_hash() as a cause to be _very_ cautious.
>
> Alternatively, we could just go ahead and add FS_OK_FOR_OVERLAY_LOWER into
> fs type flags and mark the obvious good ones.  It's not _that_ much work.
>
> I'd still like to hear details on the plans re d_path(); I don't consider
> that a deal-breaker, but we'd better have some clear idea of what we are
> getting into.

BTW, why on the Earth are you pinning that ->__upperdentry twice?  The
comment about d_delete() makes no sense whatsoever - anything other than
overlayfs itself would have to grab a reference to call that d_delete(),
which would give you refcount greater than 1 automatically.  So it would
have to be overlayfs passing that thing to d_delete() or something that
would call it, right?  Now, d_delete() itself isn't called there at all.
Which leaves passing the sucker to something outside that would call
d_delete().  Now, what would it be?

Here's the full list of d_delete() callers:
arch/s390/hypfs/inode.c:82:     d_delete(dentry);
drivers/infiniband/hw/ipath/ipath_fs.c:318:     d_delete(dir);
drivers/infiniband/hw/qib/qib_fs.c:512: d_delete(dir);
drivers/usb/gadget/function/f_fs.c:1560:
d_delete(epfile->dentry);
drivers/usb/gadget/legacy/inode.c:1611:         d_delete (dentry);
fs/btrfs/ioctl.c:2507:          d_delete(dentry);
fs/ceph/dir.c:893:              d_delete(dentry);
fs/ceph/inode.c:1114:                           d_delete(dn);
fs/ceph/inode.c:1223:                           d_delete(dn);
fs/ceph/inode.c:1395:                   d_delete(dn);
fs/configfs/dir.c:643:          d_delete(child);
fs/configfs/dir.c:834:                  d_delete(dentry);
fs/configfs/dir.c:880:                  d_delete(dentry);
fs/configfs/dir.c:1475:                         d_delete(new_dentry);
fs/configfs/dir.c:1721: d_delete(dentry);
fs/debugfs/inode.c:483:                         d_delete(dentry);
fs/devpts/inode.c:666:  d_delete(dentry);
fs/efivarfs/file.c:50:          d_delete(file->f_dentry);
fs/fuse/dir.c:1061:                     d_delete(entry);
fs/namei.c:3586:                d_delete(dentry);
fs/namei.c:3702:                d_delete(dentry);
fs/nfs/dir.c:1760:              d_delete(dentry);
fs/nfs/nfs4proc.c:2231:                         d_delete(dentry);
fs/ocfs2/dlmglue.c:3752:                d_delete(dentry);
fs/reiserfs/xattr.c:95:         d_delete(dentry);
fs/reiserfs/xattr.c:111:                d_delete(dentry);
net/sunrpc/rpc_pipe.c:607:      d_delete(dentry);
net/sunrpc/rpc_pipe.c:634:      d_delete(dentry);
security/selinux/selinuxfs.c:1212:                      d_delete(d);

We are talking about the *upper* layer; that excludes most of those
guys.  At the very least, you want that fs to support rename and xattrs.
So hypfs, infinibarf ones, gadgetfs, configfs, debugfs, devpts, efivarfs,
sunrpc and selinuxfs are right out.  Moreover, all of those are not in
the codepaths reachable from overlayfs - all of that is removal of
object driven by external event.  And we end up using a reference other
than what overlayfs would be holding.  The same goes for reiserfs
xattr code (it calls d_delete() for references it has acquired itself)
and for ocfs2.  NFS is also not an option for upper layer, according to
overlayfs docs.  FUSE is in the same boat as ocfs2 and reiserfs - we acquire
the reference by d_lookup() in the same function.  The same goes for
btrfs caller (s/d_lookup/lookup_one_len/), not to mention that this code
won't be called by overlayfs.  What's left?

fs/ceph/dir.c:893:              d_delete(dentry);
ceph_unlink().

fs/ceph/inode.c:1114:                           d_delete(dn);
ceph_fill_trace(), dn comes from d_lookup().  Not an issue.

fs/ceph/inode.c:1395:                   d_delete(dn);
ceph_readdir_prepopulate(), dn comes from d_lookup().  Not an issue.

fs/ceph/inode.c:1223:                           d_delete(dn);
ceph_fill_trace(), again.  Hell knows - it's really hard to read ;-/

fs/namei.c:3586:                d_delete(dentry);
vfs_rmdir()

fs/namei.c:3702:                d_delete(dentry);
vfs_unlink()

Now, ceph_unlink() can come only from vfs_unlink().  So we are down to
the following: victim of vfs_unlink(), victim of vfs_rmdir(), _maybe_
something strange coming from that ceph_fill_trace() callsite.

We definitely do have vfs_unlink() and vfs_rmdir() calls in overlayfs.
Not many, though - there's ovl_remove_upper(), calling them directly,
and there's ovl_cleanup(), calling them via ovl_do_{unlink,rmdir}()
wrappers.  For one thing, we could do dget()/dput() in both of those guys.
However, looking at the callers of ovl_cleanup() shows that with two
exceptions we have already grabbed/dropped dentry around the callsite.
Exceptions are ovl_clear_empty() and ovl_remove_and_whiteout().
Could as well put that dget()/dput() in those two, rather than in
ovl_clear_empty()...

IOW, modulo that ceph thing we could trivially avoid that double reference
to ->__upperdentry, just by doing a temporary dget()/dput() in a few
places in fs/overlayfs/dir.c.  Objections?  A bunch of code becomes simpler
that way, IMO...

Question to Sage: what's that d_delete() in ceph_fill_trace() about?
It's this bit:
                /* null dentry? */
                if (!rinfo->head->is_target) {
                        dout("fill_trace null dentry\n");
                        if (dn->d_inode) {
                                dout("d_delete %p\n", dn);
                                d_delete(dn);
                        } else {
                                dout("d_instantiate %p NULL\n", dn);
                                d_instantiate(dn, NULL);
                                if (have_lease && d_unhashed(dn))
                                        d_rehash(dn);
                                update_dentry_lease(dn, rinfo->dlease,
                                                    session,
                                                    req->r_request_started);
                        }
                        goto done;
                }
What codepaths could lead us there and where could that dentry have come
from?  Overlayfs aside, the things can get rather interesting if it could,
e.g. turn out to be an existing mountpoint...


----------
From: David Howells <dhowells@redhat.com>
Date: Sun, 19 Oct 2014 at 11:32
To: Al Viro <viro@zeniv.linux.org.uk>
Cc: <dhowells@redhat.com>, Miklos Szeredi <miklos@szeredi.hu>, Linus
Torvalds <torvalds@linux-foundation.org>, <mszeredi@suse.cz>


Al Viro <viro@ZenIV.linux.org.uk> wrote:

> David, could you give a braindump on selinux issues?  I hadn't watched your
> conversation with Ian closely, so I'd rather avoid doing second-hand
> retelling...

The problem boils down to this: When they fall through from the top layer to a
lower source layer, Overlayfs and unionmount both set up struct file to point
directly to the file in the lower layer.  This is then passed to various
security_xxx() functions.

For labelled-inode-based LSM's this means they see the label on the lower
inode, not the label on the overlay/union inode - indeed in unionmount, there
*is* no upper/union inode.

Overlayfs is more complicated than unionmount in that there are three layers
and it falls through from the overlay layer to the upper layer (ie. the change
stash) too.  In this case also the overlay inode is unavailable to the LSM.

Unionmount mitigates the lower-layer label problem by pointing file->f_path at
the union dentry and file->f_inode at the lower inode (and
file->f_path->dentry->d_fallthru at the lower dentry).

Further, unionmount has no upper layer problem since the changes are stored in
the union layer itself.


Having discussed this with various people in the context of docker, a tentative
consensus has been reached:

 (1) The docker source tree (ie. the lower layer) will all be under a single
     label.

 (2) The docker root (ie. the overlay/union layer) will all be under a single,
     but different label set on the overlay mount (and each docker root may be
     under its own label).

 (3) Inodes in the overlayfs upper layer will be given the overlay label.

 (4) A security_copy_up() operation will be provided to set the label on the
     upper inode when it is created.

 (5) A security_copy_up_xattr() operation will be provided to vet (and maybe
     modify) each xattr as it is copied up.

 (6) An extra label slot will be placed in struct file_security_struct to hold
     the overlay label.

 (7) security_file_open() will need to be given both the overlay and lower
     dentries.

     For overlayfs, the way this probably should be done is file->f_path should
     be set to point to the overlay dentry (thus getting /proc right) and
     file->f_inode to the lower file and make use of d_fallthru in the overlay
     dentry in common with unionmount.

 (8) When the lower file is accessed, both the lower and overlay labels should
     be checked and audited.

 (9) When the upper file is accessed, only the overlay label needs to be
     checked and audited.

David


----------
From: Al Viro <viro@zeniv.linux.org.uk>
Date: Mon, 20 Oct 2014 at 17:47
To: David Howells <dhowells@redhat.com>
Cc: Miklos Szeredi <miklos@szeredi.hu>, Linus Torvalds
<torvalds@linux-foundation.org>, <mszeredi@suse.cz>


On Sun, Oct 19, 2014 at 10:31:52AM +0100, David Howells wrote:

>      For overlayfs, the way this probably should be done is file->f_path should
>      be set to point to the overlay dentry (thus getting /proc right) and
>      file->f_inode to the lower file and make use of d_fallthru in the overlay
>      dentry in common with unionmount.

To elaborate a bit: a _lot_ of places in filesystems that used to use
->f_path.dentry->d_inode had been eliminated in favour of file_inode(...)
and all the remaining ones ought to follow.  With that done (I was actually
planning to do whack-a-mole session on those guys after most of this cycle
merges would be done - Linus, would you accept that in -rc2?) we get
surprisingly few places that even look at ->f_path.dentry.

Some of those are refering to ->d_name; we need to review those for other
reasons (potential rename() races), but for unionmount/overlayfs purposes
we couldn't care less which of dentries is used - both overlayfs and
underlying fs dentry have the same name.  FWIW, a bunch of uses are in
printks, and those should become %pD...

A bunch of places uses ->f_path.dentry->d_sb to get the superblock by
file; file_inode()->i_sb would do just fine in filesystems.  And places
like that *outside* of filesystems need a bit of review - the question is
which superblock do we want?  That of overlayfs or that of the layer?
The latter would be file_inode()->i_sb, again.  The former would be a problem
with overlayfs in its current form; with leaving f_path to point to overlayfs
it would work fine.

dir_emit_dot() and dir_emit_dotdot() use ->f_path.dentry, but those are not
problem - overlayfs explicitly opens directory in a layer.

AFAICS, nothing of what remains is on paths hot enough to really care about
an extra dereference.  So I think that after the dust from cleanups settles,
we'll be able to add an inlined helper usable for accesses to file's dentries,
and ban open-coded ->f_path.dentry in filesystems.


----------
From: Al Viro <viro@zeniv.linux.org.uk>
Date: Wed, 22 Oct 2014 at 05:36
To: Miklos Szeredi <miklos@szeredi.hu>
Cc: Linus Torvalds <torvalds@linux-foundation.org>, <mszeredi@suse.cz>


On Fri, Oct 17, 2014 at 05:30:52PM +0200, Miklos Szeredi wrote:

> Will do patches ASAP (which probably means next week) for all but having proper
> d_path() on the leaves.

Ping?  d_path() will obviously have to wait (my preference would be to leave
proper overlayfs dentry in ->f_path.dentry, set ->f_inode to one from the
layer and make sure that all filesystem code will DTRT; we are *very* close
to that already), but that's
        a) doable after the thing went in (it's not that much rewrite and
VFS-side it will probably just mean death to ->dentry_open() - sure, it's
not nice to put the method in just for one cycle, but it's not particulary
tragic, especially if it's clearly marked as "it's going away very soon,
do not rely on it") and
        b) is next cycle fodder

The same goes for the weirdness with double dget() on __upperdentry - it's
absolutely self-contained and we can deal with it later.  I would obviously
prefer fewer odd warts when it goes in, but this one isn't a bug per se.

rmdir() failure, OTOH, is one.  So's the memory footprint of cached
union of directories, seeing that every struct file over a directory
gets a copy of its own.  So's accepting layers with non-trivial ->d_revalidate,
->d_compare, ->d_hash, ->d_automount and ->d_manage.

The interim branch is in vfs.git#ovl-experimental; do you want me to post it
as-is same way you posted previous iterations?

Another thing: one more mail in that thread had bounced from google
mailsewers.  This time it claimed that delivery to Linus has failed.
I've no idea if they report all addresses; I've put the bounce message on
ftp.linux.org.uk/pub/viro/bounced2.  I really wonder what's going on
with those filters...


----------
From: Miklos Szeredi <miklos@szeredi.hu>
Date: Wed, 22 Oct 2014 at 10:12
To: Al Viro <viro@zeniv.linux.org.uk>
Cc: Linus Torvalds <torvalds@linux-foundation.org>, <mszeredi@suse.cz>


On Wed, Oct 22, 2014 at 04:35:58AM +0100, Al Viro wrote:
> On Fri, Oct 17, 2014 at 05:30:52PM +0200, Miklos Szeredi wrote:
>
> > Will do patches ASAP (which probably means next week) for all but having proper
> > d_path() on the leaves.
>
> Ping?  d_path() will obviously have to wait (my preference would be to leave
> proper overlayfs dentry in ->f_path.dentry, set ->f_inode to one from the
> layer and make sure that all filesystem code will DTRT; we are *very* close
> to that already), but that's
>       a) doable after the thing went in (it's not that much rewrite and
> VFS-side it will probably just mean death to ->dentry_open() - sure, it's
> not nice to put the method in just for one cycle, but it's not particulary
> tragic, especially if it's clearly marked as "it's going away very soon,
> do not rely on it") and
>       b) is next cycle fodder
>
> The same goes for the weirdness with double dget() on __upperdentry - it's
> absolutely self-contained and we can deal with it later.

Done, but not terribly urgent for me either.

>  I would obviously
> prefer fewer odd warts when it goes in, but this one isn't a bug per se.
>
> rmdir() failure, OTOH, is one.

I looked at this, and found no bug: it does raise CAP_DAC_OVERRIDE in both
callers (ovl_do_remove() and ovl_rename2()).

>  So's the memory footprint of cached
> union of directories, seeing that every struct file over a directory
> gets a copy of its own.

Done, at the cost of 100 or so extra lines *and* is still DoS-able if the
directory is changed between reading it from the different file instances.

I've been poring over this last night, and the proper solution would be to
update the cache as it changes, so we have only one cache.  Not hard to do
conceptually, but not a small change either.

I'm wondering if it's OK to get i_mutex in ->release().  It would simplify the
locking...

>  So's accepting layers with non-trivial ->d_revalidate,
> ->d_compare, ->d_hash, ->d_automount and ->d_manage.

Done.

Also moved notify_change() into copy-up for setattr, so the attribute update is
atomic.

And a few other cleanups and fixes.

Updated the overlayfs.current branch here:

  git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git
overlayfs.current

Thanks,
Miklos
Al Viro Oct. 17, 2024, 6:13 p.m. UTC | #4
On Thu, Oct 17, 2024 at 03:52:10PM +0200, Miklos Szeredi wrote:
> On Thu, 17 Oct 2024 at 10:18, Amir Goldstein <amir73il@gmail.com> wrote:
> 
> > It has been like that since the first upstream version.
> > My guess is that it is an attempt to avoid turning wdentry
> > into a negative dentry, which is not expected to be useful in
> > ovl_clear_empty() situations, but this is just a guess.
> 
> Yes.  This was discussed in a private thread before merging overlayfs upstream.

10 years ago ;-/  Sorry, memories swapped out...

|> BTW, why on the Earth are you pinning that ->__upperdentry twice?  The
|> comment about d_delete() makes no sense whatsoever - anything other than
|> overlayfs itself would have to grab a reference to call that d_delete(),

[snip]

Out of curiosity, which callers pass anyone's upperdentry to ovl_cleanup()?
Note that if we get dentry reference not from ovl_dentry_upper() et.al.,
we'll get that protection for free.  From quick look it seems that the only
such callchain is from ovl_clear_empty()...

Oh, well...  Shifting it over there would be asking for trouble later on.
Might be worth a comment in ovl_cleanup() - it is subtle enough to be
confusing for readers.