CVE-2025-48549 AI-generated analysis
platform/frameworks/av (audiopolicy service, AudioRecordClient / OpRecordAudioMonitor); platform/frameworks/base (PermissionManagerService app-op attribution chain handling; SystemUI AppOpsControllerImpl mic indicator)
Four coordinated changes make microphone app-op enforcement and the mic indicator consistent across a full attribution chain: audioserver's OpRecordAudioMonitor now checks and watches every link of the chain (and always OP_RECORD_AUDIO); PermissionManagerService finishes already-started proxy ops when a later link fails and aligns the preflight check with the data-delivery path; and SystemUI only suppresses the mic indicator when all of a UID's recordings are silenced.
Root cause
In services/audiopolicy/service/AudioRecordClient.cpp, OpRecordAudioMonitor::onFirstRef registered an AppOps watch only for mAttributionSource.packageName (the top of the chain) and only for mAppOp. OpRecordAudioMonitor::checkOp called mAppOpsManager.checkOp only for mAttributionSource.uid/packageName. Links further down the AttributionSourceState.next chain were neither checked nor watched, and OP_RECORD_AUDIO was not checked when mAppOp was a different op (TODOs in the code noted this). In PermissionManagerService.checkRuntimePermission, if a later link of a chain failed during startDataDelivery, proxy ops already started higher in the chain were left running, which split the op state. In SystemUI AppOpsControllerImpl, isAnyRecordingPausedLocked disabled the mic indicator for a UID as soon as any one of its AudioRecordingConfigurations was silenced, even if another recording for the same UID was not.
How the fix works
- frameworks/base bbd8db54b3c9, PermissionManagerService.java:checkRuntimePermission: adds a prev pointer. When startDataDelivery is set and performOpTransaction returns an opMode other than MODE_ALLOWED part-way through a chain, it copies prev.asState(), truncates next[0].next to an empty array, and calls finishDataDelivery on that cut chain so the already-started proxy op is finished. prev is set to current at the end of each loop iteration.
- frameworks/base 2c18e5f08282, PermissionManagerService.java:performOpTransaction (the !forDataDelivery preflight branch): when skipProxyOperation is true and singleReceiverFromDatasource is false, opMode for the first accessor is set to MODE_ALLOWED instead of calling appOpsManager.unsafeCheckOpRawNoThrow. This matches the data-delivery path, which does not check the first link of a non-trivial chain. Per the commit message, audioserver otherwise fails this preflight because it lacks a valid Uid/PackageState. The check of next still runs.
- frameworks/av db3bbd5a03e4, AudioRecordClient.cpp: adds a forward iterator AttrSourceItr over AttributionSourceState.next in the anonymous namespace. OpRecordAudioMonitor::onFirstRef now calls mAppOpsManager.startWatchingMode for every link's packageName (same flags and mOpCallback), for mAppOp and also for AppOpsManager::OP_RECORD_AUDIO when mAppOp differs. OpRecordAudioMonitor::checkOp now computes hasIt with std::all_of over the chain (checkOp(op, x.uid, x.packageName) == MODE_ALLOWED) for mAppOp, and also for OP_RECORD_AUDIO when mAppOp differs.
- frameworks/base feedcb492661, SystemUI AppOpsControllerImpl.java: isAnyRecordingPausedLocked is renamed isAllRecordingPausedLocked. It returns true if mMicMuted, false if there are no configs for the uid, and otherwise returns false as soon as one config is not silenced, so it returns true only if all are silenced. Both call sites (AppOpItem creation for an active microphone op, and updateSensorDisabledStatus) now use the new method.
Completeness
- The av change covers both the op-state computation (checkOp) and change notifications (watch registration in onFirstRef) across every link in the chain, and always includes OP_RECORD_AUDIO.
- The preflight relaxation in performOpTransaction only applies to the !forDataDelivery branch with skipProxyOperation && !singleReceiverFromDatasource. The forDataDelivery and startDataDelivery branches are unchanged.
- The finish-on-failure cleanup in checkRuntimePermission only runs when startDataDelivery is true. Other delivery paths are not touched by that commit.
- The SystemUI change still relies on AudioRecordingConfiguration listening (the added TODO b/365843152 says this is to be removed), so the indicator logic still depends on audioserver reporting isClientSilenced correctly.
- The av commit changes only AudioRecordClient.cpp and has no test changes. The Android 15 SystemUI commit adds AppOpsControllerTest coverage; the Android 14 diff shown does not.
Behavior changes
- audioserver treats a recording as lacking the op if any link of the attribution chain lacks the governing op or OP_RECORD_AUDIO, not just the top link.
- audioserver registers more AppOps watch callbacks (one per chain link per op, plus OP_RECORD_AUDIO), so op changes for any link reach RecordAudioOpCallback::opChanged.
- Preflight (non-data-delivery) checks of non-trivial chains with skipProxyOperation no longer fail because the first link (e.g. audioserver) lacks the op.
- When a later chain link fails during startDataDelivery, previously started proxy ops are finished.
- The SystemUI mic indicator is shown for a UID unless all of its known recordings are silenced (or the mic is muted). Previously a single silenced recording hid the indicator.
Per major
- Android 14: same fix: RESTRICT AUTOMERGE cherry-picks in android-security-14.0.0_r22 (av db3bbd5a03e4; base bbd8db54b3c9, 2c18e5f08282, feedcb492661). At r29 the patched AudioRecordClient.cpp and PermissionManagerService code is present. The resolver names feedcb492661 as the fix and reports 6/8 lines of the patched region present.
- Android 15: resolved in android-security-15.0.0_r10 with SystemUI commit 00344da68fce: the same code change as on 14, plus AppOpsControllerTest updates. Android 15 counterparts of the av and PermissionManagerService commits are not shown in the material. At r17, the resolver reports 6/8 lines of the patched region present.
- Android 16: not affected (not listed by the bulletin); at android-security-16.0.0_r8, 8/8 lines of the patched region are present.
- Android 17: not affected (not listed by the bulletin); at android-security-17.0.0_r1, 8/8 lines of the patched region are present.
Unknowns
- The material does not describe the concrete trigger scenario or which process the privilege escalation targets.
- The bulletin lists Android 13 as affected, but the material has no Android 13 commit or branch status.
- The material does not show why the resolver reports only 6/8 lines of the patched region at android-security-14.0.0_r29 and 15.0.0_r17, or which file that region is in.
- Whether Android 15 also received the av and PermissionManagerService counterparts is not shown.
- The material does not say how the bulletin's '16-next' entry relates to the 16.0.0_r8 state.