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 objectis 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 0000000000000000cookie 0000000000000000code 00000000flags 00000000
pid0uid 1000data 24offs 80000: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 73682a85flags 0000017fptr 0000005500000001cookie 0000000000000000[0] got handle 000000010000: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 0000000000000000cookie 0000000000000000code 00000000flags 00000000
pid0uid 1000data 28offs 80000: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 73682a85flags 0000017fptr 0000007f00000002cookie 0000007f8b9d19c0
[0] got handle 00000000
Debugger output from system_server
pwndbg> hexdump 0x0000007f8b9d19c0+00000x7f8b9d19c0383576 ab7f 0000000000000000000000|85v.|....|....|....|+00100x7f8b9d19d06500 6e 007400 5f 0040 d1 0c a87f 000000|e.n.|t._.|@...|....|+00200x7f8b9d19e06a1620000000000020 ad 81 ab7f 000000|j...|....|....|....|+00300x7f8b9d19f0e0 fc 7f 8e7f 0000 00a0 f2 c7 8a7f 000000|....|....|....|....|+00400x7f8b9d1a00
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