Android IPC and Intent — source audit
TL;DR: Android IPC happens through Intents, Messengers, AIDL services, PendingIntents, and bound services. The high-value bugs from source: exported components without permission gates, intent-redirection patterns (
setComponent(null)), PendingIntent mutability, and trust ofgetIntent().getData()without an origin check. Companion to android-source-review-methodology and the attacker-side android-intent-redirection.
Where IPC enters the source
1
2
3
4
# Entry points: untrusted Intent in
grep -rn 'getIntent()\.getData\|getIntent()\.getStringExtra\|getIntent()\.getParcelableExtra\|getIntent()\.getExtras' src/
grep -rn 'onCreate\(.*Intent\|onReceive\(.*Intent\|onStartCommand\(.*Intent\|onBind\(.*Intent' src/
grep -rn 'Messenger\|AIDL' src/
For every hit, ask: is the component exported? Is there a permission check before the data is used?
Exported components without permission gates
A Manifest.permission declaration on an exported component is the only enforced gate. Without it, any installed app can deliver an intent to that component.
1
2
3
4
5
6
7
8
9
10
<!-- bad -->
<activity android:name=".ImportActivity" android:exported="true">
<intent-filter><action android:name="android.intent.action.VIEW"/></intent-filter>
</activity>
<!-- good -->
<activity android:name=".ImportActivity" android:exported="true"
android:permission="com.example.permission.IMPORT">
<intent-filter><action android:name="android.intent.action.VIEW"/></intent-filter>
</activity>
signature-level permissions are the strongest non-system option (only apps signed by the same key can call).
The intent-redirection pattern
A “redirector” reads a payload Intent from getIntent().getParcelableExtra("forward") and calls startActivity(forwardIntent) without sanity-checking the target. An attacker delivers a payload Intent targeting an unexported component — and from inside your trusted process, it now runs:
1
2
val target = intent.getParcelableExtra<Intent>("next")
startActivity(target) // BAD — attacker controls target
Greps:
1
2
grep -rnE 'getParcelableExtra\([^)]*\).*startActivity\(' src/
grep -rn 'setComponent\(null\)\|component = null' src/
Fix: refuse intents whose component points outside your own package, or compare to an allowlist of permitted targets.
PendingIntent traps
Default mutability rules changed across API levels:
- API 31+ requires explicit
FLAG_IMMUTABLEorFLAG_MUTABLE. FLAG_MUTABLEPendingIntents with an unset target (setComponent(null), empty action) can be hijacked by another process that fills the gap.
1
grep -rn 'PendingIntent\.getActivity\|PendingIntent\.getBroadcast\|PendingIntent\.getService' src/ -B1 -A3
Look for:
FLAG_MUTABLEwithIntent()having nosetPackageorsetComponent.- Notifications that hand the PendingIntent to a system service with
FLAG_MUTABLE.
Implicit broadcasts for sensitive data
Sending a broadcast without setPackage() makes it implicit — every receiver registered for that action gets the data.
1
2
3
val i = Intent("com.example.GOT_TOKEN")
i.putExtra("token", token)
context.sendBroadcast(i) // BAD — any app with the receiver can see
Fix: setPackage(context.packageName) to keep it in-process, or use LocalBroadcastManager, or a direct method call.
AIDL services and bound services
Bound services are RPC over an interface. The threats:
onBindnot enforcingcheckCallingPermission.- Returning a binder that exposes more methods than intended.
Binder#getCallingUid()checks done after sensitive state is read.
1
2
find . -name '*.aidl'
grep -rn 'onBind\|asInterface\|checkCallingPermission\|getCallingUid' src/
Pattern to flag:
1
2
3
public IBinder onBind(Intent intent) {
return mBinder; // no permission check
}
Messengers
Messengers wrap a Handler. The Handler receives Message from any caller; data is in Message.obj or getData().
1
grep -rn 'new Messenger\(\|handleMessage\(Message' src/
Audit handleMessage — treat msg.obj and msg.getData() as fully untrusted.
Slice and AppWidget providers
Slice providers (androidx.slice.SliceProvider) and AppWidget providers handle untrusted URIs and intents respectively; same trust rules.
Source-audit checklist
- Every
exported=truehas eitherpermission=or comes from anintent-filteryou intended. - No
getParcelableExtra(...) → startActivitywithout target validation. - No
FLAG_MUTABLEPendingIntent with under-specified target. - No implicit broadcast carrying sensitive payload.
- Every bound service
onBindcheckscheckCallingPermissionor comparesgetCallingUidagainst an allowlist. - Every
handleMessagetreatsmsg.obj/getData()as attacker-controlled.