root escalation via race condition

Bug #1453900 reported by Marc Deslauriers
260
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Apport
Fix Released
Critical
Unassigned
apport (Ubuntu)
Fix Released
Critical
Martin Pitt

Bug Description

Philip Pettersson reported the following apport security issue:

Original email:
--------------------

Summary
--------------------------------
All (?) versions of the bug reporting program "apport" suffer from a
race condition that allows unprivileged users to create coredumps in
directories owned by root.

This is unrelated to the namespace bug found last month by Tavis Ormandy.

Software description
--------------------------------
apport - automatically generate crash reports for debugging

Affected distributions
--------------------------------
All versions of Ubuntu Server/Desktop since at least 12.04.
This bug should be present in any version of ubuntu that has apport
enabled in /usr/share/apport/apport.

Details
--------------------------------
When a process receives a signal that should generate a coredump,
/usr/share/apport/apport will be invoked by the kernel as root.

On line 284, apport "partially drops privileged":
 drop_privileges(pid, True)

However, this has no real security benefit since the euid of the
process will still be root.

On line 394 apport opens a file in /var/crash:
 with open(report, 'rb') as f:

"report" is the filename, which can be easily predicted.
If a user with uid 1000 makes /bin/sleep crash, the filename will be:
/var/crash/_bin_sleep.1000.crash

The directory /var/crash is world writable.

If we create a fifo in this location before making our program crash,
apport will hang on line 394 until a report is written to that fifo by us.

When apport is in this paused state, we can kill our original process
and keep forking() until we get the same pid again. We then make this process
execute /bin/su which makes our original pid a root process.

The drop_privileges() function on line 49 incorrectly uses the pid
as the indicator as to which uid we should drop privileges to. We can
therefore make apport "drop" privileges to uid 0 and write a corefile
anywhere on the system.

This can be used to write a corefile with crafted contents in /etc/cron.d,
/etc/logrotate.d and so on to gain root privileges.

Additionally, on versions since at least Ubuntu 14.04 is it possible to
completely control the contents of the written corefile. This allows easy
expoitation by leveraging /etc/sudoers.d.

Proof of concept exploit flow
--------------------------------
The partial privilege drop on line 284 allows us to send SIGSTOP to apport, which allows us great
control over the execution flow. On line 460 apport will ultimately write
the corefile contents by reading from the report file in /var/crash.

1. Create the fifo /var/crash/_bin_sleep.$uid.crash
2. Spawn a process, chdir("/etc/sudoers.d") and send it SIGSEGV
3. Send SIGKILL to the process in (2), fork() until we get the same pid
   as the process we killed.
4. In our new process, execute /bin/su
5. Send valid report data to /var/crash/_bin_sleep.$uid.crash
6. Core file is written to /etc/sudoers.d/core as root with perms 0600.

Additionally, on 14.04+ we can do this:
7. Keep sending SIGSTOP/SIGCONT until these lines have been executed:
   404: os.unlink(report)
   410: reportfile = os.fdopen(os.open(report, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0), 'wb')
8. Unlink /var/crash/_bin_sleep.$uid.crash
9. Create fifo in /var/crash/_bin_sleep.$uid.crash
10. Write crafted contents to /var/crash/_bin_sleep.$uid.crash
11. Apport will read our fifo at line 155 and create a corefile with our
    contents.

Suggested fixes
--------------------------------
1. Remove the partial privilege drop. It serves no security purpose and allows
the user to send SIGSTOP to apport, which enables race conditions.

2. Save the uid of the pid at the beginning, do not rely on the pid more than
once since the actual process can change during the course of execution.

3. Drop privileges completely as soon as possible.

4. Make sure the report files are actual files and not FIFOs.

Credit
--------------------------------
Philip Pettersson, Samsung Security Center

---------------------------------

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

This is CVE-2015-1325

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

I've subscribed the original reporter to this bug.

Please refrain from discussing this issue publicly until we have a fix available and have decided on a coordinated release date.

Martin Pitt (pitti)
Changed in apport (Ubuntu):
status: New → In Progress
importance: Undecided → Critical
assignee: nobody → Martin Pitt (pitti)
Revision history for this message
Martin Pitt (pitti) wrote :

