3 ms·
This is interesting, but stops just short of explaining why ptrace wasn't whitelisted, and what changed that allows it to be whitelisted on recent kernel versio
by roblabla 6y ago
This is interesting, but stops just short of explaining why ptrace wasn't whitelisted, and what changed that allows it to be whitelisted on recent kernel versions.
Here's what the linked commit to docker[0] says:
> 4.8+ kernels have fixed the ptrace security issues so we can allow ptrace(2) on the default seccomp profile if we do the kernel version check.
This commit itself links a commit in the linux kernel[1], which says:
> x86/ptrace: run seccomp after ptrace
> This moves seccomp after ptrace on x86 to that seccomp can catch changes made by ptrace. Emulation should skip the rest of processing too.
This doesn't give us much more information, and I'm not familiar enough with this code to understand the changeset. Thankfully, by looking up the changeset name, we can easily find the email that proposed the changeset[2], which explains the issue more in-depth:
> There has been a long-standing (and documented) issue with seccomp where ptrace can be used to change a syscall out from under seccomp. This is a problem for containers and other wider seccomp filtered environments where ptrace needs to remain available, as it allows for an escape of the seccomp filter.
So the basic idea is to use ptrace to swap an allowed syscall with a forbidden syscall. Seccomp used to run before ptrace, so it would see the allowed syscall and allow the code to continue, but then ptrace would swap the syscall to something forbidden! The details are a bit thin, however, they don't explain how we can do that. After a bit more digging, I found this pdf[3] which contains a simple POC showing the issue, and explaining it.
The TLDR is to use ptrace with `PTRACE_SYSCALLS` to execute until a syscall is hit. When a syscall is hit, seccomp would first check the syscall is allowed, and then pass execution to the tracing system of the kernel, that would stop the process for ptrace to inspect. From ptrace you can then modify the registers with `PTRACE_SET_REGS` to change the syscall being called (the syscall number is in register RAX on amd64) and resume execution. The kernel will then happily execute the modified, unchecked syscall!
So what changed is that seccomp will be run after ptrace now, so there isn't a way to modify the registers before the syscall is run anymore.
[0]: https://github.com/moby/moby/commit/1124543ca8071074a537a15db251af46a5189907 https://github.com/moby/moby/commit/1124543ca8071074a537a15d...
[1]: https://github.com/torvalds/linux/commit/93e35efb8de45393cf61ed07f7b407629bf698ea https://github.com/torvalds/linux/commit/93e35efb8de45393cf6...
[2]: https://www.mail-archive.com/linuxppc-dev@lists.ozlabs.org/msg104378.html https://www.mail-archive.com/linuxppc-dev@lists.ozlabs.org/m...
[3]: http://asm.rajiska.fr/misc/container_security.pdf http://asm.rajiska.fr/misc/container_security.pdf page 9 for the explanation, page 16 for the POC code
- JoshuaDavid 6y agoHuh. If I'm reading that correctly, that means it would be possible to build a paranoid_sudo command, where you run a command and use PTRACE_SYSCALLS on it, and for each of the syscalls it makes, try running it first as a non-root user, and if that fails prompt the user for whether they want to allow the base program to run whatever syscall requires the child process to be root. I don't think the above would actually be all that useful, but still it's kind of cool that it's possible to do at all.
- roblabla 6y agoPTRACE_SYSCALL isn't what you want, since it will always run the syscall. What you can do is use PTRACE_SYSEMU, which allows you to emulate a syscall in userland. It will break on syscall enter, allow you to emulate your syscall (e.g. with your scheme of "run it without root, ask user for elevated privs if necessary"), and resume execution at the next instruction when you ptrace again, never really executing the syscall instruction. For this to work you'd also need a way to share various namespaces (like file descriptors) which is doable via the clone2 syscall. There's a ton of cool tricks you can do with linux, it's a really powerful kernel.