lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAGudoHGP+x0VPpJnn=zWG6NLTkN8t+TvKDwErfWVvzZ7CEa+=Q@mail.gmail.com>
Date: Mon, 3 Nov 2025 17:44:07 +0100
From: Mateusz Guzik <mjguzik@...il.com>
To: Al Viro <viro@...iv.linux.org.uk>
Cc: brauner@...nel.org, jack@...e.cz, linux-kernel@...r.kernel.org, 
	linux-fsdevel@...r.kernel.org
Subject: Re: [PATCH] fs: touch up predicts in putname()

On Mon, Nov 3, 2025 at 5:45 AM Al Viro <viro@...iv.linux.org.uk> wrote:
>
> On Sun, Nov 02, 2025 at 11:42:03PM +0100, Mateusz Guzik wrote:
>
> > Even ignoring the fact that there is a refcount and people may be
> > inclined to refname(name) + take_filename(name), the following already
> > breaks:
>
> Er...  refname() doesn't need to be seen for anyone other than auditsc.c
> and core part of filename handling in fs/namei.c (I'd like to move it
> to fs/filename.c someday)...
>
> > foo() {
> >     name = getname(...);
> >     if (!IS_ERR_OR_NULL(name))
> >         bar(name);
> >     putname(name);
> > }
> >
> > bar(struct filename *name)
> > {
> >     baz(take_filename(&name));
> > }
> >
> > While the code as proposed in the branch does not do it, it is a
> > matter of time before something which can be distilled to the above
> > shows up.
>
> Breaks in which case, exactly?  If baz() consumes its argument, we are
> fine, if it does we have a leak...
>

My point is currently the idiomatic handling of struct filename is to
getname(), pass it around and then unconditionally call putname() on
it, which already branches on IS_ERR_OR_NULL. With the previous
proposed design it would be a matter of time before someone does that
and take_filename somewhere down the callstack, resulting in a bug.

The new alien_filename struct mostly sorts it out, but I have some notes on it.

> I agree that 'take_filename' is inviting wrong connotations, though.
>
> Hell knows - it might be worth thinking of that as claiming ownership.
> Or, perhaps, transformation of the original object, if we separate
> the notion of 'active filename' (absolutely tied to one thread, not
> allowed to be reachable from any data structures shared with other
> threads, etc.) from 'embryonic filename' (no refcounting whatsoever,
> no copying of references, etc., consumed on transformation into
> 'active filename').  Then getname_alien() would create an embryo,
> to be consumed before doing actual work.  That could be expressed
> in C type system...  Need to think about that.
>
> One possibility would be something like
>
> struct alien_filename {
>         struct filename *__dont_touch_that;
> };
>
> int getname_alien(struct alien_filename *v, const char __user *string)
> {
>         struct filename *res;
>         if (WARN_ON(v->__dont_touch_that))
>                 return -EINVAL;
>         res = getname_flags(string, GETNAME_NOAUDIT);
>         if (IS_ERR(res))
>                 return PTR_ERR(res);
>         v->__done_touch_that = res;
>         return 0;
> }
>
> void destroy_alien_filename(struct alient_filename *v)
> {
>         putname(no_free_ptr(v->__dont_touch_that));
> }
>
> struct filename *claim_filename(struct alien_filename *v)
> {
>         struct filename *res = no_free_ptr(v->__dont_touch_that);
>         if (!IS_ERR(res))
>                 audit_getname(res);
>         return res;
> }
>
> and e.g.
>
> struct io_rename {
>         struct file                     *file;
>         int                             old_dfd;
>         int                             new_dfd;
>         struct alien_filename           oldpath;
>         struct alien_filename           newpath;
>         int                             flags;
> };
>
> ...
>         err = getname_alien(&ren->oldpath);
>         if (unlikely(err))
>                 return err;
>         err = getname_alien(&ren->newpath);
>         if (unlikely(err)) {
>                 destroy_alien_filename(&ren->oldpath);
>                 return err;
>         }
>
> ...
>         /* note that do_renameat2() consumes filename references */
>         ret = do_renameat2(ren->old_dfd, claim_filename(&ren->oldpath),
>                            ren->new_dfd, claim_filename(&ren->newpath),
>                            ren->flags);
> ...
>
> void io_renameat_cleanup(struct io_kiocb *req)
> {
>         struct io_rename *ren = io_kiocb_to_cmd(req, struct io_rename);
>
>         destroy_alien_filename(&ren->oldpath);
>         destroy_alien_filename(&ren->newpath);
> }
>
> Might work...  Anyone found adding any instances of __dont_touch_that anywhere in
> the kernel would be obviously doing something fishy (and if they are playing silly
> buggers with obfuscating that, s/doing something fishy/malicious/), so C typechecking
> + git grep once in a while should suffice for safety.

I think this still wants some error-proofing to catch bad usage. Per
the above, the new thing deviates from the idiom claiming you can
always putname().

Perhaps like this:
struct alien_filename {
        struct filename *__dont_touch_that;
        struct task_struct *__who_can_free;
        bool is_delegated;
};

It would start with __who_can_free == NULL and would need to be
populated by a helper before destroy_alien_name is legally callable.

The consumer would denote it does not intend to free the obj by
calling delegate_alien_name(), after which some other thread needs to
take ownership.

a sketch:
/* called by the thread which allocated the name if it decides to go
through with it */
delegate_alien_name(name) {
    VFS_BUG_ON(name->delegated);
    name->delegated = true;
}

/* called by the thread using the name */
claim_alien_name(name) {
    VFS_BUG_ON(!name->delegated);
    VFS_BUG_ON(name->__who_can_free != NULL);
    name->__who_can_free = current;
}

destroy_alien_name(name) {
    if (name->delegated) {
        VFS_BUG_ON(name->__who_can_free == NULL);
        VFS_BUG_ON(name->__who_can_free != current);
    }
    putname(..);
}

So a sample correct consumer looks like this:
err = getname_alien(&name);
....
err = other_prep();
if (!err)
    actual_work(delegate_alien_name(name));
else
    destroy_alien_name(name);

the *other* thread which eventually works on the name:
claim_alien_name(name);
/* hard work goes here */
destroy_alien_name(name);

Sample buggy consumer which both delegated the free *and* decided free
anyway is caught.

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