The PoC does not actually work for me in its entirety as user process can't chdir("/etc/sudoers.d"), the dir is root:root 750. However, if I chmod 755 the dir, it does work. And either way, it always gets far enough to write a crafted core dump. So I have enough to go on with.

Changed in apport:
status: New → In Progress
importance: Undecided → Critical
Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

CRD for this issue is 2015-05-21 17:00:00 UTC

Revision history for this message
Philip Pettersson (p-pettersson-u) wrote :

Martin, what distro version are you running? I confirmed that /etc/sudoers.d is 755 by default on 12.04, 14.04 and 15.04. There are still lots of other vectors for exploitation though, the most reliable being /etc/logrotate.d. But it only runs once per day so you need a bit of patience..

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

/etc/sudoers.d is 755 for me also on 15.04.

Revision history for this message
Martin Pitt (pitti) wrote :

Hm, could it be that it's different on cloud images? But either way, details. We have a lot of similarly effective ones, like /etc/cron.d/.

Revision history for this message
Martin Pitt (pitti) wrote :

With the fixes in bug 1452239 apport drops its privileges more properly and thus it cannot actually write core dumps into directories not writable by the user. This foils this attach already. However, I still want to make this more robust, manipulating the written core dump is still evil.

> 1. Remove the partial privilege drop.

I would still like to keep this. This will do all the file system operations as the user, not root.

> 2. Save the uid of the pid at the beginning, do not rely on the pid more than once since the actual process can change during the course of execution.

Agreed.

> 3. Drop privileges completely as soon as possible.

It has always meant to do that, except that it does it wrongly in the current code. This got fixed in bug 1452239.

> 4. Make sure the report files are actual files and not FIFOs.

Any kind of stat() test sounds like a TOCTOU race. Non-ancient versions already open the report file the proper way with os.O_WRONLY | os.O_CREAT | os.O_EXCL which should be proof against race conditions, symlink attacks, and other pre-existing files. The thing I want/need to fix is to avoid closing and reopening it for the core dump -- instead I'll use O_RDWR, keep the fd open, and read the core dump from the written file. Then other processes can mess around with the directory entries all they want.

Thanks for discovering this! This is quite a clever attack.

Revision history for this message
Martin Pitt (pitti) wrote :

This is the patch against trunk, including a test case/reproducer. The /proc reading robustifications are better suited to go into the patch for bug 1452239, thus I'll update the trunk patch there. Also, for the Ubuntu packages in stable releases I'll update the debdiffs in that bug to include both fixes.

Revision history for this message
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package apport - 2.14.7-0ubuntu8.5

---------------
apport (2.14.7-0ubuntu8.5) utopic-security; urgency=medium

  * SECURITY UPDATE: When /proc/sys/fs/suid_dumpable is enabled, crashing a
    program that is suid root or not readable for the user would create
    root-owned core files in the current directory of that program. Creating
    specially crafted core files in /etc/logrotate.d or similar could then
    lead to arbitrary code execution with root privileges. Now core files do
    not get written for these kinds of programs, in accordance with the
    intention of core(5).
    Thanks to Sander Bos for discovering this issue!
    (CVE-2015-1324, LP: #1452239)
  * SECURITY UPDATE: When writing a core dump file for a crashed packaged
    program, don't close and reopen the .crash report file but just rewind and
    re-read it. This prevents the user from modifying the .crash report file
    while "apport" is running to inject data and creating crafted core dump
    files. In conjunction with the above vulnerability of writing core dump
    files to arbitrary directories this could be exploited to gain root
    privileges.
    Thanks to Philip Pettersson for discovering this issue!
    (CVE-2015-1325, LP: #1453900)
  * test_signal_crashes(): Drop hardcoded /tmp/ path in do_crash(),
    test_nonwritable_cwd() uses a different dir.

 -- Martin Pitt <email address hidden> Wed, 13 May 2015 11:59:03 +0200

Changed in apport (Ubuntu):
status: In Progress → Fix Released
Revision history for this message
Martin Pitt (pitti) wrote :
Changed in apport:
status: In Progress → Fix Released
information type: Private Security → Public Security
To post a comment you must log in.
This report contains Public Security information  
Everyone can see this security related information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.