ASA-2019-00082 – Linux kernel: Binder use-after-free via fdget() optimization


Allele Security Alert

ASA-2019-00082

Identifier(s)

ASA-2019-00082, CVE-2019-2000, A-120025789

Title

Binder use-after-free via fdget() optimization

Vendor(s)

Linux foundation

Product(s)

Linux kernel

Affected version(s)

Linux kernel since commit:

binder: use standard functions to allocate fds
https://github.com/torvalds/linux/commit/44d8047f1d87adc2fd7eccc88533794f6d88c15e

Fixed version(s)

Linux kernel with the following commit:

binder: fix use-after-free due to ksys_close() during fdget()
https://github.com/torvalds/linux/commit/80cd795630d6526ba729a089a435bf74a57af927

Proof of concept

Yes

Description

The linux kernel contains a use-after-free in the Binder IPC mechanism via fdget() optimization.

Technical details

In the Linux kernel, normally, when a `struct file *` is read from the file descriptor table, the reference counter of the `struct file` is bumped to account for the extra reference; this happens in fget(). Later, if the extra reference is not needed anymore, the refcount is dropped via fput(). A negative effect of this is that, if the `struct file` is frequently accessed, the cacheline containing the reference count is constantly dirty; and if the `struct file` is used by multiple tasks in parallel, cache line bouncing occurs.

Linux provides the helpers fdget() and fdput() to avoid this overhead. fdget() checks whether the reference count of the file descriptor table is 1, implying that the current task has sole ownership of the file descriptor table and no concurrent modifications of the file descriptor table can occur. If this check succeeds, fdget() then omits the reference count increment on the `struct file`. fdget() sets a flag in its return value that signals to fdput() whether a reference count has been taken. If so, fdput() uses the normal fput()
logic; if not, fdput() does nothing.

This optimization relies on a few rules, including:

A) A reference taken via fdget() must be dropped with fdput() before the end of the syscall.
B) A task’s reference to its file descriptor table may only be duplicated for writing if that task is known to not be between fdget() and fdput().
C) A task that might be between an elided fdget() and fdput() must not use ksys_close() on the same file descriptor number as used for fdget().

The current upstream code violates rule C. The following sequence of events can cause fput() to drop the reference count of an in-use binder file to drop to zero:

Task A and task B are connected via binder; task A has /dev/binder open at file descriptor number X. Both tasks are single-threaded.

– task B sends a binder message with a file descriptor array (BINDER_TYPE_FDA) containing one file descriptor to task A
–  task A reads the binder message with the translated file descriptor number Y
–  task A uses dup2(X, Y) to overwrite file descriptor Y with the /dev/binder file
–  task A unmaps the userspace binder memory mapping; the reference count on task A’s /dev/binder is now 2
–  task A closes file descriptor X; the reference count on task A’s /dev/binder is now 1
– task A invokes the BC_FREE_BUFFER command on file descriptor X to release the incoming binder message
–  fdget() elides the reference count increment, since the file descriptor table is not shared – the BC_FREE_BUFFER handler removes the file descriptor table entry for X and decrements the reference count of task A’s /dev/binder file to zero

Because fput() uses the task work mechanism to actually free the file, this doesn’t immediately cause a use-after-free that KASAN can detect; for that, the following sequence of events works:

–  task A closes file descriptor X; the reference count on task A’s /dev/binder is now 1
–  task A forks off a child, task C, duplicating the file descriptor table; the reference count on task A’s /dev/binder is now 2
– task A invokes the BC_FREE_BUFFER command on file descriptor X to release the incoming binder message
– fdget() in ksys_ioctl() elides the reference count increment, since the file descriptor table is not shared
– the BC_FREE_BUFFER handler removes the file descriptor table entry for X and decrements the reference count of task A’s /dev/binder file to 1
–  task C calls close(X), which drops the reference count of task A’s /dev/binder to 0 and frees it
– task A continues processing of the ioctl and accesses some property of e.g. the binder_proc => KASAN-detectable UAF

Credits

Jann Horn (Google Project Zero)

Reference(s)

1719 – Android: binder use-after-free via fdget() optimization –  project-zero – Monorail
https://bugs.chromium.org/p/project-zero/issues/detail?id=1719

Android Security Bulletin — February 2019
https://source.android.com/security/bulletin/2019-02-01.html

binder: fix proc->files use-after-free
https://github.com/torvalds/linux/commit/7f3dc0088b98

binder: fix use-after-free due to ksys_close() during fdget()
https://github.com/torvalds/linux/commit/80cd795630d6526ba729a089a435bf74a57af927

binder: use standard functions to allocate fds
https://github.com/torvalds/linux/commit/44d8047f1d87adc2fd7eccc88533794f6d88c15e

FROMLIST: binder: fix proc->files use-after-free
https://android.googlesource.com/kernel/msm/+/1b652c7c29b7

CVE-2019-2000
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-2000

CVE-2019-2000
https://nvd.nist.gov/vuln/detail/CVE-2019-2000

If there is any error in this alert or you wish a comprehensive analysis, let us know.

Last modified: October 5, 2019

We are not responsible for any data loss, device corruption or any other type of issue due to the use of any information mentioned in our security alerts.