Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Fri, 4 Feb 2022 20:09:13 +0100
From: Joakim Sindholt <>
Subject: Re: [PATCH] pack posix_spawn file actions into extraneous

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.


View attachment "0001-pack-posix_spawn-file-actions-into-extraneous-paddin.patch" of type "text/x-diff" (11783 bytes)

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.