December 12, 2019

Whoopsie-daisy: Chaining accidental features of Ubuntu’s crash reporter to get LPE

Kevin Backhouse

Severity and mitigation

This post is an overview of five vulnerabilities that I found in Ubuntu's crash reporting system: CVE-2019-7307, CVE-2019-11476, CVE-2019-11481, CVE-2019-11484, CVE-2019-15790. Two of those vulnerabilities, CVE-2019-11476 and CVE-2019-11481, are low-severity local denial-of-service of vulnerabilities, but the remaining three are significantly more serious. When chained together, these vulnerabilities allow a local unprivileged attacker to read arbitrary files on the system. In other words, they combine to create a read-only local privilege escalation vulnerability. This means an attacker could exploit these vulnerabilities to steal important information, such as the SSH keys of other users. One of the five vulnerabilities, CVE-2019-15790, is also reusable in other exploit chains. It enables an attacker to obtain the ASLR offsets for any process that they can start (or restart). While this isn't particularly useful by itself, if you find a memory corruption vulnerability in a system service, you're more likely to be able to exploit it if you have access to its ASLR offsets. As an example of this, I was initially unable to get anything beyond a denial-of-service from CVE-2019-11484, but with the help of CVE-2019-15790, I was able to get code execution.

The original bug reports for each of the five vulnerabilities are now publicly visible on Ubuntu's bug tracking site:

The first two vulnerabilities were fixed on July 9, 2019. The remaining three were fixed on October 29, 2019. The full LPE exploit chain relies on CVE-2019-7307, which was fixed on July 9, so the vulnerabilities that were fixed on October 29 were already partially mitigated for anyone who had kept their system up-to-date. If you haven't already done so, please make sure that you have upgraded to the latest versions of apport and whoopsie.

Overview

Let's review the architecture of Ubuntu's crash reporting system. Later, I'll follow up with additional posts covering the three most serious vulnerabilities:

Crash reporting in Ubuntu

To better understand the vulnerabilities, it's helpful to know about the architecture of Ubuntu's crash reporting system. It consists of several distinct components. You may be familiar with this component if you're an Ubuntu user:

chrome has closed unexpectedly

That dialog box is apport-gtk. Although it's the most visible component of the crash reporting system, it's arguably the least interesting from a security perspective. First, it doesn't run with elevated privileges. It can only read crash reports that are owned by the current user. And, in cases where a system process or a process belonging to another user crashes, the dialog box doesn't appear. Second, despite appearances, it isn't responsible for uploading the crash report. If you click Send, it creates a file with the extension .upload in the directory /var/crash as a signal that the report should be uploaded.

Let's deliberately crash a program and see what happens:

kev@constellation:~$ sleep 60s &
[1] 4268
kev@constellation:~$ kill -SIGSEGV 4268
kev@constellation:~$
[1]+  Segmentation fault      (core dumped) sleep 60s
kev@constellation:~$

In the example, I started /bin/sleep and used kill to crash it with a segmentation fault. The following diagram illustrates what happens next:

The crash is handled initially by the kernel. Then, the kernel reads the core_pattern file to determine what it should do with the core dump. On a default Ubuntu installation, the core_pattern file looks like this:

kev@constellation:~$ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P

The pipe symbol at the beginning of the file indicates that the kernel should pipe the core dump to /usr/share/apport/apport. apport is Python program, which runs with root privileges (although it drops privileges later). Its job is to create a crash report and write it to the directory /var/crash. In this example, the crash report looks like this:

kev@constellation:~$ ls -l /var/crash/
total 32
-rw-r----- 1 kev whoopsie 32211 Oct 24 12:30 _bin_sleep.1001.crash
kev@constellation:~$

The directory /var/crash is the communication hub of the crash reporting system. The other components communicate with each other by writing files in /var/crash. Both apport-gtk and whoopsie monitor /var/crash for new files. whoopsie is responsible for uploading the crash report to daisy.ubuntu.com, but it doesn't do so until it sees a file with the extension .upload. This means that when you click Send, apport-gtk only creates an empty file with the .upload extension. That's the trigger for whoopsie to parse the crash report and upload it to daisy.ubuntu.com.1

Security boundaries in the crash reporter

I like how Ubuntu's crash reporting system has been designed. The separation into multiple components minimizes the amount of code that needs to run with root privileges. I also like the fact that the UI component, apport-gtk, does not connect to the internet. It's an interesting contrast to the Windows crash reporting system, which also had a privilege escalation vulnerability earlier this year: CVE-2019-0863, found by Gal De Leon of Palo Alto Networks. In a blog post about the vulnerability, Gal De Leon explains that the component responsible for uploading crash reports, wermger.exe, runs with system privileges. In contrast, on Ubuntu the only component which runs as root is apport.

