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
Race condition in ptrace() allows setuid debugging #5230
Comments
Nice catch! :) How would you break ASLR by running this many times though? Every time you |
That is true, but because it's a 32 bit system you only have so many options to place it. If I take a quick look into the code it looks like userspace is limited from That means that if the exploit is only failing because of ASLR you could just run it 776192 times, literally, and it will work. Assuming 10 tries per second that would take ~22 hours. There's room for improvement ;-) |
Adding an example exploit that combines this issue with #5270 to get a local privilege escalation. To run the exploit first revert to commit 4b7b92c. This is the last commit before the patch above. The race in ptrace.cpp is incredibly small. To test this exploit you want to add a delay at the critical moment in
The exploit will probably fail on the first run, but work on the second:
The attached code contains some additional details. |
A race condition exists in
ptrace()
that allows any user to attach to setuid processes. The core issue is a lack of synchronisation betweenptrace()
andexecve()
.Details
When handling a
ptrace()
syscall the kernel starts by checking the state of the process, permissions, supplied arguments etc. If all these checks pass the kernel will continue to perform the requested action. This can be seen as two seperate steps.The problem is that there is a small window between these steps. In this window 'things' could change without the kernel being aware of that. For example, the debuggee might be running with different privileges.
A sample timeline of how the bug might happen:
Reproducing
The window to race is very small, and in this window a lot of other code must run. To reproduce the bug its easiest to widen the window with a fake sleep at the critical moment.
I used the following patch in Kernel/Ptrace.cpp
Before showing the bug you can use the attached demo script to show NORMAL behaviour, even when this patch is applied. The script will:
fork()
PT_POKE
. this fails because of permissionsExpected output:
Now comment out the sleep in the parent:
This will mimic the timeline above. The code now calls
PT_POKE
before the child has started the setuid binary. All checks pass, but because of the added print statements it will hang for a bit after the checks. By the time printing is done the debuggee has changed to the setuidpasswd
, but we are still allowed to poke it.Expected output:
The error message is a bit misleading. Because the demo tries to poke an unmapped address (0x41414141) and errors out. But to confirm the real problem you can verity the kernel log. It should print "PT_POKE: all security checks done, going to poke!" to indicate it passed all the checks.
Exploitation
Exploitation might be tricky:
However, I feel that if there is a reliable way to win the race exploitation should be possible. An attacker could for example break ASLR by running the exploit many times.
Winning the race might be possible by using tricks with signals, thread priorities, or abusing side-effects/delays of functions between the two steps. I did not look into this.
Attachments
demo.cpp.txt
The text was updated successfully, but these errors were encountered: