Re: SO_PEERCRED and pidfd

From: Christian Brauner
Date: Wed Mar 18 2020 - 09:43:28 EST


On Wed, Mar 18, 2020 at 08:07:29AM -0500, Eric W. Biederman wrote:
> Simon Ser <contact@xxxxxxxxxxx> writes:
>
> > On Tuesday, March 17, 2020 7:58 PM, <ebiederm@xxxxxxxxxxxx> wrote:
> >
> >> Simon Ser contact@xxxxxxxxxxx writes:
> >>
> >> > Hi all,
> >> > I'm a Wayland developer and I've been working on protocol security,
> >> > which involves identifying the process on the other end of a Unix
> >> > socket 1. This is already done by e.g. D-Bus via the PID, however
> >> > this is racy 2.
> >> > Getting the PID is done via SO_PEERCRED. Would there be interest in
> >> > adding a way to get a pidfd out of a Unix socket to fix the race?
> >>
> >> I think we are passing a struct pid through the socket metadata.
> >> So it should be technically feasible.
> >>
> >> However it does come with some long term mainteance costs.
> >>
> >> The big question is what is a pid being used for when being passed.
> >> Last I looked most of the justifications for using metadata like that
> >> with unix domain sockets led to patterns of trust that were also
> >> exploitable.
> >>
> >> Looking at the proposale in 1 even if you have race free access
> >> to /proc/<pid>/exe using pidfds it is possible to change /proc/<pid>/exe
> >> to be anything you can map so that seems to be an example of a problem.
> >
> > /proc/<pid>/exe is a symlink. It doesn't seem like it's possible to
> > unlink it and re-link it to something else (fails with EPERM).
> >
> > Is there a way to do this?
>
> prctl(PR_SET_MM_MAP, ...);
> It is locked down a bit but not enough to trust it in general.

That at least requires CAP_SYS_ADMIN in the current user namespace. But
it seems potentially dangerous when you think about cross-user-namespace
interactions, e.g. execing a binary located on the host and doing
setns(mnt+user).

>
> Further there are games I can play with ptrace where I can start an
> executable and control it, so that you think it is the expected
> executable calling the shots, when in fact it is the process acting
> as the debugger performing the work.
>
> Plus there are the other million and ways known to hijack a setuid
> executable which also apply to this executable you would trust
> because of it's exe_link.
>
> Even beyond that to have a trusted process it's entire life cycle needs
> to be trusted, so that you don't have the danger of someone unscrupulous
> hijacking the process with bad input.

Relying on /proc/<pid>/exe for anything has been a constant source of
bugs and is something we have warned against. The rexec trick we do for
both runC and lxc nowadays when attaching/running containers are
basically ways to ensure that you're executing an in-memory program that
has no attachment to the actual on-disk binary and is a result of
problems like this.

>
> >> So it would be very nice to see a use case spelled out where
> >> the pid reuse race mattered, and that trusting a pid makes sense.
> >
> > The use-case is identifying which process is at the other end of the
> > socket. Once the process is identified, security rules can be applied.
> > For instance a Wayland compositor might give access to a
> > screen capture interface if the program is a trusted screen shooter.
> >
> > Some want to get the full path to the executable, and read the
> > /proc/<pid>/exe symlink. Some want to read a special file created at
> > the root of the process' file system namespace, and access
> > /proc/<pid>/root.
>
> Once we reach the point of having a special file, it is much better
> to pass that special file. Or possibly something derived from the
> special file in a zero knowledge proof sort of way, to prove you
> are a trusted process.
>
> Passing a file descriptor as a token the process is the trusted
> process, is a perfectly fine way to provide proof and unix
> domains sockets have supported that from day one.

The use-case that e.g. comes from system daemons is that they need to
talk to e.g. glibc's syslog() api. And daemon loggers need to know who
they are talking to and that needs to be race-free. And they don't get
sent fds. Assuming we can patch glibc and all loggers to send a
pidfd and put a contract in place saying any message without a pidfd
sent along or a pidfd that is not referring to the SO_PEERCREDs uid
sent along will be rejected still feels wrong. In such cases the daemon
wants to be in control and requested the credentials be given to him. So
I can see the use for another SO_*