The Android App Sandbox does a great job
of keeping apps separate by default.
But what can you do when the provided level of isolation
doesn't match your application's model?
Hi, I'm Geremy Condra, and in this video,
we'll be talking about how you can use the Android Platform
Support for different isolation models
to make your app more robust and more secure.
What you see here is a loose model of some
of the most important layers of isolation
Android provides-- SELinux protections
that separate applications from system components.
You see Linux User, a UID separation,
which keeps separate apps separate.
And you see the process level separation
between app components.
So let's start off talking about process separation.
When Zygote spawns an application,
that application generally gets its own process.
This prevents other applications from modifying its memory
and isolates one app against a crash in another.
So the fact that your app and someone else's terrible app
are on the same phone doesn't impact your app's experience.
It also isolates the application from system components
and system components from each other.
Zooming in a little bit, we can see
that two apps which don't share a process
can still communicate in a variety of ways, such as binder
or the intent system, or over legacy mechanisms like pipes
or sockets.
The thing to take away from this is
that process separation isn't very restrictive.
Since you can still talk across these boundaries,
it's easy to get good isolation with little cost.
And of course, when we're talking about isolation here,
what we really mean is that if one process dies or gets
compromised, other processes can continue to run correctly.
So that's the default model.
And it's a good one for a huge variety of apps.
But what happens if that basic model doesn't give you
the isolation you need?
What do you do when you want to isolate one part of your app
from another-- like, say, a crashy service
or a network-aware component-- from something that
handles keys or other sensitive data?
Fortunately, Android's got your back.
Activities and services both support
the Android process attribute.
This allows you to specify exactly what process
you want to run your code in and getting a new one spawned
for that component if it doesn't already exist.
In this example, we have the service
that we don't want taking down the rest of our app
if it crashes.
By specifying that it should be run in a process called colon
crashy, we make sure that Android will run the service
in a new process created explicitly for this purpose.
Once you're done, the set of processes
looks a little more like this.
Because colon crashy and the app processor are separate,
crashy can go down and not take down the rest of the app.
You can also use this to better restrict your IPC endpoint.
In this model, colon crashy won't wind up
indirectly talking to the IPC endpoints needed
by other components.
So why do we need the other layers of isolation?
Why not just use processes for everything?
The reason we don't use process separation for everything
is because it doesn't come with a different set
of persistent credentials.
So while another process might not
be able to modify your memory, if it has the same UID,
it'll have the same permissions, it'll
be able to read and write the same files, et cetera.
As a result, besides being separated
by process boundaries, Android applications
are also generally run as their own user.
This forms the basis for the App Sandbox,
as well as the separation between application home
directories.
In this way, Android provides a kernel-enforced guarantee
that the application's data can only
be modified by that application, unless the app
decides otherwise.
This gives a ton of flexibility and control to the application.
There are two exceptions to this rule that every app gets
its own UID-- shared UID and isolated process.
Let's talk about shared UID first.
In this diagram, apps B and C are marked shared UID,
while app A is not.
Applications marked shared UID and signed
with the same keys are able to do
almost anything the other can do.
So from the security perspective,
marking two applications shared UID
means that they can be treated as essentially the same app.
And that's sometimes useful to help break up applications
or provide access to resources in interesting ways.
But in security, we more often get the opposite question.
What do I do if I want to break one app into several security
contexts instead?
And that's where isolated process comes in.
To see why it's valuable let's return
to the example of the crashy service.
Especially for native code, it may not make sense just
to isolate yourself from the possibility
that your code will crash.
If the code is really suspect, like certain kinds of renderers
or media codecs, you may not want
to have very much to do with that code at all.
In fact, you may want to run it in a completely separate
processing user-- essentially, as its own separate app.
And that's exactly what isolated process does.
The net result looks something like this,
with app A split into two components.
Each is in a different user and process,
meaning that the isolated part of the application
is as separate from app A as other apps are.
So when should you use process, and when should you
use isolated process?
Use process when the isolated code is pure Java, when
it doesn't contain an interpreter or similar,
or when it needs to interact with other processes
on your behalf.
Use isolated process when the isolated code
is native code, when it performs complex operations
like rendering or encoding, when it contains
an interpreter or JIT, and when it
doesn't need to run as your user.
That's it for isolation for Android app developers.
Be sure to check out the other videos in this series,
and thanks for watching.