User to root privilege escalation (ab)using the crash forwarding feature of apport

Bug #1438758 reported by Stéphane Graber on 2015-03-31
260
This bug affects 1 person
Affects Status Importance Assigned to Milestone
Apport
Critical
Martin Pitt
apport (Debian)
Fix Released
Undecided
Ritesh Raj Sarraf
apport (Ubuntu)
Critical
Unassigned
Trusty
Critical
Unassigned
Utopic
Critical
Unassigned
Vivid
Critical
Unassigned

Bug Description

Back in Ubuntu 14.04, I introduced an apport feature that will have it forward any crash to another apport running in the task's namespace (in the case where the pid of the task in its namespace isn't equal to that in the host namespace).

This feature simply checks for the presence of /usr/share/apport/apport in the task's root directory. If it exists, it will chroot and exec the script.

The problem is that as apport is a coredump handler triggered by the kernel, it'll always run as real root, regardless of the crashed task's owner and namespace.

This therefore allows an unprivileged user to craft a specific filesystem structure, pivot_root to it, then crash a process inside it, causing apport outside of the namespace to execute a script as real root. By bind-mounting /proc from the host into that namespace, the unprivileged user can then access any file on the host as real root, causing the privilege escalation.

An exploit is attached to this bug. It's been confirmed to be runnable as a nobody user on a regular Ubuntu system and to successfully read any file on the host.

Related branches

CVE References

Stéphane Graber (stgraber) wrote :
Stéphane Graber (stgraber) wrote :

The fix for this issue will be to have apport do the following tasks prior to executing the crash handler in the container:
 - replicate the task's apparmor profile
 - attach to all namespaces (setns)
 - seteuid and setegid to 0 of that namespace
 - sanitize the fd list (only 0, 1 and 2, all pointing to /dev/null)

Stéphane Graber (stgraber) wrote :

After thinking about it some more, the above solution will still let the user bypass various protections (seccomp, capability drop, ...) and implementing all of that in apport is starting to feel a bit ridiculous.

Instead I'll work on a different patch to apport which will have it attach to the namespaces it requires to generate the crash file, but not actually run any code from within the container. That should give us the right result which is a valid crash file appearing in /var/crash of the container but no untrusted code being executed from the outside.

Stéphane Graber (stgraber) wrote :

Turns out that option is also way too difficult to implement short of rewriting a quarter of apport.

So instead I'll look into a lxc-specific solution using our existing attach mechanism which is safe.

Stéphane Graber (stgraber) wrote :

Finally got something working.

This patch will load python3-lxc if present, then extract the list of all running containers based on available LXC command sockets, for each of those it'll then attempt to match the namespaces of the crashed task with the container. If a match is found, apport is then run inside that container.

As this now uses the LXC API, this guarantees the apparmor profile is respected, as well as seccomp, selinux, capabilities, personalities and any other security mechanism provided by LXC. The environment is also completely cleared and LXC closes any non-standard fd before execcing the command.

I've confirmed this to be working here. The exploit I posted earlier is now detected as an unknown container and skipped, proper containers with apport present inside get a proper apport call with a valid /var/crash entry as a result.

Changed in apport (Ubuntu Trusty):
status: New → Triaged
Changed in apport (Ubuntu Utopic):
status: New → Triaged
Changed in apport (Ubuntu Trusty):
importance: Undecided → Critical
Changed in apport (Ubuntu Utopic):
importance: Undecided → Critical
Stéphane Graber (stgraber) wrote :

Martin: Can you review this patch please?

Oh, one note. I was hoping not to have to set LANG to en_US.UTF-8 but unfortunately the kernel spawns apport with a pretty much clean environment (makes sense) so I can't inherit LANG from the parent apport and calling the container apport without LANG set leads to UTF-8 encoding errors on the apport end. As C.UTF-8 isn't widely available, en_US.UTF-8 is usually a much safer bet.
I'm very open to cleaner alternatives :)

Martin Pitt (pitti) wrote :

Just a quick drive-by review: Why do you need to set $LANG in the first place if the "outside" apport has never needed it? If there's an encoding error, we should fix that instead; do you have a stack trace?

en_US.UTF-8 isn't guaranteed to be available; C.UTF-8 ought to be available everywhere, so if we absolutely must hardcode a locale, it should be C.UTF-8. But let's try to avoid that.

+ if path[-1] != "command":
+ continue

What's that magic "command" string here?

(stylistic nitpick: single quotes everywhere, please; but I can do that on merging into trunk)

+ os.environ['HOME'] = '/root'

I believe that ought to be '/'? it's not necessarily the case that /root exists.

