Bug 101595

Summary: wl_keyboard::keymap fd is shared and can be modified from any client
Product: Wayland Reporter: 67b0226d
Component: westonAssignee: Daniel Stone <daniel>
Status: RESOLVED FIXED QA Contact:
Severity: major    
Priority: medium CC: 67b0226d, bryce, daniel, derekf, giuliocamuffo, jadahl, levans, mailroxas, mgraesslin, ppaalanen, ran234, thiago
Version: unspecified   
Hardware: All   
OS: All   
Whiteboard:
i915 platform: i915 features:
Attachments: Minimal example that makes the keymap invalid
Minimal example that makes the keymap invalid
Reproducer that also tries to reopen /proc/<pid>/<fd> with write access
simple refactor preceding bug fix
Fix to prevent keymap trashing
Reprodurer that also tries to chmod the tmpfile
Patch for KWayland

Description 67b0226d 2017-06-26 10:09:09 UTC
Created attachment 132248 [details]
Minimal example that makes the keymap invalid

The xkb keymap is sent to clients using the wl_keyboard::keymap() event that includes a file descriptor that should be mmap()ed and then given to xkb_keymap_new_from_string.

Although there is commonly no reason to do so, the fd can be mmap()ed with PROT_WRITE and MAP_SHARED flags, so the client can modify it. Since weston only uses one global fd that it shares with all clients, changes to the mmap()ed keymap are visible to other clients until the compositor changes keymap and thus resets the fd. This means that the keymap can be made invalid or replaced altogether. As the change is not announced by the compositor, this will only apply to newly started clients. Qt apps seem to crash at start when the keymap is invalid.

This is definitely a bug and potentially a security hole because the Wayland protocol was designed specifically such that clients cannot interfere with each other.

As a result of discussing this in IRC, the possibilities here seem to be:
* Reopen the file descriptor read-only before handing it out to clients. For this to work, the unlink() call on the temporary file will have to be delayed until after the reopen.
* Use memfd_create() and seal the file descriptor. This requires Linux >= 3.17. memfd is generally desirable because it eliminates the race where other applications can grab the temporary file before it is unlinked.
Comment 1 67b0226d 2017-06-26 10:51:47 UTC
memfd_create solution seems to have the severe problem that file descriptors sealed with F_SEAL_WRITE cannot be mmaped() with MAP_SHARED, even as PROT_READ only. The solution would be to use MAP_PRIVATE, but still many existing clients that use MAP_SHARED (because of copy-paste and because it made no difference previously) would break.
Comment 2 Jonas Ådahl 2017-06-26 12:22:40 UTC
On Fedora 26, I cannot reproduce the keymap corruption with neither weston or mutter given the attached client, as the client fails with a SIGSEGV when trying to write the 'a' into keymap[0]. As far as I can tell, "keymap" points to something looking like a keymap, so it should be pointing to the shared memory.
Comment 3 67b0226d 2017-06-26 12:49:36 UTC
Created attachment 132254 [details]
Minimal example that makes the keymap invalid

Uploaded wrong version of the test program, sorry
Comment 4 Daniel Stone 2017-06-27 11:55:14 UTC
Mutter bug (restricted, has patch): https://bugzilla.gnome.org/show_bug.cgi?id=784206

KDE bug (not yet restricted, no patch): https://bugs.kde.org/show_bug.cgi?id=381674

Enlightenment: reported privately (server down)

wlc: reported privately (no private issue reporting)

QtWayland: reported via mail to security contact

smithay: reported privately (no private issue reporting)

Westeros: attempting to report privately

Chromium: reported privately
Comment 5 Daniel Stone 2017-06-27 12:10:29 UTC
I would like to co-ordinate disclosure/release for all these implementations. If you know of any I've missed, please also feel free to add them here.

