On Sat, 09 Feb 2019 at 23:26:53 +0100, Francesco Poli wrote:
> On Sat, 9 Feb 2019 22:31:14 +0100 Michael Biebl wrote:
> > I guess I already mentioned the two alternatives (runuser/setpriv).
> [...]
> 
> Maybe setpriv is equivalent to s6-setuidgid.
> If this is the case, it can be used as an alternative to s6-setuidgid.

As far as I can tell, s6-setuidgid sets the uid, primary gid and
supplementary group list with setuid(), setgid() and setgroups() (by
a somewhat convoluted route involving putting them in the environment
with s6-envuidgid and reading them back out in s6-applyuidgid, presumably
out of a preference for small composable tools), and doesn't do anything
else of interest (for example resetting HOME or LOGNAME to reflect the
new identity, which it should perhaps do).

Running `setpriv --reuid NAME --init-groups PROGRAM ARGS` appears to be
equivalent to `s6-setuidgid NAME PROGRAM ARGS`. This is because on Linux
(and hopefully the non-Linux ports too), setuid(x) called by a privileged
process is documented to be equivalent to setresuid(x, x, x), which
is exactly what setpriv --reuid ends up doing; and setpriv --init-groups
takes the `struct passwd` for the NAME given to --reuid (or --ruid),
and sets the primary gid and supplementary group list based on it,
just like the combination of s6-envuidgid and s6-applyuidgid.

If you don't consider the current use of s6-setuidgid to be a bug, then
using setpriv would have an equivalent effect without a non-Essential
dependency, closing Bug #921819 and the very similar bug #916753.
Anything beyond that (for example altering DISPLAY, HOME, LOGNAME, etc.
to get an environment more closely resembling the target user's login
shell) is a separate request for enhancement that is not solved by
s6-setuidgid either, so please don't let the perfect be the enemy of
the good here.

> I would like some insight especially on [message #30], regarding the
> fact that runuser does something basically equivalent to what su does,
> and thus seems to be unfit to irreversibly drop root privileges

The major difference between {setpriv,s6-setuidgid} and runuser is that
runuser, like su, sets up a new PAM session and re-initializes some
standard environment variables.

Also like su, if run with -l, it also tries to behave like a login shell
(clears more environment variables, changes to the target user's home
directory, etc.) and runs a different PAM stack (which registers with
systemd-logind, if installed, as a new login session).

However, runuser is not setuid (unlike su), so it cannot increase
privileges, only drop them. The only thing it *can* do is to drop root
privileges; so if you consider it to be unfit to do that for some reason,
it would have no purpose at all. I assume the maintainers of util-linux
are not in the habit of adding tools that have no purpose.

> and
> regarding my search for a command that works like s6-setuidgid, but
> runs the given command inside the user's login shell (with all the
> environment that the user would get on a normal login).

As stated, this isn't really well-defined. Whether and how this is
possible depends what you mean by "a normal login". The execution
environment the user would get from login(1) as invoked by getty(8),
from a display manager like xdm, and from sshd are all different (they
invoke different PAM stacks); and that's before you've even entered
any shells.

Running a given command in a user's login shell also suffers from the fact
that you don't actually know what the user's login shell does. It isn't
guaranteed to be a POSIX shell, or a Bourne-compatible shell, or in any
way suitable for non-interactive use, and it isn't guaranteed to take
the same command-line arguments as dash and bash.

> But I would like to have a command that runs a given command inside the
> regular user's login environment, as I said above.
> Do you know one such command?

The tl;dr answer is "no". The slightly longer answer is "it's
complicated".

If your design relies on being able to do this, it might be a good idea
to rethink aspects of your design. For instance, instead of bending
over backwards to run a web browser as the invoking user, it might be
reasonable to print a file:/// URL to the terminal, and hope the invoking
user has a terminal in which they can Ctrl+click URLs? Recent versions
of some terminals even have an escape sequence to mark arbitrary text
as a clickable URL, as used by `ls --hyperlink`.

The long answer starts with: this is not really how it works. You can't
unilaterally move a child process from your own execution environment
into a logged-in user's execution environment: whatever you do, it will
inherit the inheritable aspects of its execution environment from you,
not from the user.

Historically, the inheritable aspects of the execution environment were
sufficiently well-known and sufficiently few in number (for instance
credentials, environment variables and working directory) that you could
get away with resetting them to known-good (or at least
conjectured-to-be-good) values and hoping it was enough. However, a modern
Linux process has an ever-increasing number of attributes that are
inherited from its parent (open file descriptors, rlimits, capabilities,
namespaces, seccomp bits, cgroups, audit loginuid, LSM contexts...),
some of which are explicitly designed to be impossible to forge, or set
once/impossible to unset, or similar; so that doesn't really work any
more, if it ever did.

