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
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
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
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
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).
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