The reason is that libxkbcommon's parser is, frankly, awful, and full of holes. I'm up to 10 patches, mostly for NULL dereferences (or very very small offsets to NULL), some infinite recursion, an attempt to free() stack memory (seems bad), one unbounded heap walk which could in theory lead to use of arbitrary memory as a read-only string source, and one out-of-bounds stack dereference whose effect again seems extremely limited (if (stack_var[-12]) output[c] = 'x').

This is what I've found thus far through AFL and libFuzzer/UBSAN/ASAN/MSAN runs, though they are not yet fully eating their own corpus. I've also been manually walking through the parser/lexer and am confident I'll find at least a fair few more by targeting hotspots.

The parser is _extremely_ generous in what it will consume, and there are about a million spots which don't verify that the input is what they would expect. For example, the parser will happily accept a geometry section in the middle of an interpret section.

Given this, I'd like at least a couple of weeks to not only be able to be confident that I've bottomed out the XKB issues, as well as give the fuzzers an extended run at the parser with a much more aggressive corpus.

I've contacted xorg-security@ to request two CVEs: one covering the generic issue of clients given shared writeable FDs for keymaps, and another covering the million xkbcommon vulnerabilities.

My apologies for this: I'd specifically spent some time reading up on fuzzing (and writing parsers in Rust ...) over the past few weeks for fear of this exact same issue, and now it seems that time has arrived. :\
Comment 6 Derek Foreman 2017-06-27 20:49:47 UTC
Nice catch. :(

Reopening the file descriptor read only may present additional hazards on linux if we're still only keeping a single file descriptor in the compositor and sharing it with everyone, even if it is read only.

Once that descriptor is in the hands of a client it, the underlying (unlinked) file may be reopened with write permissions via /proc/self/fd/

Unless proc isn't mounted, or a client is prevented from accessing its own /proc/self/fd/ directory in some way, then a client is still capable of messing with the compositor's keyboard map.

It may be that we need to create a new keymap file per client instead of trying to share one.
Comment 7 Jonas Ådahl 2017-06-28 02:10:29 UTC
Created attachment 132291 [details]
Reproducer that also tries to reopen /proc/<pid>/<fd> with write access

(In reply to Derek Foreman from comment #6)
> Nice catch. :(
> 
> Reopening the file descriptor read only may present additional hazards on
> linux if we're still only keeping a single file descriptor in the compositor
> and sharing it with everyone, even if it is read only.
> 
> Once that descriptor is in the hands of a client it, the underlying
> (unlinked) file may be reopened with write permissions via /proc/self/fd/

Yea, passing a read-only fd doesn't help. I tried run both unsandboxed and from within a flatpak sandbox (by running the new reproducer on a "fixed" mutter in a io.github.GnomeMpv sandbox), and I can see how the keymap still gets permanently trashed for every client after running the reproducer).
Comment 8 Derek Foreman 2017-06-28 18:24:58 UTC
Created attachment 132307 [details] [review]
simple refactor preceding bug fix

Setting up all callers that send keymaps to go through a helper function so we can create unique fds in that helper function.

It doesn't really stand on its own so I've attached it for review here.
Comment 9 Derek Foreman 2017-06-28 18:26:21 UTC
Created attachment 132308 [details] [review]
Fix to prevent keymap trashing

This fixes the problem in my testing, and is robust against jonas' trashkeymapharder reproducer
Comment 10 Derek Foreman 2017-06-28 18:28:57 UTC
I've also fixed this similarly in enlightenment locally.  We've not created a phab ticket for this, as even with restricted permissions I'm not sure what the scope of visibility is.

I'll hold off any public action until Daniel's ready to co-ordinate disclosure.
Comment 11 Martin Flöser 2017-06-28 19:40:01 UTC
I'm going to create a similar patch for KWayland. Don't know yet how to handle the review for it as in KDE infrastructure everything is open (we have neither restricted bug reports, nor phab requests) and on the other hand frameworks (which KWayland is part of) has a requirement for code review.
Comment 12 Daniel Stone 2017-06-28 23:47:49 UTC
(In reply to Martin Gräßlin from comment #11)
> I'm going to create a similar patch for KWayland. Don't know yet how to
> handle the review for it as in KDE infrastructure everything is open (we
> have neither restricted bug reports, nor phab requests) and on the other
> hand frameworks (which KWayland is part of) has a requirement for code
> review.

Phabricator does allow restriction of tasks to groups, but restricting code review changes is far more difficult. If you want to share something here, I will review it in context; if you have additional reviewers, I'd recommend either inviting them here or sharing it directly with them.
Comment 13 Thiago Macieira 2017-06-29 00:38:41 UTC
A different solution: if the file descriptor is pointing to an actual file, you can make the file read-only. This can be accomplished atomically with:

  open(name, O_RDWR | O_CREAT | O_EXCL, 0444);

The file descriptor will be open for writing into a file that no one else can open for writing, not even via reopening via /proc/<n>/fd. The compositor will need to reopen as read-only so that fd can be shared without passing on the write permissions.

The above also works for O_TMPFILE.

It does not work for memfd, since the "file" that backs memfd is created with write permissions. I recommend either changing the kernel to make reopening read-only (or unreadable) or adding an extra flag or parameter so that the permissions can be set.
Comment 14 Derek Foreman 2017-06-29 01:22:17 UTC
I'm not up to speed on how applications will be sandboxed - if a client is the same uid as the compositor, all it would have to do is chmod(2) /proc/self/fd/whatever and add back the appropriate permissions before the open()


Are we concerned about the case where the compositor and client are the same uid?
Comment 15 Jonas Ådahl 2017-06-29 02:14:42 UTC
Created attachment 132327 [details]
Reprodurer that also tries to chmod the tmpfile

(In reply to Derek Foreman from comment #14)
> I'm not up to speed on how applications will be sandboxed - if a client is
> the same uid as the compositor, all it would have to do is chmod(2)
> /proc/self/fd/whatever and add back the appropriate permissions before the
> open()
>

This was my concern regarding this too, so I tried. The attached reproducer still works when creating the tmpfile with mode 0444. Works both outside and inside a flatpak sandbox (again, tried with io.github.GnomeMpv since it was relatively small).
Comment 16 Thiago Macieira 2017-06-29 02:30:58 UTC
(In reply to Derek Foreman from comment #14)
> Are we concerned about the case where the compositor and client are the same
> uid?

I wouldn't recommend being so. If the UID is the same, you can just kill(2) the compositor and everyone dies.

There could be an escalation if another process by a different user is also connected to the compositor and, by way of corrupting the file, that process crashes. But I'd argue that the bug lies in the crashing process in the first place. Also, there isn't much in terms of security here, since the current process could also ptrace(2) the compositor and make it send anything to that other user.
Comment 17 Thiago Macieira 2017-06-29 05:01:12 UTC
In case we really want to prevent materialisation of the file from /proc, just use O_EXCL with O_TMPFILE.
Comment 18 Jonas Ådahl 2017-06-29 06:55:06 UTC
(In reply to Thiago Macieira from comment #17)
> In case we really want to prevent materialisation of the file from /proc,
> just use O_EXCL with O_TMPFILE.

How would I create a O_TMPFILE file with I can write to in weston but not in the client but not re-open as read-only (as there is no path to open)?
Comment 19 Thiago Macieira 2017-06-29 07:07:37 UTC
(In reply to Jonas Ådahl from comment #18)
> How would I create a O_TMPFILE file with I can write to in weston but not in
> the client but not re-open as read-only (as there is no path to open)?

weston opens it with open("/tmp", O_RDWR | O_TMPFILE, 0444). This will return a file descriptor with which you can write to the temporary file, but no one else will be able to open it for writing.

To pass it to other processes, you should reopen it O_RDONLY at /proc/self/fd/%d.

Now, a process with suitable permissions can still chmod the /proc/PID/fd/%d file and thus make it writable again. But as I said, processes with that permission can already do damage to your compositor anyway and other applications. This solution works for when the compositor is running as a different user and against accidental mistakes.
Comment 20 Jonas Ådahl 2017-06-29 07:41:56 UTC
(In reply to Thiago Macieira from comment #19)
> (In reply to Jonas Ådahl from comment #18)
> > How would I create a O_TMPFILE file with I can write to in weston but not in
> > the client but not re-open as read-only (as there is no path to open)?
> 
> weston opens it with open("/tmp", O_RDWR | O_TMPFILE, 0444). This will
> return a file descriptor with which you can write to the temporary file, but
> no one else will be able to open it for writing.
> 
> To pass it to other processes, you should reopen it O_RDONLY at
> /proc/self/fd/%d.

Oh, I see. I thought you meant the fd would *not* be materialized from /proc this way, but it still does, just than one doesn't have to unlink() it.

The fact operating on the fd via /proc/<pid>/fd/<fd> is the thing that we can't allow (see below).

> 
> Now, a process with suitable permissions can still chmod the /proc/PID/fd/%d
> file and thus make it writable again. But as I said, processes with that
> permission can already do damage to your compositor anyway and other
> applications. This solution works for when the compositor is running as a
> different user and against accidental mistakes.

But a process that can chmod() and open() /proc/self/fd/<fd> should still not be able to affect other clients or the compositor in any unwanted way. For example in a flatpak sandbox, a process can access /proc/<pid>/fd/* of itself and other processes in the same sandbox environment (which the compositor is not part of) and from there chmod and reopen the any fd at ease.
Comment 21 67b0226d 2017-06-29 09:09:21 UTC
(In reply to Thiago Macieira from comment #16)
> I wouldn't recommend being so. If the UID is the same, you can just kill(2)
> the compositor and everyone dies.
> 
> There could be an escalation if another process by a different user is also
> connected to the compositor and, by way of corrupting the file, that process
> crashes. But I'd argue that the bug lies in the crashing process in the
> first place. Also, there isn't much in terms of security here, since the
> current process could also ptrace(2) the compositor and make it send
> anything to that other user.
ptrace is not the problem, /proc is. While ptrace can be limited by yama_scope (which many distributions already do by default), /proc still allows very potent access to the running environment of other processes including anonymous file descriptors. Why there is no option to restrict this is beyond me, but I guess we won't be able to do anything about it.

Do note that with cgroups, UID being the same does not mean that you can see and/or kill all other processes running under that UID. Main concern here is breaking out of sandboxes by way of the compositor.
Comment 22 Thiago Macieira 2017-06-29 15:14:39 UTC
When cgroups are in action, the /proc entries change too. But just like in the chroot case, I don't know what exactly /proc looks like. I know some things change (/proc/<PID>/mount, cwd, exe, etc.) but I don't know what else is left. This needs some investigation.

And regarding ptrace, I used it as an example. There are other things that a process with the same UID as the compositor may be allowed to do, like kill(2) the compositor, which would be as damaging. This can be solved with cgroups.

I'll take a look at what /proc looks like from a chroot'ed and changed user namespace.
Comment 23 Daniel Stone 2017-06-29 15:46:30 UTC
It's not just chroot. If you're in a new PID namespace, you can't try to ptrace the compositor, access it via /proc, etc.
Comment 24 Thiago Macieira 2017-06-29 16:35:18 UTC
https://github.com/flatpak/flatpak/wiki/Sandbox says "/proc shows only the processes in the app sandbox" so in a flatpak's sandbox, you don't see the /proc/PID of the compositor in the first place. That happens because Flatpak unshares the PID namespace.

If you don't unshare that, you can still see the /proc/PID of the compositor and, in turn, kill the compositor. ptracing the compositor does not work, though.

My conclusion is still that if you can see the /proc/PID of the compositor, you haven't secured enough and therefore we do not need to protect against the chmod. Separate file descriptors do not protect against a chmod+open attack anyway, if the file is open.
Comment 25 Derek Foreman 2017-06-29 17:58:25 UTC
Thiago, sorry if I misunderstand what you're saying...

I don't need access to /proc/<compositor_pid>/fd at all if the compositor is running as the same uid as the client.  I can chmod the fd in my own /proc/self/fd directory, because it was created with my uid, and therefore I have permission to do so (even if it's already unlinked).

The attempted fix I've provided for weston creates a new file for every wl_keyboard_send_keymap() event - and the client is free to mess with it in any way they choose because it's no longer the compositor's shared copy for all clients.

If the proc space isn't properly unshared a client could mess with these files at creation time during the race between mkostemp and unlink - I completely agree with your conclusion that seeing the compositors /proc/PID indicates an environment that isn't secured enough.

But I think file permissions won't help us because even when sandboxed we're probably the legitimate owner of the file.
Comment 26 Thiago Macieira 2017-06-29 20:51:54 UTC
(In reply to Derek Foreman from comment #25)
> Thiago, sorry if I misunderstand what you're saying...
> 
> I don't need access to /proc/<compositor_pid>/fd at all if the compositor is
> running as the same uid as the client.  I can chmod the fd in my own
> /proc/self/fd directory, because it was created with my uid, and therefore I
> have permission to do so (even if it's already unlinked).

Oh, I see. Right, I had not thought of the /proc/self case from the client. You're completely right, my suggestion won't work.

Back to having different files/memfds per client.
Comment 27 Martin Flöser 2017-06-30 18:33:22 UTC
Created attachment 132375 [details] [review]
Patch for KWayland

I created a patch for KDE's Wayland server library. For references it's attached. I'm going to push as soon as Daniel is ready for disclosure.
Comment 28 Daniel Stone 2017-06-30 19:30:55 UTC
Thanks Martin. I'm still bottoming out the xkbcommon side of things, which will take a while still. I'll aim for end-of-July disclosure, to give two weeks for further fixes and a more targeted fuzzing run, plus a week or two for distributions to prepare.
Comment 29 67b0226d 2017-10-10 17:51:44 UTC
Friendly ping - any update on this? End of July was some time ago.
Comment 30 67b0226d 2018-02-14 10:58:32 UTC
Hi again, can we at least land the patches that we have already?
Daniel, what's your current ETA on the xkbcommon stuff?
Comment 31 Martin Flöser 2018-02-14 18:03:26 UTC
> Hi again, can we at least land the patches that we have already?

+ 1, I would also like to land the patch.
Comment 32 Derek Foreman 2018-03-27 14:48:13 UTC
It looks like we're all pretty eager to land this...

weston 4.0 final is coming up quickly (rc1 this coming monday) and it would be really nice if we could include this fix.

Would everyone be ok with that?
Comment 33 Derek Foreman 2018-04-17 16:16:33 UTC
This is beginning to approach its first birthday, what needs to be done before we can land patches?
Comment 34 Daniel Stone 2018-06-04 06:50:43 UTC
(In reply to Derek Foreman from comment #33)
> This is beginning to approach its first birthday, what needs to be done
> before we can land patches?

Nothing from my side. Jonas, Martin, any idea on disclosure timeline?
Comment 35 Martin Flöser 2018-06-05 18:34:33 UTC
From my side the disclosure can happen any time. As in KDE's case Frameworks is affected we need at max one month till a public release. The patch is prepared. Personally I tend towards doing a public review once it's disclosed.
Comment 36 Jonas Ådahl 2018-06-05 19:15:50 UTC
I don't have an opinion about a disclosure timeline, but I guess there is no point in waiting for too long. FWIW, the patch in mutter is reviewed and ready to learn.
Comment 37 Derek Foreman 2018-08-17 14:32:45 UTC
Ok, have just landed the weston bits for this.
Comment 38 Jonas Ådahl 2018-08-17 14:47:06 UTC
Landed the fix in mutter too.
Comment 39 Daniel Stone 2019-04-05 10:34:16 UTC
Marking this as fixed, since the fixes have landed everywhere now.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.