Our beta release of Plasma 5.5 includes a Wayland session file which allows to start in a Plasma on Wayland session directly from a login manager supporting Wayland session. This is a good point in time to look at the security of our Plasma on Wayland session.
Situation on X11
X11 is not secure and has severe conceptual issues like
- any client connected to the X server (either remote or local) can read all input events
- any client can get information about when another window rendered and get the content of the window
- any client can change any X attribute of any other window
- any window can position itself
- many more issues
This can be used to create very interesting attacks. It’s one of the reasons why I for example think it’s a very bad idea to start the file manager as root on the same X server. I’m quite certain that if I wanted to I could exploit this relatively easily just through what X provides.
The insecurity of X11 also influenced the security design of applications running on X11. It’s pointless to think about preventing potential attacks if you could get the same by just using core X11 functionality. For example KWin’s scripting functionality allows to interact with the X11 windows. In general one could say that’s dangerous as it allows untrusted code to change aspects of the managed windows, but it’s nothing you could not get with plain X11.
Improvements on Wayland
Wayland removes the threats from the X11 world. The protocols are designed in a secure way. A client cannot in any way interact with windows from other clients. This implies:
- reading of key events for other windows is not possible
- getting window content of other windows is not possible
In addition the protocols do not allow to e.g. position windows themselves, or raise themselves, change stacking order and so on.
Security removed in Plasma
But lots of these security restrictions do not work for us in Plasma. Our desktop shell need to be able to get information of other windows (e.g. Tasks applet), be able to mark a panel as a panel (above other windows) and need to be able to position windows itself.
Given that we removed some of the security aspects again and introduced a few custom protocols to provide window management facilities and also to allow Plasma windows to position themselves. At the moment we have no security restrictions on that yet which gives this functionality to all clients.
We will address this in a future release. There are multiple ways how this could be addressed, e.g. using the Wayland security module library or use systemd in some way to restrict access. Overall I think it will require rethinking security on a Linux user session in general, more on that later on.
Security added in Plasma compared to X11
The most important change on the security front of the desktop session is a secure screen locker. With Plasma 5.5 on Wayland we are able to provide that and address some long standing issues from X11. The screen locks even if a context menu is open or anything else grabbing input. The compositor knows that the screen is locked and knows which window is the screen locker. This is a huge change compared to X11: the XServer has no concept of a screen locker. Our compositor can now do the right thing when the screen is locked:
- don’t render other windows
- ensure input events are only handled in the lock screen
- prevent access to screen grabbing functionality while screen is locked
As a matter of fact the Wayland protocol itself doesn’t know anything about screen locking either. This is now something we added directly to KWin and doesn’t need any additional custom Wayland interfaces.
How to break the security?
Now imagine you want to write a key logger in a Plasma/Wayland world. How would you do it? I asked myself this question recently, thought about it, found a possible solution and had a key logger in less than 10 minutes: ouch.
Of course there is no way to get a client to act as a key logger. The Wayland protocol is designed in a secure way and also our protocol additions do not weaken that. So the key to get a key logger is to attack KWin.
So what can an attacker do with KWin if he owns it? Well pretty much anything. KWin internally has a very straight forward trust model: everything is trusted and everything can access anything. There is not much to do about that, this is kind of how binaries work.
For example as a Qt application each loaded plugin has access to the QCoreApplication::instance. From there one could just use Qt’s meta object inspection to e.g. get to the InputRedirection model and connect to the internal signal on each key press:
<code>void ExamplePlugin::installKeyLogger() { const auto children = QCoreApplication::instance()->children(); for (auto it = children.begin(); it != children.end(); ++it) { const QMetaObject *meta = (*it)->metaObject(); if (qstrcmp(meta->className(), "KWin::InputRedirection") != 0) { continue; } connect(*it, SIGNAL(keyStateChanged(quint32,InputRedirection::KeyboardKeyState)), this, SLOT(keyPressed(quint32)), Qt::DirectConnection); } } void ExamplePlugin::keyPressed(quint32 key) { qDebug() << "!!!! Key: " << key; } </code>
But Martin, why don’t you just remove the signal, why should any other aspect of KWin see the key events? Because this is just the example of the most trivial exploit. Of course it’s not the only one. If you have enough time and money you could write more sophisticated ones. For example look at this scenario:
KWin uses logind to open restricted files like the input event files or the DRM node. For this KWin registers as the session controller in logind. Now a binary plugin could just send a DBus call to logind to also open the input event files and read all events. Or open the DRM node and take over rendering from KWin. There is nothing logind could do about it: how should it be able to distinguish a valid from an invalid request coming from KWin?
How to secure again?
As we can see the threat is in loading plugins. So all we need to do is ensure that KWin doesn’t load any plugins from not trusted locations (that is not from any user owned locations). This is easy enough for QML plugins where we have the complete control. In fact it’s easy to ensure for any of KWin’s own plugins. We can restrict the location of all of them.
And even more: by default a system is setup in a way that no binary plugins are loaded from user’s home. So yeah, no problem after all? Well, unfortunately not. During session startup various scripts are sourced which can override the environment variables to influence the loading of plugins. And this allows to also use the well known LD_PRELOAD hack. My naive approach to circumvent this issue didn’t work out at all as I had to learn that already the session startup and the PAM interaction source scripts. So your session might be owned very early.
An approach to black list (unset) env variables is futile. There are too many libraries KWin relies on which in turn load plugins through custom env variables. Most obvious examples are Qt and Mesa. But there are probably many more. If we forget to unset one variable the protection is broken.
A different approach would be to white list some known secure env variables to be passed to KWin. But this also requires that at the point where we want to do the restriction the session is not already completely broken. This in turn means that neither PAM nor the session manager may load any variables into the session before starting the session startup. And that’s unfortunately outside what we can do in our session startup.
So for Plasma 5.5 I think there is nothing we can do to get this secure, which is fine given that the Wayland session is still under development. For Plasma 5.6 we need to rethink the approach completely and that might involve changing the startup overall. We need to have a secure and controlled startup process. Only once KWin is started we can think about sourcing env variables from user locations.
So how big is the threat? By default it’s of course secure. Only if there is already a malicious program running in the system there is a chance of installing a key logger in this way. If one is able to exploit e.g. a browser in a way that it can store an env variable script in one of the locations, you are owned. Or if someone is able to get physical access to your unencrypted hard disk, there is a threat. There are easy workarounds for a user: make all locations from where scripts are sourced during session startup non-writable and non-executable, best change ownership to root and encrypt your home location.
Overall it means that Plasma 5.5 on Wayland is not yet able to provide the security I would have liked to have, but it’s still a huge improvement over X11. And I’m quite certain that we will be able to solve this.
Great blog post Martin!
There is only so much you can do without mandatory access control systems (like SELinux) or GrSec. There is also this wonderful little out-of-the-tree module implementing TPE: https://github.com/cormander/tpe-lkm
I would say, try not to achieve a perfect solution but still reduce the attack surface for the people who do not use any kind of TPE.
Anyway, I still think sanitizing the environment variables of the applications launched by plasma and kwin may make sense, but I would need to think about use cases where it would be good not to have this.
I really need to get back to working on WSM, it is becoming quite urgent!
Yeah things like SELinux can be a solution. Unfortunately hardly any desktop oriented distro ships with it. Thus if we decide to go this road we need at the same time look into how to get it secure in other means as well. There’s just no point in us providing a solution which doesn’t work in practice cause distros do things differently. And I don’t fool myself there: if we ask distros to enable SELinux we get bikeshedding up to the point that distros will think of kicking our software out.
So yeah interesting things to think about. Personally I’m much in favor of going in the direction of SELinux to lock down.
Yes, agreed, hence why tpe-lkm sounds like a good plan, especially on phones/tablets 🙂
What distros don’t have it? My Debian has it by default as far as I know.
In the packages of enabled by default? I’m a Debian user myself and neither selinux nor apparmor are installed on my system. Which means disastrous need to change their default.
I agree it’s difficult to rely on that and even if distributions enable it by default there is not THE solution inside the Kernel. Some prefer apparmor, some SELinux, some maybe TOMOYO, etc. You would practically need to support all of them. MAC in Linux at its current state is only suitable to provide an optional additional Layer of security I think.
> Or if someone is able to get physical access to your unencrypted hard disk, there is a threat.
In this case you have a big problem anyway. 😉
> If one is able to exploit e.g. a browser in a way that it can store an env variable script in one of the locations, you are owned.
This is the real issue. With unrestricted access to the home directory such an exploited browser is possibly able to do many unwanted things. This applies not only to KWin (which is of course a worthwhile target as a very central component of the Desktop) but to every other application which executes code lying around in ~ or maybe simply loads a manipulated config file which makes it do things in favor of the attacker.
Isolation between the users applications on the level of the windowing system is only part of the puzzle (but surely an important one). To be effective the same needs to be done to other resources like the file system (maybe by using namespaces).
Yeah, it’s not enabled by default indeed, but packages are there. Setting it up can be not very easy though, especially depending on filesystems used and etc.
openSUSE ships with enabled apparmor by default (at least since 2 years now…). Not saying that this is a way to go, just there is example… 🙂
You could call it with systemd-run as you have more control over the environment vars …but then you might need to call it as root to assign the tty permissions…
Also to secure the input devices, possibly would have to start a fullscreenshell compositor as a different (service account) user, give the current user permission to connect to that fullscreen shell, and then call kwin on it?
I don’t really think it would help. We would still have the problem of the logind session controller (if KWin doesn’t take it any process is free to take it) and with that the malicious process is still able to get the input device.
Also I really think it’s a good thing that we don’t need a root run process any more to get to the input events. Let’s not go back to X architecture 😉
X is a bad idea. But requiring ‘elevated priveleges’ to open devices is not such a bad idea if you pair it with authentication scheme to delegate, I think. I posted about a potential way to do the security based on ‘tokens’ generated at login time and valid for the life time of the login –> taking this a step further:
A privileged service that knew which tokens were associated with what sessions/seats could offer open() method calls that takes the device to open and a token. Then it could check that token and requested device both match to the right seat/session and then either return an opened device node or fail. In that case, when only the right compositor has the right ‘token’ then it means only the compositor can open the devices associated with the specific session of the specific user.
I may be missing the obvious, though.
Thanks for the post! As someone with only a weak understanding of X11 and Wayland, it’s useful to see a clear set of specific examples where Wayland solves some fundamental X11 security design flaws.
It’s also fascinating to see what desktop environment developers have to contend with above (so to speak) Wayland to be secure.
Does logind uses DBus? If it’s, then we can set privileges to talk with logind daemon, so only compositors and some other programs can talk.
Yes it’s dbus. But how do you want to identify “compositors”. White list binary names?
DBUS have access control mechanism. Linux kernel allows for local sockets to obtain information about process on other side(it’s still impossible for programs far away), so DBus collect this information and check program have permission to talk with some service.
I have suggested onto OpenFate idea to handle binaries in /bin, /sbin, /usr/bin, /usr/sbin differently than other, for example disallow to debug them. Also we could change linker to remove some environment variable. Reason? These programs are installed with system or by package manager, so each dependency must been satisfied. Change to LD_LIBRARY path is needed by third party programs.
agree, that’s a good suggestion.
Great blog post! I don’t really have any solutions to offer, but more of “problems to think about.” If solutions like SELinux or systemd are sought out, then the BSDs won’t be able to use your solution. You’ll effectively be locking them out of future updates. I really wish I had some platform-agnostic ideas off the top of my head to suggest as potential solutions, but I don’t.
Do the BSD’s even do Wayland? In any case BSD’s wouldn’t ‘lose out’ — they would simply be unable to gain the same improvements in security until someone steps up and implements an equivalent BSD version of the scheme for the necessary operations.
I remember that KWin has custom shaders support, loaded as text files from the user home directory.
If that is kept and if those shaders process the whole screen, it is possible to get the content of the screen using the timing attack (if we can get the FPS from KWin in some way, for example).
See http://www.w3.org/Graphics/fx/wiki/CSS_Shaders_Security for example of that problem with web content and css shaders. (That problem was solved by removing CSS Shaders completely, but they were never enabled by default to start with).
Thanks a lot! You are absolutely right, we load the shaders from GenericDataLocation. This needs to be changed and I just created a task for that.
s/conceptional/conceptual/
(I’m not a native speaker, take it with a grain of salt 🙂
Btw, nice article! 😀
Thanks, just checked the dictionary. That was clearly taking me the German word making it look English 😉
A few thoughts:
Arguably the best way is by assigning SELINUX labels/contexts and enforcing. Not as portable though.
A weaker version: with the magic of cgroups, the display manager (e.g. sddm) could ensure that kwin and the “session” live in specific, globally unique, c-groups and pass this information on to kwin. AFAIK moving processes around in c-groups is a privileged operation, which the login manager *would* be able to do as it runs with the privs, but the user session and any child processes won’t. So kwin could rely on this info to be correct (unless privilege escalation was achieved by the attacker). Also not portable.
Weaker still: the login manager generates a token (e.g. random UUID) and passes this information on to kwin as well as the user session. The user session can authenticate itself against kwin by providing the token, but should not pass-, leak- or disclose in any way the value of the token to child processes.
This probably requires a special process to babysit the token and desktop in order to be able to fully recover from a Plasma crash (otherwise the token would be ‘lost’ to the next Plasma), that process could also act as ‘oracle’ so kwin doesn’t have to store the token.
I have thought about c-groups for solving our security problem and think it could work. In general I think that we might need somthing like “trust rings”: everything in KWin’s trust ring would be in one cgroup, everything else in another one.
Yep: ‘trust’ rings is the way to go — and a commonality between each ‘working’ solution is that they all explicitly or implicitly partition the user session between “trusted” and “untrusted” user processes:
– SELINUX labels: those with the magic label, and those without
– Using a system service account: those that run as the service account kwin & desktop (trusted) and those that run as the normal user (untrusted).
– Using a token (and oracle) to distinguish: those that know the token and those that don’t (untrusted)
– Using cgroups: those in the right magic cgroup, and those outside.
The benefit of the system user account is that it is a somewhat portable scheme that maybe kind of could work/be useful even for X (nested X server for untrusted apps) and could be further bolstered with LoginManager/cgroups magic and SELINUX label (defence in depth). The downside is that it is a lot more complex because you need to pretend that two login sessions are actually one, and be mindful of this during the entire lifecycle of the session — as opposed to getting it right, once, and upfront when you are conveniently still running with elevated privileges to do it.
IMHO only real solution would be to handle plug-ins as separate processes (preferably in sandbox). At this point it is probably too much work (I don’t know Qt/KDE specifics).
It wouldn’t solve anything as system libraries (e.g. Mesa) also load plugins from env variable specified locations.
Yay! … Now we need a firewall for the window manager… 😉
I want to express my greatest praise for an security sensitive developer: Thanks in advance!
I’m very fond of the idea to use tokens – But: If it is possible to eavesdropping while Kwin submitts the token to the starting up process, everything becomes absurd… I’m no expert on this, but you may know, how intercentable this comunication is.
The first exchange is the real weakpoint of tokens, i thing.
It is important, that …
– tokens can’n get guessed or bruteforced too easy,
– the generation of tokens is kryptographicly reliable
– !! the tokens never ever will be submitted in the regular IPC !!
(…if there could be sniffed – let’s don’t make it easy ;] )
Grouping:
It’s possible to group up processes by tokens alone, if the regular tokens are generated by deriving it from the startkey (the key, Kwin got from the LoginManager). These tokens have to be a kryptographic oneway: It has to be impossible to guess the startkey, even when multiple tokens already known. But , in reverse, knowing the startkey, the token can get verified.
To make a group, a regular token as a grouptoken has to be generated, but stays secret. The tokens for every single of grouped processes are generated based of the grouptoken (like deriving it from the startkey). The chain of processtoken -> grouptoken -> startkey now can get veryfied consistandly. I can imagine to build key-trees like this, like common practice in the kryptography…
Startkey
|
grouptoken#xy
| | | |
token01 token02 token03 token04 …
By utilitising tokens, we could go one step further: How about encrypting the IPC-steam using the token as the key? That would be intresting for remote-sessions, login windows or child processes (specialy for those, who want to secure against the mother process: Logins, tunneling, sandboxes, …), etc…
In those paranoid times, I sometimes wish for Operating System generated capchas to confirm system / security sensitive input: “Are you human ?!”. But without kryptographic hardened input + rendering, applications alike are off limits… :[
Keep up your great work!