Exploit-DB #40381
Description: Google Android - getpidcon Usage binder Service Replacement Race Condition
Type: dos
Platform: android
Date: 2016-09-14
Author: Google Security Research

Exploit:
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=851

This is very similar to forshaw's bug (<https://code.google.com/p/android/issues/detail?id=200617>, <https://bugs.chromium.org/p/project-zero/issues/detail?id=727>).

The servicemanager, when determining whether the sender of a binder transaction is authorized to register a service via SVC_MGR_ADD_SERVICE, looks up the sender's SELinux context using getpidcon(spid), where spid is the value of the sender_pid field in the binder_transaction_data that was received from the binder driver.

This is problematic because getpidcon($pid) is only safe to use if the caller either knows that the process originally referenced by $pid can't transition from zombie to dead (normally because it is the parent or ptracer of $pid) or if the caller can validate that the process referenced by $pid can not have spawned before $pid referred to the correct process based on the age of the process that $pid points to after the getpidcon() call. (The same thing applies to pretty much any API that refers to processes using PIDs.)

This means that an attacker can, at least theoretically, register arbitrary services that would normally be provided by the system_server if he can execute / cause execution of the following operations in the right order:

 - The main exploit process $exploit forks, creates process $child
 - $child does $binder_fd = open("/dev/binder", ...)
 - $child forks, creates process $subchild
 - $child exits. The binder_proc belonging to $binder_fd still holds a reference
   to $child. $child transitions to zombie status.
 - The exploit repeatedly forks processes that instantly die until there are no unallocated
   PIDs between ns_last_pid and $child's PID.
 - $subchild sends a SVC_MGR_ADD_SERVICE binder message to the service manager
 - the service manager receives the binder message. The kernel fills the
   sender_pid field with the result of `task_tgid_nr_ns(sender, [...])`,
   where `sender` is `t->from->proc->tsk`, the task_struct of $child.
 - $exploit uses `waitpid()` to transition $child from zombie to dead status
 - $exploit sends a HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION
   binder message to system_server
 - system_server launches a new worker thread
   (in ActivityManagerService.logStrictModeViolationToDropBox)
 - the service manager calls getpidcon()
 - system_server's worker thread dies

As far as I can tell, this exploit approach contains the following race conditions:

 - If $exploit calls waitpid() before the service manager has performed the binder
   read (more accurately, before the task_tgid_nr_ns call), the service manager sees
   PID 0. This race isn't hard to win, but it would help to have some primitive to either stall
   the service manager after the task_tgid_nr_ns call or at least detect whether it has
   performed the binder read. On older Android versions, voluntary_ctxt_switches
   in /proc/$pid/status might have helped with that, but nowadays, that's blocked.
   When this race condition fails, you'll get an SELinux denial with
   scontext=servicemanager.
 - If the service manager calls getpidcon() before the system_server has launched a
   worker thread, the call will either fail (if there is no such PID) or return the
   not-yet-reaped $child process. Again, having a primitive for stalling the service manager
   would be useful here.
   When this race condition fails, it will cause either an SELinux denial with
   scontext=untrusted_app or an "failed to retrieve pid context" error from the
   service manager.
 - If the system_server's worker thread dies before getpidcon(), getpidcon() will fail.
   To avoid this race, it would be very helpful to be able to spawn a thread in system_server
   that has a controlled or at least somewhat longer lifetime.


Because of the multiple races, it is hard to hit this bug, at least without spending days on finding ways to eliminate races or widen race windows, optimizing the exploit to not cycle through the whole pid range for every attempt and so on. Because of that, I decided to run my PoC on a patched Android build (based on android-6.0.1_r46) with the following modifications to show that, while the race window is very hard to hit, there is such a race:

-------
$ repo diff

project frameworks/base/
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 33d0a9f..371ecd7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12269,6 +12269,9 @@ public final class ActivityManagerService extends ActivityManagerNative
                     if (report.length() != 0) {
                         dbox.addText(dropboxTag, report);
                     }
+                    try {
+                        Thread.sleep(2000);
+                    } catch (InterruptedException e) {}
                 }
             }.start();
             return;

project frameworks/native/
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index 7fa9a39..0600eb1 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -7,6 +7,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include <private/android_filesystem_config.h>
 
@@ -204,6 +205,9 @@ int do_add_service(struct binder_state *bs,
     if (!handle || (len == 0) || (len > 127))
         return -1;
 
+    if (uid > 1000)
+        sleep(2);
+
     if (!svc_can_register(s, len, spid)) {
         ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
              str8(s, len), handle, uid);
-------

These modifications widen the race windows sufficiently to be able to hit the bug with a few tries.