The diagram I've provided has boxes to indicate the privilege levels of different components. apport-gtk, on the left, runs as the current user and has no special privileges. apport, on the right, runs as root but doesn't interact directly with either the UI or the internet. whoopsie, in the middle, is a daemon process running as "whoopsie", which is a system user with limited privileges. It interacts with the internet (daisy.ubuntu.com), but not with the UI.

Properties of /var/crash

As mentioned, the directory /var/crash is the communication hub of the crash reporting system. To enable this, it has the SGID and sticky bits set, like the following:

kev@constellation:~$ ls -al /var/crash/
total 48
drwxrwsrwt  2 root whoopsie 12288 Oct 25 09:10 .
drwxr-xr-x 17 root root      4096 Jul 17 19:31 ..
-rw-r-----  1 kev  whoopsie 32211 Oct 24 12:30 _bin_sleep.1001.crash
kev@constellation:~$

The SGID bit means that any file written to /var/crash becomes owned by the whoopsie group, which enables the whoopsie daemon to read it. The sticky bit prevents other users from deleting or renaming crash reports that don't belong to them.

Attack surface of the crash reporter

Since the crash reporter has a good architecture with clear security boundaries, where's the attack surface? The most critical component is apport, because it runs as root. At first glance, it doesn't seem to have an attack surface, because it's invoked by the kernel and doesn't interact directly with either the UI or the internet. But, in some ways it's similar to a setuid binary because it can be invoked by any user. (All you have to do is send a SIGSEGV to a process like I did previously with /bin/sleep.) And it reads numerous files, some of which are configuration files in the user's home directory. All of the vulnerabilities that I found in apport involve tricking it into using its root privileges to read a file that I don't have permission to access. In two cases (CVE-2019-7307 and CVE-2019-15790) I was also able to trick it into including the contents of that file in a crash report.

While working on exploits for the vulnerabilities, I discovered that apport has another type of attack surface: timing. To get the exploits to work, I often needed to control the timing of apport so that certain events would happen in a specific order. First, I found that I can observe the timing of apport by watching the files it accesses, and use that information to trigger key events at precisely the right time. Second, I discovered several ways that I can pause apport during its execution. One technique is to acquire a file lock on /var/crash/.lock, which causes apport to pause when it starts. Another is to send it a SIGSTOP signal. Being able to send apport a SIGSTOP is an interesting consequence of apport dropping privileges during its execution. Apport drops privileges as a security precaution: the less time it spends running as root, the safer. But, ironically, that enables me to send it signals, which I couldn't do if it remained root.

At first glance, the whoopsie daemon appears to be rather uninteresting from a security perspective. It reads the crash reports in /var/crash and uploads them to daisy.ubuntu.com. It runs as the whoopsie user, which has very few privileges. In fact, the only reason that I became interested in whoopsie is that it can read all the crash reports, even those generated by root processes. My exploit for CVE-2019-7307 can read any file on the system and include its contents in a crash report. But that crash report is only readable by root and whoopsie. I looked into whether I might be able to trick whoopsie into uploading the crash report to a different URL than daisy.ubuntu.com, but concluded that it would be impossible, particularly because whoopsie uses libcurl with the CURLOPT_SSL_VERIFYPEER option for the upload, as you can see at whoopsie.c:326:

curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, verifypeer);

So to complete the exploit chain, I needed a way to run code as whoopsie, which I eventually achieved by chaining CVE-2019-11484 and CVE-2019-15790. The former is a heap buffer overflow in whoopsie, triggered by creating a malicious crash report in /var/crash. The latter is an information disclosure vulnerability in apport which enables me to defeat whoopsie's ASLR.

Timeline

To be continued ...

Stay tuned for the next three posts in this series:


  1. You might be interested to find out how many crash reports your system has uploaded to daisy.ubuntu.com. I found a comment on whoopsie's bug tracker, which explains how to find this information. First, you need to find your whoopsie ID, as follows: sudo cat /var/lib/whoopsie/whoopsie-id. Then, visit https://errors.ubuntu.com/user/ID, with your whoopsie ID substituted for ID. You should see a list of the reports that your system has uploaded, but you won't be able to view their contents unless you've been granted special access by Ubuntu.