Create non-bypassable op restrictions
Even if the app is privileged or has the audio retriction bypass it
can't bypass these restrictions.
These will be used for the mic and camera toggles since under no
circumstances should sensitive information reach the application layer
while the toggles are muting.
Test: Verify privileged app couldn't access mic when toggle is blocking
Fixes: 188122748
Change-Id: Ib1bad074b3744fd565e8c8ff1c726be91c830d3e
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ed00436..92756b6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7413,8 +7413,18 @@
*/
public void setUserRestrictionForUser(int code, boolean restricted, IBinder token,
@Nullable Map<String, String[]> excludedPackageTags, int userId) {
+ setUserRestrictionForUser(code, restricted, token, excludedPackageTags, userId, false);
+ }
+
+ /**
+ * An empty array of attribution tags means exclude all tags under that package.
+ * @hide
+ */
+ public void setUserRestrictionForUser(int code, boolean restricted, IBinder token,
+ @Nullable Map<String, String[]> excludedPackageTags, int userId, boolean rejectBypass) {
try {
- mService.setUserRestriction(code, restricted, token, userId, excludedPackageTags);
+ mService.setUserRestriction(code, restricted, token, userId, excludedPackageTags,
+ rejectBypass);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 3cf4621..3cc7e64 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -92,7 +92,7 @@
void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle);
- void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in Map<String, String[]> excludedPackageTags);
+ void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, in Map<String, String[]> excludedPackageTags, boolean rejectBypass);
void removeUser(int userHandle);
void startWatchingActive(in int[] ops, IAppOpsActiveCallback callback);
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index baec544..6d0d589 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -26,7 +26,6 @@
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -55,6 +54,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.graphics.drawable.Icon;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
@@ -164,6 +164,8 @@
private EmergencyCallHelper mEmergencyCallHelper;
private KeyguardManager mKeyguardManager;
+ private int mCurrentUser = -1;
+
public SensorPrivacyService(Context context) {
super(context);
mContext = context;
@@ -173,6 +175,19 @@
mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
+
+ mUserManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserCreated(UserInfo user, Object token) {
+ setCurrentUserRestriction();
+ }
+
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ removeUserRestrictions(user.id);
+ }
+ });
}
@Override
@@ -191,6 +206,20 @@
}
}
+ @Override
+ public void onUserStarting(TargetUser user) {
+ if (mCurrentUser == -1) {
+ mCurrentUser = user.getUserIdentifier();
+ setCurrentUserRestriction();
+ }
+ }
+
+ @Override
+ public void onUserSwitching(TargetUser from, TargetUser to) {
+ mCurrentUser = to.getUserIdentifier();
+ setCurrentUserRestriction();
+ }
+
class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub implements
AppOpsManager.OnOpNotedListener, AppOpsManager.OnOpStartedListener,
IBinder.DeathRecipient {
@@ -1318,17 +1347,45 @@
}
private void setUserRestriction(int userId, int sensor, boolean enabled) {
- if (sensor == CAMERA) {
- mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, enabled,
- mAppOpsRestrictionToken, null, userId);
- } else if (sensor == MICROPHONE) {
- mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, enabled,
- mAppOpsRestrictionToken, null, userId);
- mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO_HOTWORD, enabled,
- mAppOpsRestrictionToken, null, userId);
+ if (userId == mCurrentUser) {
+ setCurrentUserRestriction(sensor, enabled);
}
}
+ private void setCurrentUserRestriction() {
+ boolean micState = mSensorPrivacyServiceImpl
+ .isIndividualSensorPrivacyEnabled(mCurrentUser, MICROPHONE);
+ boolean camState = mSensorPrivacyServiceImpl
+ .isIndividualSensorPrivacyEnabled(mCurrentUser, CAMERA);
+
+ setCurrentUserRestriction(MICROPHONE, micState);
+ setCurrentUserRestriction(CAMERA, camState);
+ }
+
+ private void setCurrentUserRestriction(int sensor, boolean enabled) {
+ int[] userIds = mUserManagerInternal.getUserIds();
+ int code;
+ if (sensor == MICROPHONE) {
+ code = OP_RECORD_AUDIO;
+ } else if (sensor == CAMERA) {
+ code = OP_CAMERA;
+ } else {
+ Log.w(TAG, "Invalid sensor id: " + sensor, new RuntimeException());
+ return;
+ }
+ for (int i = 0; i < userIds.length; i++) {
+ mAppOpsManager.setUserRestrictionForUser(code, enabled,
+ mAppOpsRestrictionToken, null, userIds[i], true);
+ }
+ }
+
+ private void removeUserRestrictions(int userId) {
+ mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, false,
+ mAppOpsRestrictionToken, null, userId, true);
+ mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, false,
+ mAppOpsRestrictionToken, null, userId, true);
+ }
+
private final class DeathRecipient implements IBinder.DeathRecipient {
private ISensorPrivacyListener mListener;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 76abfbf..0c046c4 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4564,6 +4564,9 @@
// package is exempt from the restriction.
ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle)) {
+ if (restrictionState.rejectBypass(code, userHandle)) {
+ return true;
+ }
RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
if (opBypass != null) {
// If we are the system, bypass user restrictions for certain codes
@@ -6145,6 +6148,8 @@
for (int j = 0; j < restrictionCount; j++) {
int userId = restrictionState.perUserRestrictions.keyAt(j);
boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
+ boolean[] rejectBypassOps =
+ restrictionState.perUserRejectBypasses.valueAt(j);
if (restrictedOps == null) {
continue;
}
@@ -6169,6 +6174,9 @@
restrictedOpsValue.append(", ");
}
restrictedOpsValue.append(AppOpsManager.opToName(k));
+ if (rejectBypassOps != null && rejectBypassOps[k]) {
+ restrictedOpsValue.append(" rejectBypass=true");
+ }
}
}
restrictedOpsValue.append("]");
@@ -6249,14 +6257,14 @@
String restriction = AppOpsManager.opToRestriction(i);
if (restriction != null) {
setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
- userHandle, null);
+ userHandle, null, false);
}
}
}
@Override
public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
- Map<String, String[]> excludedPackageTags) {
+ Map<String, String[]> excludedPackageTags, boolean rejectBypass) {
if (Binder.getCallingPid() != Process.myPid()) {
mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
Binder.getCallingPid(), Binder.getCallingUid(), null);
@@ -6272,11 +6280,12 @@
}
verifyIncomingOp(code);
Objects.requireNonNull(token);
- setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags,
+ rejectBypass);
}
private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
- int userHandle, Map<String, String[]> excludedPackageTags) {
+ int userHandle, Map<String, String[]> excludedPackageTags, boolean rejectBypass) {
synchronized (AppOpsService.this) {
ClientRestrictionState restrictionState = mOpUserRestrictions.get(token);
@@ -6290,7 +6299,7 @@
}
if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
- userHandle)) {
+ userHandle, rejectBypass)) {
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
@@ -6820,8 +6829,10 @@
private final class ClientRestrictionState implements DeathRecipient {
private final IBinder token;
SparseArray<boolean[]> perUserRestrictions;
+ SparseArray<boolean[]> perUserRejectBypasses;
SparseArray<Map<String, String[]>> perUserExcludedPackageTags;
+
public ClientRestrictionState(IBinder token)
throws RemoteException {
token.linkToDeath(this, 0);
@@ -6829,13 +6840,17 @@
}
public boolean setRestriction(int code, boolean restricted,
- Map<String, String[]> excludedPackageTags, int userId) {
+ Map<String, String[]> excludedPackageTags, int userId, boolean rejectBypass) {
boolean changed = false;
if (perUserRestrictions == null && restricted) {
perUserRestrictions = new SparseArray<>();
}
+ if (perUserRejectBypasses == null && rejectBypass) {
+ perUserRejectBypasses = new SparseArray<>();
+ }
+
int[] users;
if (userId == UserHandle.USER_ALL) {
// TODO(b/162888972): this call is returning all users, not just live ones - we
@@ -6895,6 +6910,20 @@
}
changed = true;
}
+
+ boolean[] userRejectBypasses = perUserRejectBypasses.get(thisUserId);
+ if (userRejectBypasses == null && rejectBypass) {
+ userRejectBypasses = new boolean[AppOpsManager._NUM_OP];
+ perUserRejectBypasses.put(thisUserId, userRejectBypasses);
+ }
+ if (userRejectBypasses != null
+ && userRejectBypasses[code] != rejectBypass) {
+ userRejectBypasses[code] = rejectBypass;
+ if (!rejectBypass && isDefault(userRejectBypasses)) {
+ perUserRejectBypasses.remove(thisUserId);
+ }
+ changed = true;
+ }
}
}
}
@@ -6932,6 +6961,17 @@
return !ArrayUtils.contains(excludedTags, attributionTag);
}
+ public boolean rejectBypass(int restriction, int userId) {
+ if (perUserRejectBypasses == null) {
+ return false;
+ }
+ boolean[] rejectBypasses = perUserRejectBypasses.get(userId);
+ if (rejectBypasses == null) {
+ return false;
+ }
+ return rejectBypasses[restriction];
+ }
+
public void removeUser(int userId) {
if (perUserExcludedPackageTags != null) {
perUserExcludedPackageTags.remove(userId);