Here we present our task kill mechanism.
The Android programming paradigm dictates that every application component of a given application resides in a single process.
The Activity Manager was designed to only consider a one-to-one relationship between an application and a process.
However, it is possible for an application to launch additional processes (e.g., to launch shell commands like ping or ps), which are only “considered” by the kernel.
One of the core ideas behind TrUbi is for strapps to control which applications, processes and threads are running in the system during those strapps' execution.
The easiest way to terminate an application is to use the ActivityManagerService.
However, considering Android's current architecture, it would be possible for an application to launch additional processes, possibly with malicious intent, that would survive the ActivityManagerService termination call, because, the Activity Manager itself doesn't map those processes to the apps which launched them in the first place.
This means that if we use the ActivityManagerService termination call, it would only terminate the app process and not the other processes.
Having all this under consideration, and taking advantage of ASM's callback capabilities, we made some changes to support the termination of processes associated with an application.
*Note:* because we assume there is only one application per UID, the termination of processes is made based on those processes' UIDs, rather than on their parent processes ID.
In the following Image we present the Task Kill workflow:
As a base for our tests we terminate apps via terminal commands.
When we make the am force-stop <package name> command, it basically triggers the Activity Manager to terminate the app's process (how that termination is actually accomplished hasn't been yet fully determined).
Because we have our strapp, or in this case an ASM app, that already activated the task_kill hook, whenever the kernel tries to terminate a process, our strapp gets immediately notified of that intention, and decides whether or not that termination goes through.
ASM was developed with only two options, allow or deny. In this case we want a third option in which every other process associated with the app is also terminated.
This image was adapted from ASM's normal execution of a hook, which you can find here.
As you can see from that original image, this image is quite similar. In this image we basically introduced the way we force the termination of an app, the execution calls of the callback (red arrow path), the return of the strapp's decision (green arrow path), and the new modifications introduced to guarantee the termination of the app related processes (blue arrow path).
We now focus on the changes made to ASM in order to terminate those specific kinds of processes.
Because ASM had a kernel level hook to control the termination of tasks, but didn't really provided that hook to the middleware and consequently to ASM apps, the first step we took was to effectively extend that hook to the middleware.
After that we tried to implement the rest of our idea, which was originally very straightforward. Instead of only returning ASM_ALLOW or ASM_DENY, we would introduce a new return value, more specifically ASM_KILL_ALL, that would be interpreted by asm_kill_all( ) which would in turn terminate the other processes itself before terminating the actual app's process.
However we faced some problems with the netlink socket through which the ASM callback mechanism is implemented.
After applying our modifications we observed that the return values from the callbacks were always either 1 or 2, and independently of the value we returned in the middleware, the kernel would only receive one of those two values.
Considering the ASM code already had something that could point to this odd behaviour (we don't really know because there's no documentation), we opted for a different solution. Btw, the code in question has this:
if(reply==USER_ASM_ALLOW) reply=0; else reply=-1;//either uninitialized or USER_ASM_DENY, default DENY
Since we could not use the netlink socket, we opted to use the other mechanism that ASM uses to communicate from the middleware to the kernel, the ioctl call.
Because this call is only available in the native code, we begun by adding a new native method to the ASMService, and then in the cpp class that actually maps the java native methods into actual native code, we added the code that performed the ioctl call (partial copy-paste from the other methods available in that class).
Finally we modified the asm_ioctl function in asm_iface.c to terminate every process associated with the app's UID.