The reason that some of these newer aspects of the execution environment
are designed to be set once/impossible to unset is that you can't abuse
privileges you can't gain. Privilege escalation via mechanisms like
setuid is inherently dangerous, because all the inheritable process
attributes of the setuid process are inherited from a potential attacker,
and the setuid process is responsible for making sure that none of them
can be used to trick it into misusing its privileges.

Earlier, I talked about getty(8)/login(1), xdm and sshd. All of those
have something in common: they are "entry point" services through which
users log in. More specifically, they start as a system-level process,
outside the context of any particular user login session (usually),
and they create a user login session (for their child process, or for a
process that replaces them in exec()). In a system with pam_loginuid
("audit sessions") enabled, system-level processes have their loginuid
attribute set to -1, while user-session processes have their loginuid set
to the uid of the user whose session it is. After setting the loginuid
to a value other than -1, it cannot change.

In a system booted with systemd, there is another way in which
system-level processes and user login sessions are disjoint: system-level
processes are in the system.slice cgroup, user-level processes are
in user.slice, system-level processes can move their children into
user.slice, and user-level processes cannot normally move their children
back into system.slice (although suitably privileged user-level processes
*can* use IPC to ask a system-level process like systemd to create more
system-level children).

On a system booted with sysvinit, things are a bit more blurry: if
a logged-in sysadmin runs "/etc/init.d/ssh restart", sshd becomes a
child of their user session (its loginuid is set to the sysadmin's),
and because you can't escape from a user session, all subsequent ssh
sessions are formally part of the sysadmin's user session (which seems
vanishingly unlikely to be what they meant). The same applies to xdm
and other display managers.

The closest you can get to executing an arbitrary command in a user's
session is to use IPC to tell a process that is already associated with
the user's login session to run an equivalent command for you. For
instance, on a system that was booted with systemd and has working
libpam-systemd and dbus-user-session packages, you could do the equivalent
of something like this (totally untested):

    numeric_uid=$(id -u $username)

    if [ -d /run/user/$numeric_uid/systemd ] \
    && [ -S /run/user/$numeric_uid/bus ]; then
        setpriv --reuid $username --initgroups \
        env \
        -u DBUS_SESSION_BUS_ADDRESS \
        XDG_RUNTIME_DIR="/run/user/$numeric_uid" \
        systemd-run --user --wait COMMAND [ARGS...]
    else
        ... this is not one of those systems, so do something else ...
    fi

In this example, systemd-run is a child of the shell script, and has the
user's credentials (uid and groups) but remains outside their session -
but the COMMAND that it launches via IPC is a child of their
`systemd --user` process, and inherits its execution environment from
there instead. You can think of it as being a bit like a ssh client
logging in to a remote system to run the COMMAND.

That snippet depends on the implementation detail that systemd sets user
processes' XDG_RUNTIME_DIR to /run/user/$numeric_uid, which in principle
it would be within its rights to change at any time, but in practice
there's no reason why it would. It also makes use of the fact that
systemd-run uses a D-Bus socket in the XDG_RUNTIME_DIR to communicate
with systemd --user, and that systemd-run doesn't strongly depend on
other environment variables having their expected values.

You could also do this by attaching a ptrace-based debugger to a process
in the user session and forcing it to fork-and-exec the desired command...
but don't do that.

The way this is *meant* to work in recent desktop environments is that
you don't inject arbitrary commands into user sessions "from the outside".
Instead, a per-user service or application inside the user session uses
IPC (D-Bus or similar) to subscribe to signals from a system service, and
the system service notifies interested clients when something interesting
happens. For instance, this is how the needrestart-session package works,
and is also how UIs for system services like NetworkManager and wicd work.

apt-listbugs is in a particularly precarious situation
because apt is often run from inside a user session, under a
(setuid!) privilege-escalation tool like su, sudo or pkexec. So the
process hierarchy looks something like this:

[init, etc.]
    \- xdm or sshd or whatever ****
        \- [multiple levels of user session processes]
            \- xterm or similar
                \- sudo apt-get update ****
                    \- [apt stuff]
                         \- apt-listbugs
                              \- the bit we're now talking about ****
                                  \- firefox

where all the lines marked **** are either dropping or escalating
privileges.

    smcv

Reply via email to