Discussing this on Twitter is the modern day equivalent of Fermat discussing mathematical proofs via marginalia. So let us expand upon what Peter Wullinger did not have the space to fit into a margin a Twitter post.
There were, of course, other ways that the systemd people could have done this:
- Advise system administrators to use
ssh -x -- account@host systemctl stuff…
instead of systemctl -H account@host stuff…
- Use OpenSSH's built in forwarding mechanism for local-domain sockets so that internally
systemctl
spawns something like ssh -xT -L /run/user/user/account@host/bus:/run/dbus/system_bus_socket -- account@host /bin/true
instead of (as it does) ssh -xT -- account@host systemd-stdio-bridge
There are a number of problems with the approach actually taken, several of which are security-related. None appear, so far, to rise to the level of outright vulnerabilities, however.
"What's this undocumented systemd-stdio-bridge
program?"
Consider, first off, the system administrator looking at logs and the output of ps
, and wondering about these people all running systemd-stdio-bridge
via SSH. We have, as can be seen from "Strange 'agetty' process running on my VPS.", people who wonder about Weitse Venema's agetty
program and we know that MacOS already has malware named systemd
in an attempt to fool people, so it is not a stretch nor unreasonable to imagine that system administrators would wonder about systemd-stdio-bridge
too.
The problem is that this is another occasion where systemd's doco is worse than poor: it is outright absent. There is no manual page for systemd-stdio-bridge
written by the systemd people, at all. So the system administrator's first recourse, reading the manual, is no help. (This is intentional on the parts of the systemd people, who have not included system administrators wanting to identify running programs in their use case.)
It might help the system administrator to learn that the program went through a phase of being called systemd-bus-proxyd
, which had a manual page. Both it and the manual page were removed from systemd, but when the older program was later brought back by systemd developer David Herrmann, a manual page was not restored along with it. (Note, if you do go and dig it up, that the old manual page for systemd-bus-proxyd
does not correctly describe the current command-line usage of systemd-stdio-bridge
.)
Had the systemd developers considered the doco alongside the program, they might have spotted one current bug: systemctl
in certain circumstances passes options that systemd-stdio-bridge
does not support, and systemd-stdio-bridge
supports different options that systemctl
makes no use of.
"What's this undocumented sd_bus_set_address
stuff?"
One of Peter Wullinger's notes is that none of this is documented, and this is at least alas true in the case of the system administrator who tries to resort to reading the code for the systemd-stdio-bridge
program. This is a compiled C program, not one written in an interpreted scripting language. As Peter Wullinger points out, it relies upon calling a sd_bus_set_address()
function. The systemd people provide no manual page for sd_bus_set_address()
, leaving dangling hyperlinks on other systemd manual pages (such as the hyperlink from the sd_bus_new()
manual page for example).
Parsing bugs
It was not initially clear what Peter Wullinger thinks to be an "escape, parse, unescape bug". But there is a lot of parsing going on under the covers; not the least of which is systemd's Desktop Bus library constructing strings such as "unixexec:path=ssh,argv1=-xT,argv2=--,argv3=account%40host,argv4=systemd-stdio-bridge,arg5=--machine=fred
" from their constituent parts in one place only to go and parse them back into their constituent parts in another. And we know from Daniel J. Bernstein (amongst many others) that designing to avoid parsing, including this sort of marshalling into pseudo-human-readable form and then unmarshalling back into machine-readable form, is a good idea.
It turns out that Peter Wullinger was referring to the reason that argv2=--
is now in there. That is of course a well known problem, that is sad to say exemplified far too widely. However, an additional and more subtle parsing-related problem in the aforementioned superfluous serialization and deserialization is the very ad hoc nature of the system. Notice that an equals sign character to be passed in argv3
is escaped as %3d
. The equals sign in arg5=--machine=
is however not escaped. The serialization does not obey the same rules as the deserialization. This way, bugs and security problems lie; and this is one of the very things that Daniel J. Bernstein recommended to avoid by not parsing in the first place.
Reinventing the wheel
There is, of course the notion that systemd-stdio-bridge
is a reinvention of the wheel that has not knocked all of the corners off to make it round enough, yet. After all, as aforementioned, OpenSSH has a built-in mechanism, far more widely used and tested, for forwarding access to local-domain sockets over the SSH connection from remote host to local machine. (It has had it since OpenSSH 6.7, released the year before systemd developer David Herrmann reintroduced the systemd-stdio-bridge
program. Patches for this have been around since 2006, years before systemd existed.)
Militating against this are two things.
First: Spawning a program remotely and then talking to it via its standard input and output, transmitted over the SSH connection, is not exactly an unusual use of SSH. It's the rexec
model of doing things, after all.
Second: Local-domain socket forwarding has to be done carefully. With the standard I/O mechanism, no other D-Bus client program can (absent tricks like debug access) use the forwarding mechanism to itself connect to the remote D-Bus broker. The aforementioned mechanism however has to be careful to:
- not allow anyone other than the user any access at all to
/run/user/user/account@host/
so that only the user's (and the superuser's) processes can access the local proxy socket
- guard against
/run/user/user/
being writable by someone other than the user, so that no-one else can come along and sneakily replace /run/user/user/account@host
with a symbolic link or perform other such tricks
- take care that
/run/user/user/account@host/bus
does not collide amongst multiple invocations of systemctl
(which can be done by employing a locally unique name instead of just bus
)
Further reading