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

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)

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.

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.

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 :)

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?

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.

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.

> 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?

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).

@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.

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.