summaryrefslogtreecommitdiff
path: root/media/utils/ServiceUtilities.cpp
diff options
context:
space:
mode:
authorAtneya Nair <atneya@google.com>2025-06-30 17:08:22 -0700
committeraoleary <seanm187@gmail.com>2025-10-07 18:44:08 +0000
commitd4d87d930bfbc5c7a00ed5f7a2649f8b838673a8 (patch)
treeeef2f14546ccf8ce19a0e573477c80563f924fb2 /media/utils/ServiceUtilities.cpp
parente385f5a69f5d3a87d14149bd840894e9247aee9b (diff)
[RESTRICT AUTOMERGE] Fix audio AppOps refcount mismatchHEADt13.0
Iea630bb2bf1b54697ab6a95ee9f65db5cc4d134a I8a3498a08a31543219279cb063c043fd0c767cd7 The state of the appop corresponding to a recording must be consistent with the APS notion of whether a recording is silenced. Ensure that when we start recording, we only un-silence the track if the op is allowed from the appop side. This requires plumbing the exact perm state through the startRecording API. Correctly handle virtual sources by checking only their associated op state, rather than OP_RECORD. Update the appop callback listening/handling as well, by only listening/evaluating OP_RECORD for relevant sources. Ie7d2661b4027b5ce6bee9b6c63b70f0b120dcbcc When AppOp restrictions are in place, starting an op will return SOFT_DENY, but leave the op in a paused state, which can restart unexpectedly when the restriction is removed (despite no data delivery occurring). Work around this by first checking if recording is restricted, and avoiding the start call in that case. Instead, use a note call in order to maintain a rejected op for listeners (for prompting the mic unblock dialogue). Idd4389e7e79ee819c29ec2676d2e22d5a4b12249 Un-require RECORD_OP for VOICE_DOWNLINK Ibc735ea463aa6e09f8a24fc3cb50c39b70bbbea8 Handle targetSdk failures for recording Test: atest CtsMediaAudioPermissionTestCases Test: atest CtsMediaAudioTestCases Test: atest RuntimePermissionsAppOpTrackingTest Test: atest SensorPrivacyMicrophoneTest Test: atest AppOpsControllerTest Test: atest AudioPlaybackCaptureTest with mic blocked Test: sts-tradefed run sts-dynamic-develop -m \ CtsMediaAudioPermissionTestCases Test: manual oboetester app recording in background silenced Test: manual recorder with mic toggle disabled no indicator Test: manual poc Bug: 293603271 Flag: EXEMPT security (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e5c79cfb8ea5c8446d5a146203e9a716067fb61d) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3f73604fba84d1870864de76685412f8cf0c0560) Merged-In: Iea630bb2bf1b54697ab6a95ee9f65db5cc4d134a Change-Id: Iea630bb2bf1b54697ab6a95ee9f65db5cc4d134a
Diffstat (limited to 'media/utils/ServiceUtilities.cpp')
-rw-r--r--media/utils/ServiceUtilities.cpp143
1 files changed, 105 insertions, 38 deletions
diff --git a/media/utils/ServiceUtilities.cpp b/media/utils/ServiceUtilities.cpp
index 4ee45c795e..92385f3203 100644
--- a/media/utils/ServiceUtilities.cpp
+++ b/media/utils/ServiceUtilities.cpp
@@ -40,6 +40,11 @@
namespace android {
+namespace {
+constexpr auto PERMISSION_GRANTED = permission::PermissionChecker::PERMISSION_GRANTED;
+constexpr auto PERMISSION_HARD_DENIED = permission::PermissionChecker::PERMISSION_HARD_DENIED;
+}
+
using content::AttributionSourceState;
static const String16 sAndroidPermissionRecordAudio("android.permission.RECORD_AUDIO");
@@ -71,19 +76,33 @@ static String16 resolveCallingPackage(PermissionController& permissionController
int32_t getOpForSource(audio_source_t source) {
switch (source) {
- case AUDIO_SOURCE_HOTWORD:
- return AppOpsManager::OP_RECORD_AUDIO_HOTWORD;
+ case AUDIO_SOURCE_FM_TUNER:
+ return AppOpsManager::OP_NONE;
case AUDIO_SOURCE_ECHO_REFERENCE: // fallthrough
case AUDIO_SOURCE_REMOTE_SUBMIX:
return AppOpsManager::OP_RECORD_AUDIO_OUTPUT;
case AUDIO_SOURCE_VOICE_DOWNLINK:
return AppOpsManager::OP_RECORD_INCOMING_PHONE_AUDIO;
+ case AUDIO_SOURCE_HOTWORD:
+ return AppOpsManager::OP_RECORD_AUDIO_HOTWORD;
case AUDIO_SOURCE_DEFAULT:
default:
return AppOpsManager::OP_RECORD_AUDIO;
}
}
+bool isRecordOpRequired(audio_source_t source) {
+ switch (source) {
+ case AUDIO_SOURCE_FM_TUNER:
+ case AUDIO_SOURCE_ECHO_REFERENCE: // fallthrough
+ case AUDIO_SOURCE_REMOTE_SUBMIX:
+ case AUDIO_SOURCE_VOICE_DOWNLINK:
+ return false;
+ default:
+ return true;
+ }
+}
+
std::optional<AttributionSourceState> resolveAttributionSource(
const AttributionSourceState& callerAttributionSource) {
AttributionSourceState nextAttributionSource = callerAttributionSource;
@@ -113,7 +132,7 @@ std::optional<AttributionSourceState> resolveAttributionSource(
return std::optional<AttributionSourceState>{myAttributionSource};
}
-static bool checkRecordingInternal(const AttributionSourceState& attributionSource,
+static int checkRecordingInternal(const AttributionSourceState& attributionSource,
const String16& msg, bool start, audio_source_t source) {
// Okay to not track in app ops as audio server or media server is us and if
// device is rooted security model is considered compromised.
@@ -121,39 +140,74 @@ static bool checkRecordingInternal(const AttributionSourceState& attributionSour
// user is active, but it is a core system service so let it through.
// TODO(b/141210120): UserManager.DISALLOW_RECORD_AUDIO should not affect system user 0
uid_t uid = VALUE_OR_FATAL(aidl2legacy_int32_t_uid_t(attributionSource.uid));
- if (isAudioServerOrMediaServerOrSystemServerOrRootUid(uid)) return true;
-
- // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
- // may open a record track on behalf of a client. Note that pid may be a tid.
- // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
- const std::optional<AttributionSourceState> resolvedAttributionSource =
- resolveAttributionSource(attributionSource);
- if (!resolvedAttributionSource.has_value()) {
- return false;
- }
+ if (isAudioServerOrMediaServerOrSystemServerOrRootUid(uid)) return PERMISSION_GRANTED;
const int32_t attributedOpCode = getOpForSource(source);
+ if (isRecordOpRequired(source)) {
+ // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+ // may open a record track on behalf of a client. Note that pid may be a tid.
+ // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+ std::optional<AttributionSourceState> resolvedAttributionSource =
+ resolveAttributionSource(attributionSource);
+ if (!resolvedAttributionSource.has_value()) {
+ return PERMISSION_HARD_DENIED;
+ }
- permission::PermissionChecker permissionChecker;
- bool permitted = false;
- if (start) {
- permitted = (permissionChecker.checkPermissionForStartDataDeliveryFromDatasource(
- sAndroidPermissionRecordAudio, resolvedAttributionSource.value(), msg,
- attributedOpCode) != permission::PermissionChecker::PERMISSION_HARD_DENIED);
+ permission::PermissionChecker permissionChecker;
+ int permitted;
+ if (start) {
+ // Do a double-check, where we first check without actually starting in order to handle
+ // the behavior of AppOps where ops are sometimes started but paused for SOFT_DENIED.
+ // Since there is no way to maintain reference consensus due to this behavior, avoid
+ // starting an op when a restriction is in place by first checking. In the case where we
+ // startOp would fail, call a noteOp (which will also fail) instead. This preserves
+ // behavior that is reliant on listening to op rejected events (such as the hint
+ // dialogue to unmute the microphone). Technically racy, but very unlikely.
+ //
+ // TODO(b/294609684) To be removed when the pause state for an OP is removed.
+ permitted = permissionChecker.checkPermissionForPreflightFromDatasource(
+ sAndroidPermissionRecordAudio, resolvedAttributionSource.value(), msg,
+ attributedOpCode);
+ if (permitted == PERMISSION_GRANTED) {
+ permitted = permissionChecker.checkPermissionForStartDataDeliveryFromDatasource(
+ sAndroidPermissionRecordAudio, resolvedAttributionSource.value(), msg,
+ attributedOpCode);
+ } else {
+ // intentionally don't set permitted
+ permissionChecker.checkPermissionForDataDeliveryFromDatasource(
+ sAndroidPermissionRecordAudio, resolvedAttributionSource.value(), msg,
+ attributedOpCode);
+ }
+ } else {
+ permitted = permissionChecker.checkPermissionForPreflightFromDatasource(
+ sAndroidPermissionRecordAudio, resolvedAttributionSource.value(), msg,
+ attributedOpCode);
+ }
+
+ return permitted;
} else {
- permitted = (permissionChecker.checkPermissionForPreflightFromDatasource(
- sAndroidPermissionRecordAudio, resolvedAttributionSource.value(), msg,
- attributedOpCode) != permission::PermissionChecker::PERMISSION_HARD_DENIED);
+ if (attributedOpCode == AppOpsManager::OP_NONE) return PERMISSION_GRANTED; // nothing to do
+ AppOpsManager ap{};
+ PermissionController pc{};
+ return ap.startOpNoThrow(
+ attributedOpCode, attributionSource.uid,
+ resolveCallingPackage(pc,
+ String16{attributionSource.packageName.value_or("").c_str()},
+ attributionSource.uid),
+ false,
+ attributionSource.attributionTag.has_value()
+ ? String16{attributionSource.attributionTag.value().c_str()}
+ : String16{},
+ msg);
}
-
- return permitted;
}
bool recordingAllowed(const AttributionSourceState& attributionSource, audio_source_t source) {
- return checkRecordingInternal(attributionSource, String16(), /*start*/ false, source);
+ return checkRecordingInternal(attributionSource, String16(), /*start*/ false, source) !=
+ PERMISSION_HARD_DENIED;
}
-bool startRecording(const AttributionSourceState& attributionSource, const String16& msg,
+int startRecording(const AttributionSourceState& attributionSource, const String16& msg,
audio_source_t source) {
return checkRecordingInternal(attributionSource, msg, /*start*/ true, source);
}
@@ -164,19 +218,32 @@ void finishRecording(const AttributionSourceState& attributionSource, audio_sour
uid_t uid = VALUE_OR_FATAL(aidl2legacy_int32_t_uid_t(attributionSource.uid));
if (isAudioServerOrMediaServerOrSystemServerOrRootUid(uid)) return;
- // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
- // may open a record track on behalf of a client. Note that pid may be a tid.
- // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
- const std::optional<AttributionSourceState> resolvedAttributionSource =
- resolveAttributionSource(attributionSource);
- if (!resolvedAttributionSource.has_value()) {
- return;
- }
-
const int32_t attributedOpCode = getOpForSource(source);
- permission::PermissionChecker permissionChecker;
- permissionChecker.finishDataDeliveryFromDatasource(attributedOpCode,
- resolvedAttributionSource.value());
+ if (isRecordOpRequired(source)) {
+ // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+ // may open a record track on behalf of a client. Note that pid may be a tid.
+ // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+ const std::optional<AttributionSourceState> resolvedAttributionSource =
+ resolveAttributionSource(attributionSource);
+ if (!resolvedAttributionSource.has_value()) {
+ return;
+ }
+
+ permission::PermissionChecker permissionChecker;
+ permissionChecker.finishDataDeliveryFromDatasource(attributedOpCode,
+ resolvedAttributionSource.value());
+ } else {
+ if (attributedOpCode == AppOpsManager::OP_NONE) return; // nothing to do
+ AppOpsManager ap{};
+ PermissionController pc{};
+ ap.finishOp(attributedOpCode, attributionSource.uid,
+ resolveCallingPackage(
+ pc, String16{attributionSource.packageName.value_or("").c_str()},
+ attributionSource.uid),
+ attributionSource.attributionTag.has_value()
+ ? String16{attributionSource.attributionTag.value().c_str()}
+ : String16{});
+ }
}
bool captureAudioOutputAllowed(const AttributionSourceState& attributionSource) {