Re: [PATCH] openat2: switch to __attribute__((packed)) for open_how
From: Aleksa Sarai
Date: Wed Dec 18 2019 - 12:32:11 EST
On 2019-12-17, David Laight <David.Laight@xxxxxxxxxx> wrote:
> From Aleksa Sarai
> > Sent: 17 December 2019 06:47
> ...
> > > Just use u64 for all the fields.
> >
> > That is an option (and is the one that clone3 went with), but it's a bit
> > awkward because umode_t is a u16 -- and it would be a waste of 6 bytes
> > to store it as a u64. Arguably it could be extended but I personally
> > find that to be very unlikely (and lots of other syscalls would need be
> > updated).
>
> 6 bytes on interface structure will make almost no difference.
> There is no reason to save more than 16 bits anywhere else.
You have a point, and clone3's way of dealing with it does make life
easier. It also removes the need to care about explicit padding and
padding holes entirely.
> You could error values with high bits set.
Of course we'll give -EINVAL with invalid values, that's one of the
reasons openat2(2) exists after all. :P
> > I'm just going to move the padding to the end and change the error for
> > non-zero padding to -E2BIG.
>
> The padding had to be after the u16 field.
Right, I was suggesting to move the u16 field later in the struct too.
But after thinking about it some more, it doesn't help with
extensibility at all (a subsequent non-u16 extension will leave holes).
So I'm probably just going to go with either the -E2BIG patch or switch
to u64s.
> > > Use 'flags' bits to indicate whether the additional fields should be looked at.
> > > Error if a 'flags' bit requires a value that isn't passed in the structure.
> > >
> > > Then you can add an extra field and old source code recompiled with the
> > > new headers will still work - because the 'junk' value isn't looked at.
> >
> > This problem is already handled entirely by copy_struct_from_user().
> >
> > It is true that for some new fields it will be necessary to add a new
> > flag (such as passing fds -- where 0 is a valid value) but for most new
> > fields (especially pointer or flag fields) it will not be necessary
> > because the 0 value is equivalent to the old behaviour. It also allows
> > us to entirely avoid accepting junk from userspace.
>
> Only if userspace is guaranteed to memset the entire structure before
> making the call - rather than just fill in all the fields it knows
> about. If it doesn't use memset() then recompiling old code with new
> headers will pass garbage to the kernel. copy_struct_from_user()
> cannot solve that problem.
You don't need to /explicitly/ memset(), since
struct open_how how = { .flags = O_RDWR, .resolve = RESOLVE_IN_ROOT };
or even
struct open_how how = {}; /* or { 0 } if you prefer. */
will clear all of the unused fields.
But, I can add a NOTE to the man-page to clarify that this is how users
should fill their structs (or rather, that they should zero-fill them
somehow to avoid this problem).
While this might be a little annoying, I would argue that given the
openat2(2) man page explains how extensions work (in great detail) and
mentions several times that the structure may have new fields added to
it in the future -- programs which don't zero-fill the struct should be
simply seen as buggy. Note that those buggy programs *will still work*
on new kernels -- until you recompile them with new headers (because
they made an incorrect assumption about the structures they were using).
As an aside, the other downside from the uapi side is that we would
probably have to spend flag bits *that are shared with openat(2)* for
such extensions, so I'd like to avoid that as much as necessary.
> You'll never be able to guarantee that all code actually clears the
> entire structure - so at some point extending it will break recompilations
> of old code - annoying.
Only if they're explicitly doing something like
struct open_how how;
how.flags = O_RDWR;
how.resolve = RESOLVE_IN_ROOT;
memset(how.__padding, 0, sizeof(how.__padding));
As above, given the description of extensions in the man-page, I would
consider that style of struct initialisation to be eyebrow-raising at
best.
I'm sorry, but I'm simply against the idea of silently ignoring garbage
that userspace passes to the kernel -- even if it's tied to a flag. That
has proven to be an awful idea and in fact openat2(2) was written
precisely to fix this problem. To be honest, this reminds me of
(hypothetical) code like:
int flags;
flags |= O_PATH | O_CLOEXEC;
open("foo", flags); /* yay, mystery fds! */
IMHO that shouldn't have ever worked, and the only way to stop userspace
from passing garbage is to always reject it.
--
Aleksa Sarai
Senior Software Engineer (Containers)
SUSE Linux GmbH
<https://www.cyphar.com/>
Attachment:
signature.asc
Description: PGP signature