Sandboxing X11 for dummies
(I don’t mean GNOME levels of dumb, you should at least know how to not eat your own faeces)
But you can be as dumb as I, which is pretty dumb. So it’s well known that X11 is basically designed like a user account is on Unix. What does this mean? It means that anything that connects to your X server can essentially introspect and modify the entire X server’s contents. Get this into your head, it can trivially listen to all your keypresses, see all the surfaces of all other windows. There is no concept of ‘privacy’ here any more than that any program you run as your user can read and modify all your files. In theory this is not objectionable, it still can’t touch other users so the user-barrier remains protected. But you can give other users access to your X server at which point those users can read all your keypresses so be mindful when you do that.
Now, you often read that X11 is impossible to sandbox against this or whatever, that’s a myth. There are many ways to restrict the access a client can perform, the most common one is a convoluted mess of using a nested X server in which the client is ran that you don’t trust. Turns out there’s a simpler way which no one is talking about for some reasn, the X11 SECURITY extension which Xorg, the most popular X server has implemented. That basically hides all this info from certain clients.
how does it normally work.
If your Xorg is compiled with --enable-xcsecurity (some distributions don’t, bugger if I know why) your Xorg can now partition the list of connected clients into ‘trusted’ and ‘untrusted’. The ‘trusted’ clients are the old ones we all know and love that can introspect and modify the entire state of the X server. The untrusted ones can only access those clients that share their session cookie. As a test to demonstrate how easily you can get all keypresses in X11 and thus make a keylogger. Let’s use xev and wmctrl (install it if you don’t have it yet) to show how.
First off, we use wcmtrl to obtain a list of all the X window ids on our display, which should look something like this:
—— — wmctrl -l
0x01e00002 -1 Q conky (Q)
0x02600002 -1 Q conky (Q)
0x01c00002 -1 Q conky (Q)
0x02200002 -1 Q conky (Q)
0x02000002 -1 Q conky (Q)
0x02800002 -1 Q conky (Q)
0x02400002 -1 Q conky (Q)
0x01400015 -1 N/A ~ : tmux — Konsole
0x03e00015 0 N/A ~ : start-main — Konsole
0x03800057 1 Q Buddy List
0x03a00014 0 N/A sandboxing-x11.md — Kate
So the last one, which is the window of my text editor, we’re going to snoop on that window’s keypresses with xev, if we do:
$ xev -id 0x03a00014
And select our text editor window, and type in it, we see a bunch of ‘events’ scroll by in xev. No root required, xev, like any graphical program in our X session can snoop on all keypresses of any Window, for trusted programs this is not an issue, in fact this functionality is useful to write automation scripts, to implement hotkey daemons, to make Window managers, this is how X11 works, everything is a shared resource and fundamental parts of your desktop work by manipulating those resources. But if we don’t trust an application, we’d like to secure against it. Enter the SECURITY extension
such security, such wow
So I have this very simple shell script which launches a program in a sandbox as far as Xorg is concerned. It needs the xauth utility, again, install this if you don’t have it.
#!/bin/sh
set -eu
# this is where we will save the cookie used to identify this untrusted session
cookie=/tmp/.Xauthority-$USER-$$$$-untrusted
# create file and set its permissions so that other users can't read it, very important
touch "$cookie"
chmod 0600 "$cookie"
# we use xauth to put an untrusted cookie into this file, Xorg will be able to identify the client is untrusted due to this cookie's contents
xauth -f "$cookie" generate "$DISPLAY" MIT-MAGIC-COOKIE-1 untrusted
# the XAUTHORITY environment variable is used by xlib to point to the cookie we are using
export XAUTHORITY="$cookie"
# and just exec into the actual program
exec "$@"
So I stored this in ~/.local/bin/Xsecexec, made it executable, and here we go:
$ Xsecexec xev -id $(wmctrl -l | grep Kate\$ | awk '{print $1}')
Nothing. No input events any more if we select the text editor. Wondrous isn’t it?
Now, we can also let xev generate its own window. If we do:
$ Xsecexec xev
It will generate a new window, and input events are again printed into the terminal when we focus that window and press random keys. Because it can access its own windows.
so what is wrong here?
Diligent readers will immediately scream that I have not actually secured anything, after all if our program was malicious, it could’ve just read the normal cookie in ~/.Xauthority and gained access to the unrestricted X server as trusted client or to another untrusted cookie to snoop on that untrusted session. And yes, that’s true, so obviously this needs to work in concord with some kind of filesystem sandboxing that denies it access to read those files. This is just a proof of concept demonstrating that Xorg has had GUI sandboxing capabilities for a decade apparently.
The other disclaimer is that some X applications will assume they run as trusted and can’t run as untrusted and will crash when ran in untrusted mode.
Did you know something else? Apparently OpenSSH when you tunnel X by default treats clients as untrusted. You’ve possibly been using this for a while even if you didn’t know what it was.