1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.audio;
17 
18 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
19 
20 import android.Manifest;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.app.AppOpsManager;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.media.AudioFocusRequest;
28 import android.media.AudioManager;
29 import android.os.Binder;
30 import android.os.Build;
31 import android.os.UserHandle;
32 import android.text.TextUtils;
33 import android.util.Slog;
34 
35 import com.android.server.utils.EventLogger;
36 
37 import java.io.PrintWriter;
38 
39 /**
40  * Class to encapsulate all audio API hardening operations
41  */
42 public class HardeningEnforcer {
43 
44     private static final String TAG = "AS.HardeningEnforcer";
45     private static final boolean DEBUG = false;
46     private static final int LOG_NB_EVENTS = 20;
47 
48     final Context mContext;
49     final AppOpsManager mAppOps;
50     final boolean mIsAutomotive;
51 
52     final ActivityManager mActivityManager;
53     final PackageManager mPackageManager;
54 
55     final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS,
56             "Hardening enforcement");
57 
58     /**
59      * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
60      */
61     public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100;
62     /**
63      * Matches calls from {@link AudioManager#adjustVolume(int, int)}
64      */
65     public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101;
66     /**
67      * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)}
68      */
69     public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102;
70     /**
71      * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)}
72      */
73     public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103;
74     /**
75      * Matches calls from {@link AudioManager#setRingerMode(int)}
76      */
77     public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200;
78     /**
79      * Matches calls from {@link AudioManager#requestAudioFocus(AudioFocusRequest)}
80      * and legacy variants
81      */
82     public static final int METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS = 300;
83 
HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, PackageManager pm)84     public HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps,
85             PackageManager pm) {
86         mContext = ctxt;
87         mIsAutomotive = isAutomotive;
88         mAppOps = appOps;
89         mActivityManager = ctxt.getSystemService(ActivityManager.class);
90         mPackageManager = pm;
91     }
92 
dump(PrintWriter pw)93     protected void dump(PrintWriter pw) {
94         // log
95         mEventLogger.dump(pw);
96     }
97 
98     /**
99      * Checks whether the call in the current thread should be allowed or blocked
100      * @param volumeMethod name of the method to check, for logging purposes
101      * @return false if the method call is allowed, true if it should be a no-op
102      */
blockVolumeMethod(int volumeMethod)103     protected boolean blockVolumeMethod(int volumeMethod) {
104         // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED
105         if (mIsAutomotive) {
106             if (!autoPublicVolumeApiHardening()) {
107                 // automotive hardening flag disabled, no blocking on auto
108                 return false;
109             }
110             if (mContext.checkCallingOrSelfPermission(
111                     Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
112                     == PackageManager.PERMISSION_GRANTED) {
113                 return false;
114             }
115             if (Binder.getCallingUid() < UserHandle.AID_APP_START) {
116                 return false;
117             }
118             // TODO metrics?
119             // TODO log for audio dumpsys?
120             Slog.e(TAG, "Preventing volume method " + volumeMethod + " for "
121                     + getPackNameForUid(Binder.getCallingUid()));
122             return true;
123         }
124         // not blocking
125         return false;
126     }
127 
128     /**
129      * Checks whether the call in the current thread should be allowed or blocked
130      * @param focusMethod name of the method to check, for logging purposes
131      * @param clientId id of the requester
132      * @param durationHint focus type being requested
133      * @param attributionTag attribution of the caller
134      * @param targetSdk target SDK of the caller
135      * @return false if the method call is allowed, true if it should be a no-op
136      */
137     @SuppressWarnings("AndroidFrameworkCompatChange")
blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId, int durationHint, @NonNull String packageName, String attributionTag, int targetSdk)138     protected boolean blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId,
139             int durationHint, @NonNull String packageName, String attributionTag, int targetSdk) {
140         if (packageName.isEmpty()) {
141             packageName = getPackNameForUid(callingUid);
142         }
143 
144         if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
145             if (DEBUG) {
146                 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
147             }
148             return false;
149         } else if (targetSdk < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
150             if (DEBUG) {
151                 Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking due to sdk="
152                         + targetSdk);
153             }
154             return false;
155         }
156 
157         String errorMssg = "Focus request DENIED for uid:" + callingUid
158                 + " clientId:" + clientId + " req:" + durationHint
159                 + " procState:" + mActivityManager.getUidProcessState(callingUid);
160 
161         // TODO metrics
162         mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG);
163 
164         return true;
165     }
166 
getPackNameForUid(int uid)167     private String getPackNameForUid(int uid) {
168         final long token = Binder.clearCallingIdentity();
169         try {
170             final String[] names = mPackageManager.getPackagesForUid(uid);
171             if (names == null
172                     || names.length == 0
173                     || TextUtils.isEmpty(names[0])) {
174                 return "[" + uid + "]";
175             }
176             return names[0];
177         } finally {
178             Binder.restoreCallingIdentity(token);
179         }
180     }
181 
182     /**
183      * Notes the given op without throwing
184      * @param op the appOp code
185      * @param uid the calling uid
186      * @param packageName the package name of the caller
187      * @param attributionTag attribution of the caller
188      * @return return false if the operation is not allowed
189      */
noteOp(int op, int uid, @NonNull String packageName, @Nullable String attributionTag)190     private boolean noteOp(int op, int uid, @NonNull String packageName,
191             @Nullable String attributionTag) {
192         if (mAppOps.noteOpNoThrow(op, uid, packageName, attributionTag, null)
193                 != AppOpsManager.MODE_ALLOWED) {
194             return false;
195         }
196         return true;
197     }
198 }
199