the python3-lxc dependency is quite heavy; is that always save to use within the rather minimal context of apport (as being called by the kernel -- i. e. no environment, no association to any user, always has root privileges), and will never block unnecessarily? Would calling lxc-attach directly be a lighter alternative here?

Thanks!

Stéphane Graber (stgraber) wrote :

Hi Martin,

I'll get you the stack trace when I'm back on my laptop a bit later.

Agreed about C.UTF-8 if we know it's going to be around on all systems running 14.04 and higher (can't remember when it was actually introduced in glibc).

As for the command stuff, that's because LXC abstract sockets are always:
 @<lxcpath>/<container name>/command

So all LXC abstract sockets are guaranteed to end with /command, the rest of the path is user controlled.

As for HOME, I guess we can take it out entirely too, I don't believe it's actually needed here, I just tend to always set PATH and HOME after I clear the environment, but not having it should be fine too.

We shouldn't depend on python3-lxc, only use it if it's available on the system, which it will on any system with LXC installed as lxc-ls uses it. I actually expect going through python3-lxc to be significantly faster than shelling out to a binary which ends up calling the exact same API call. Using the python3 binding also allows us to do some sanity checks before interacting with a container (the may_control and state part).

Note that my patch properly deals with the case where python3-lxc isn't available and also only imports it in the case where the crash comes from a container, so it shouldn't impact apport's performance at all in the standard case.

Stéphane Graber (stgraber) wrote :

ERROR: apport (pid 2562) Wed Apr 1 04:35:30 2015: called for pid 2560, signal 11, core limit 0
ERROR: apport (pid 2562) Wed Apr 1 04:35:30 2015: executable: /bin/sleep (command line "sleep 30")
ERROR: apport (pid 2562) Wed Apr 1 04:35:30 2015: Unhandled exception:
Traceback (most recent call last):
  File "/usr/share/apport/apport", line 391, in <module>
    if is_closing_session(pid, pidstat.st_uid):
  File "/usr/share/apport/apport", line 213, in is_closing_session
    env = e.read().split('\0')
  File "/usr/lib/python3.4/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1732: ordinal not in range(128)
ERROR: apport (pid 2562) Wed Apr 1 04:35:30 2015: pid: 2562, uid: 0, gid: 0, euid: 0, egid: 0
ERROR: apport (pid 2562) Wed Apr 1 04:35:30 2015: environment: environ({'PATH': '/usr/sbin:/usr/bin:/sbin:/bin', 'container': 'lxc'})

Steve Beattie (sbeattie) wrote :

Marc assigned this as CVE-2015-1318.

Martin Pitt (pitti) wrote :

I fixed the UnicodeDecodeError in http://bazaar.launchpad.net/~apport-hackers/apport/trunk/revision/2940 . I guess you have some environment variable with a non-ascii value (like "Stéphane" ☺). Thanks for the other explanations!

So with that I guess we can drop the hardcoded locale setting?

Stéphane Graber (stgraber) wrote :

Yeah, DEBFULLNAME contains non-ascii chars :)

Stéphane Graber (stgraber) wrote :

I'll prepare the three debdiffs (trusty, utopic, vivid) including a cleaned up version of my patch per above comments and a cherry-pick of Martin's unicode patch.

Stéphane Graber (stgraber) wrote :

given that this gives instant root to anyone on trusty or higher, I suspect we want to push all 3 of them at the same time and before committing the fix upstream. I suspect Martin will also want to tag a new upstream release with the fix very soon after that.

Marc Deslauriers (mdeslaur) wrote :

ACK, I'll prepare the security updates once the debdiffs are ready.

Martin Pitt (pitti) wrote :

> I suspect Martin will also want to tag a new upstream release with the fix very soon after that.

Correct. Can we make this public after the Easter holidays? I took an extra holiday tomorrow, and Fri/Mon are national holidays. Given for how long this has existed, pushing this out on Tuesday might suffice?

Marc Deslauriers (mdeslaur) wrote :

FIne by me.

Marc Deslauriers (mdeslaur) wrote :

Fine by me

Stéphane Graber (stgraber) wrote :

debdiffs attached. Doing test builds now.

Martin Pitt (pitti) wrote :

Note that for upstream I'd also like to integrate the exploit into the tests. I ported the exploit to python and to apport's tests and simplified it a bit. It can be run by itself with

  test/run signal_crashes.test_ns_forward_privilege

Stéphane Graber (stgraber) wrote :

Doh, we'll need another round...

Stéphane Graber (stgraber) wrote :

