Refactor security checks to register policies

There is no functional changes in this patch

The checks were much more complex then needed.

The behaviour on empty mix was not explicit, it depended on non trivial behaviour
of anyMatch and allMatch and the code flow.
There was an early positive return leading to the rest of the permission
check to be skipped. Which was correct but prone to bugs if the code was
changed.
The code in general was hard to change without introducing security bug
as well as difficult to review.

Change it to a two part section, first the permissions needed are
determined explicitly.
Then those permissions are checked.
Early return is only used for permission denial.

Test: atest android.media.cts.AudioPlaybackCaptureTest#testCaptureMediaUsage
Bug: 129948989
Change-Id: I6b4177e47025af983ba6c980d58ea308c4c378f1
Signed-off-by: Kevin Rocard <krocard@google.com>
This commit is contained in:
Kevin Rocard
2019-04-03 12:37:32 -07:00
parent 9637326ea8
commit 935df08b15

View File

@@ -6741,40 +6741,58 @@ public class AudioService extends IAudioService.Stub
private boolean isPolicyRegisterAllowed(AudioPolicyConfig policyConfig,
IMediaProjection projection) {
boolean isLoopbackRenderPolicy = policyConfig.getMixes().stream().allMatch(
mix -> mix.getRouteFlags() == (mix.ROUTE_FLAG_RENDER | mix.ROUTE_FLAG_LOOP_BACK));
boolean requireValidProjection = false;
boolean requireCaptureAudioOrMediaOutputPerm = false;
boolean requireModifyRouting = false;
if (isLoopbackRenderPolicy) {
boolean allowPrivilegedPlaybackCapture = policyConfig.getMixes().stream().anyMatch(
mix -> mix.getRule().allowPrivilegedPlaybackCapture());
if (allowPrivilegedPlaybackCapture
&& !(hasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)
|| hasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT))) {
// Opt-out can not be bypassed without a system permission
return false;
if (policyConfig.getMixes().isEmpty()) {
requireModifyRouting |= true;
}
for (AudioMix mix : policyConfig.getMixes()) {
// If mix is requesting a privileged capture
if (mix.getRule().allowPrivilegedPlaybackCapture()) {
// then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission
requireCaptureAudioOrMediaOutputPerm |= true;
}
if (canProjectAudio(projection)) {
// Policy that do not modify the audio routing only need an audio projection
return true;
// If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough
// otherwise MODIFY_AUDIO_ROUTING permission is required
if (mix.getRouteFlags() == mix.ROUTE_FLAG_LOOP_BACK_RENDER && projection != null) {
requireValidProjection |= true;
} else {
requireModifyRouting |= true;
}
}
boolean hasPermissionModifyAudioRouting =
(PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
android.Manifest.permission.MODIFY_AUDIO_ROUTING));
if (hasPermissionModifyAudioRouting) {
return true;
if (requireCaptureAudioOrMediaOutputPerm
&& !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT)
&& !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) {
Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or "
+ "CAPTURE_AUDIO_OUTPUT system permission");
return false;
}
return false;
if (requireValidProjection && !canProjectAudio(projection)) {
return false;
}
if (requireModifyRouting
&& !callerHasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)) {
Log.e(TAG, "Can not capture audio without MODIFY_AUDIO_ROUTING");
return false;
}
return true;
}
private boolean hasPermission(String permission) {
return PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(permission);
private boolean callerHasPermission(String permission) {
return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
/** @return true if projection is a valid MediaProjection that can project audio. */
private boolean canProjectAudio(IMediaProjection projection) {
if (projection == null) {
Log.e(TAG, "MediaProjection is null");
return false;
}