Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=889
The interaction between the kernel /dev/binder and the usermode Parcel.cpp mean
that when a binder object is passed as BINDER_TYPE_BINDER or BINDER_TYPE_WEAK_BINDER,
a pointer to that object (in the server process) is leaked to the client process
as the cookie value. This leads to a leak of a heap address in many of the privileged
binder services, including system_server.
See attached PoC, which leaks the addresses of allocated heap objects in system_server.
Output running from the shell (run on droidfood userdebug build, MTC19X):
shell@bullhead:/ $ /data/local/tmp/binder_info_leak
--- binder info leak ---
[0] opening /dev/binder
[0] looking up activity
0000: 00 . 01 . 00 . 00 . 1a . 00 . 00 . 00 . 61 a 00 . 6e n 00 . 64 d 00 . 72 r 00 .
0016: 6f o 00 . 69 i 00 . 64 d 00 . 2e . 00 . 6f o 00 . 73 s 00 . 2e . 00 . 49 I 00 .
0032: 53 S 00 . 65 e 00 . 72 r 00 . 76 v 00 . 69 i 00 . 63 c 00 . 65 e 00 . 4d M 00 .
0048: 61 a 00 . 6e n 00 . 61 a 00 . 67 g 00 . 65 e 00 . 72 r 00 . 00 . 00 . 00 . 00 .
0064: 08 . 00 . 00 . 00 . 61 a 00 . 63 c 00 . 74 t 00 . 69 i 00 . 76 v 00 . 69 i 00 .
0080: 74 t 00 . 79 y 00 . 00 . 00 . 00 . 00 .
BR_NOOP:
BR_TRANSACTION_COMPLETE:
BR_REPLY:
target 0000000000000000 cookie 0000000000000000 code 00000000 flags 00000000
pid 0 uid 1000 data 24 offs 8
0000: 85 . 2a * 68 h 73 s 7f . 01 . 00 . 00 . 01 . 00 . 00 . 00 . 55 U 00 . 00 . 00 .
0016: 00 . 00 . 00 . 00 . 00 . 00 . 00 . 00 .
- type 73682a85 flags 0000017f ptr 0000005500000001 cookie 0000000000000000
[0] got handle 00000001
0000: 00 . 01 . 00 . 00 . 1c . 00 . 00 . 00 . 61 a 00 . 6e n 00 . 64 d 00 . 72 r 00 .
0016: 6f o 00 . 69 i 00 . 64 d 00 . 2e . 00 . 61 a 00 . 70 p 00 . 70 p 00 . 2e . 00 .
0032: 49 I 00 . 41 A 00 . 63 c 00 . 74 t 00 . 69 i 00 . 76 v 00 . 69 i 00 . 74 t 00 .
0048: 79 y 00 . 4d M 00 . 61 a 00 . 6e n 00 . 61 a 00 . 67 g 00 . 65 e 00 . 72 r 00 .
0064: 00 . 00 . 00 . 00 . 05 . 00 . 00 . 00 . 70 p 00 . 77 w 00 . 6e n 00 . 65 e 00 .
0080: 64 d 00 . 00 . 00 .
BR_NOOP:
BR_TRANSACTION_COMPLETE:
BR_REPLY:
target 0000000000000000 cookie 0000000000000000 code 00000000 flags 00000000
pid 0 uid 1000 data 28 offs 8
0000: 00 . 00 . 00 . 00 . 85 . 2a * 68 h 73 s 7f . 01 . 00 . 00 . 02 . 00 . 00 . 00 .
0016: 7f . 00 . 00 . 00 . c0 . 19 . 9d . 8b . 7f . 00 . 00 . 00 .
- type 73682a85 flags 0000017f ptr 0000007f00000002 cookie 0000007f8b9d19c0
[0] got handle 00000000
Debugger output from system_server
pwndbg> hexdump 0x0000007f8b9d19c0
+0000 0x7f8b9d19c0 38 35 76 ab 7f 00 00 00 00 00 00 00 00 00 00 00 |85v.|....|....|....|
+0010 0x7f8b9d19d0 65 00 6e 00 74 00 5f 00 40 d1 0c a8 7f 00 00 00 |e.n.|t._.|@...|....|
+0020 0x7f8b9d19e0 6a 16 20 00 00 00 00 00 20 ad 81 ab 7f 00 00 00 |j...|....|....|....|
+0030 0x7f8b9d19f0 e0 fc 7f 8e 7f 00 00 00 a0 f2 c7 8a 7f 00 00 00 |....|....|....|....|
+0040 0x7f8b9d1a00
This is pretty obviously the case; the code in Parcel.cpp that flattens binder objects
to pass via binder transactions:
status_t flatten_binder(const sp<ProcessState>& /*proc*/,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
ALOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = handle;
obj.cookie = 0;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
obj.cookie = reinterpret_cast<uintptr_t>(local); // <--- is a pointer to the object
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = 0;
obj.cookie = 0;
}
return finish_flatten_binder(binder, obj, out);
}
and the kernel code which processes this to send to the target process modifies
the fp->handle entry, overwriting fp->binder, but does not alter fp->cookie, which
contains the second pointer.
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct binder_ref *ref;
struct binder_node *node = binder_get_node(proc, fp->binder);
if (node == NULL) {
node = binder_new_node(proc, fp->binder, fp->cookie);
if (node == NULL) {
return_error = BR_FAILED_REPLY;
goto err_binder_new_node_failed;
}
node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
}
if (fp->cookie != node->cookie) {
binder_user_error("%d:%d sending u%016llx node %d, cookie mismatch %016llx != %016llx\n",
proc->pid, thread->pid,
(u64)fp->binder, node->debug_id,
(u64)fp->cookie, (u64)node->cookie);
goto err_binder_get_ref_for_node_failed;
}
if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
return_error = BR_FAILED_REPLY;
goto err_binder_get_ref_for_node_failed;
}
ref = binder_get_ref_for_node(target_proc, node);
if (ref == NULL) {
return_error = BR_FAILED_REPLY;
goto err_binder_get_ref_for_node_failed;
}
if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc;
binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,
&thread->todo);
trace_binder_transaction_node_to_ref(t, node, ref);
binder_debug(BINDER_DEBUG_TRANSACTION,
" node %d u%016llx -> ref %d desc %d\n",
node->debug_id, (u64)node->ptr,
ref->debug_id, ref->desc);
} break;
In the case of 64-bit processes, we also leak the high dword of the fp->binder pointer, because
a uint32_t is smaller than a binder_uintptr_t.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40515.zip