Actually, I just thought of a way to attack my current fix with a crafted /command abstract socket. I'll update my debdiffs to include a fix (privilege drop to the process uid/gid of the socket owner).

Stéphane Graber (stgraber) wrote :

new debdiffs restrict the feature to root-spawned containers.

Martin Pitt (pitti) wrote :

There is a slight error in def get_container():

Traceback (most recent call last):
  File "/usr/share/apport/apport", line 345, in <module>
    container, owner = get_container(host_pid)
TypeError: 'NoneType' object is not iterable

In the failure case it needs to

            return (None, None)

not just "None". I fixed that in the patch which I'll push to trunk as soon as this gets released.

This is the complete patch including (fixed) test case and NEWS entry that I'm going to push.

Martin Pitt (pitti) wrote :

@Ritesh: This is a security vulnerability in Apport >= 2.13 which also affects Debian. I'm subscribing you to warn you in advance, so that you can provide a timely update, by either packaging the upcoming 2.17.1 upstream release or applying the patches above.
THIS IS STILL CONFIDENTIAL! Please do not leak anything about this at any place other than this bug report. The Ubuntu security team will coordinate a publication/release time.

Changed in apport:
status: New → In Progress
assignee: nobody → Martin Pitt (pitti)
Changed in apport (Debian):
assignee: nobody → Ritesh Raj Sarraf (rrs)
Changed in apport:
importance: Undecided → Critical

On Wednesday 08 April 2015 10:56 AM, Martin Pitt wrote:
> @Ritesh: This is a security vulnerability in Apport >= 2.13 which also affects Debian. I'm subscribing you to warn you in advance, so that you can provide a timely update, by either packaging the upcoming 2.17.1 upstream release or applying the patches above.
> THIS IS STILL CONFIDENTIAL! Please do not leak anything about this at any place other than this bug report. The Ubuntu security team will coordinate a publication/release time.
Thanks for the heads up Martin. For Debian, we'll work on pushing the
newer 2.17.1 version, asap, when it is released.

--
Ritesh Raj Sarraf
RESEARCHUT - http://www.researchut.com
"Necessity is the mother of invention."

Stéphane Graber (stgraber) wrote :

Thanks Martin. I've fixed that issue and update the debdiffs.

Stéphane Graber (stgraber) wrote :
Stéphane Graber (stgraber) wrote :
Stéphane Graber (stgraber) wrote :
Marc Deslauriers (mdeslaur) wrote :

I plan on publishing this update on 2015-04-14.

information type: Private Security → Public Security
Changed in apport (Ubuntu Trusty):
status: Triaged → Fix Released
Changed in apport (Ubuntu Utopic):
status: Triaged → Fix Released
Martin Pitt (pitti) wrote :
Changed in apport:
status: In Progress → Fix Committed
Martin Pitt (pitti) wrote :

New upstream release containing this fix: https://launchpad.net/apport/trunk/2.17.1

Ritesh, you can go ahead with the Debian update now.

Changed in apport:
status: Fix Committed → Fix Released
Changed in apport (Ubuntu Vivid):
status: Triaged → In Progress
assignee: nobody → Martin Pitt (pitti)
Martin Pitt (pitti) wrote :

vivid update uploaded.

Changed in apport (Ubuntu Vivid):
assignee: Martin Pitt (pitti) → nobody
status: In Progress → Fix Committed
tags: added: patch
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package apport - 2.17.1-0ubuntu1

---------------
apport (2.17.1-0ubuntu1) vivid; urgency=medium

  * New upstream bug fix release:
    - SECURITY UPDATE: Fix root privilege escalation through crash forwarding
      to containers.
      Version 2.13 introduced forwarding a crash to a container's apport. By
      crafting a specific file system structure, entering it as a namespace
      ("container"), and crashing something in it, a local user could access
      arbitrary files on the host system with root privileges.
      Thanks to Stéphane Graber for discovering and fixing this!
      (CVE-2015-1318, LP: #1438758)
    - apport-kde tests: Fix imports to make tests work again.
    - Fix UnicodeDecodeError on parsing non-ASCII environment variables.
    - apport: use the proper pid when calling apport in another PID namespace.
      Thanks Brian Murray. (LP: #1300235)
 -- Martin Pitt <email address hidden> Tue, 14 Apr 2015 09:10:17 -0500

Changed in apport (Ubuntu Vivid):
status: Fix Committed → Fix Released
Ritesh Raj Sarraf (rrs) wrote :

This was fixed in the upload of apport 2.17.3-1 to Debian Experimental

Changed in apport (Debian):
status: New → 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

Bug attachments