|
|
Message-ID: <20220504201010.GZ7074@brightrain.aerifal.cx>
Date: Wed, 4 May 2022 16:10:10 -0400
From: Rich Felker <dalias@...c.org>
To: musl@...ts.openwall.com
Subject: Re: [PATCH] pack posix_spawn file actions into extraneous
padding
On Fri, Feb 04, 2022 at 08:09:13PM +0100, Joakim Sindholt wrote:
> On Sat, Jan 29, 2022 at 02:43:28PM +0100, Joakim Sindholt wrote:
> > I've written this patch that packs posix_spawn file actions into the
> > posix_spawn_file_actions struct padding. I'm not sure if the way I've
> > split the code up is desirable. Maybe the faexpand() function needs a
> > different name and to be in its own .c file; or maybe a different
> > approach is preferable.
>
> Here's a candidate v2 patch to simplify some things. Changes since the
> first one:
> * fa->__actions is initialized to point to itself. This makes faexpand a
> little simpler and means that ->__actions->__actions always points to
> the last file_actions struct.
> * The previous version caused an increase of 32 bytes in used stack
> space in child() on amd64. This one only causes a 16 byte increase.
> I'm not sure how big of a deal-breaker that is.
> * The two switches have been merged into one. This makes it simpler IMO
> but also has some consequences:
>
> The current implementation in musl uses a pipe to communicate
> success/failure back to the parent process. This means that the
> file_actions struct can accidentally succeed if it tries to use the pipe
> fd as a source fd or worse, overwrite the pipe fd when targeting it in
> either the dup2 or open actions.
> Musl solves this by moving the pipe fd if it's the target of an
> operation. Currently musl will move the pipe if it's the source fd of
> close or fchdir, or if it's the target fd of dup2 or open. You'll note
> that the close and fchdir cases mean that instead of failing
> immediately, it will dup the pipe fd, close the old pipe fd, and then do
> the actual close/fchdir syscall which then fails. It's presumably
> implemented this way because it's simple and doesn't really cause a
> problem. The operation will result in failure regardless.
>
> This patch, in an attempt to be very simple, introduces a similar but
> perhaps slightly worse bug. The encoding of every operation is
> essentially [fd, other stuff], meaning it can do the pipe move check on
> the fd in the first slot. It won't work for close since the fd is
> actually -fd-1, so close gets its own check. If the check is
> unconditional then we might get an unnecessary move with chdir, which is
> encoded as [dummy fd, -FDOP_CHDIR]. I've chosen an fd that's unlikely to
> collide (INT_MAX) however if you have every fd from 0 to INT_MAX-1 open
> then the pipe fd will be on fd INT_MAX and it will not be movable. The
> result is that the unnecessary pipe move fails and an otherwise
> perfectly legal and possible chdir action will fail.
>
> The fix is very simple but does end up once again causing some
> duplication in the op logic:
> -if (fd == p) {
> +if (fd == p && fa->__pad[i] != -FDOP_CHDIR) {
>
> Alternatively you could do the whole command check twice, only move the
> fd if it's the target of dup2 or open, and then have a check in fchdir
> for whether it's trying to use the pipe fd as a source, like the ones
> already in close and dup2.
>
> Or maybe this bug is acceptable.
Since we can already have "spurious" failures anyway from insufficient
free fd space to shuffle the pipe fd, I don't think the thing with
FDOP_CHDIR matters. If we do ever decide we care, you've already
presented a way we could solve the problem.
(I noted on IRC that we could also solve the problem of the "density"
of the coding space by using the 32 bits of __pad0[1] to store type
information for the 16 slots, but this does not seem necessary.)
One change I think I would like to make is not using the names __pad0
and __pad directly in code. It's ugly. Instead, fdop.h could do:
#define fa_cnt __pad0[0]
#define fa_ops __pad
It might also make sense to define a macro for the fa_ops array limit
#define FA_OPS_MAX_CNT ...
using sizeof on a compound literal as appropriate.
We could also for consistency (not using __ junk names from the public
header directly) do
#define fa_chain __actions
Not sure if that's the best name for it but it seems to make things
make more sense than writing __actions in the code that's no longer
using the pointed-to data as the actions list.
I think I'm happy at this point with just doing "strace tests", but
sometime between now and next release I would like to get a better set
of tests into libc-test that cover usage of sufficiently many ops to
require allocations. Something like a test function that takes a list
of operations and builds both the file actions object and a shell
command to evaluate that the file actions were executed correctly,
using:
read x <&%d && test "$x" = %s
and:
! 2>/dev/null <&%d
to assert that each fd is either closed or reads the expected content
(expected content could be temp files with different content in each).
Powered by blists - more mailing lists
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.