1 /* 2 * Copyright (C) 2021 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 17 package com.android.compatibility.common.util; 18 19 import static android.telephony.TelephonyManager.CarrierPrivilegesCallback; 20 21 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 22 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 23 24 import static org.junit.Assert.fail; 25 26 import android.Manifest; 27 import android.annotation.TargetApi; 28 import android.content.Context; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageManager; 31 import android.os.Build; 32 import android.os.PersistableBundle; 33 import android.telephony.CarrierConfigManager; 34 import android.telephony.SubscriptionManager; 35 import android.telephony.TelephonyManager; 36 import android.util.Log; 37 38 import java.security.MessageDigest; 39 import java.util.Objects; 40 import java.util.Set; 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.atomic.AtomicBoolean; 44 45 /** 46 * Utility to execute a code block with carrier privileges, or as the carrier service. 47 * 48 * <p>The utility methods contained in this class will release carrier privileges once the specified 49 * task is completed. 50 * 51 * <p>This utility class explicitly does not support cases where the calling application is SIM 52 * privileged; in that case, the rescinding of carrier privileges will time out and fail. 53 * 54 * <p>Example: 55 * 56 * <pre> 57 * CarrierPrivilegeUtils.withCarrierPrivileges(c, subId, () -> telephonyManager.setFoo(bar)); 58 * CarrierPrivilegeUtils.asCarrierService(c, subId, () -> telephonyManager.setFoo(bar)); 59 * </pre> 60 * 61 * @see TelephonyManager#hasCarrierPrivileges() 62 */ 63 @TargetApi(Build.VERSION_CODES.TIRAMISU) 64 public final class CarrierPrivilegeUtils { 65 private static final String TAG = CarrierPrivilegeUtils.class.getSimpleName(); 66 67 private static class CarrierPrivilegeChangeMonitor implements AutoCloseable { 68 private final CountDownLatch mLatch = new CountDownLatch(1); 69 private final Context mContext; 70 private final boolean mIsShell; 71 private final TelephonyManager mTelephonyManager; 72 private final CarrierPrivilegesCallback mCarrierPrivilegesCallback; 73 74 /** 75 * Construct a {@link CarrierPrivilegesCallback} to monitor carrier privileges change. 76 * 77 * @param c context 78 * @param subId subscriptionId to listen to 79 * @param gainCarrierPrivileges true if wait to grant carrier privileges, false if wait to 80 * revoke 81 * @param overrideCarrierServicePackage {@code true} if this should wait for an override to 82 * take effect, {@code false} if this should wait for the override to be cleared 83 * @param isShell true if the caller is Shell 84 */ CarrierPrivilegeChangeMonitor( Context c, int subId, boolean gainCarrierPrivileges, boolean overrideCarrierServicePackage, boolean isShell)85 CarrierPrivilegeChangeMonitor( 86 Context c, 87 int subId, 88 boolean gainCarrierPrivileges, 89 boolean overrideCarrierServicePackage, 90 boolean isShell) { 91 mContext = c; 92 mIsShell = isShell; 93 mTelephonyManager = mContext.getSystemService( 94 TelephonyManager.class).createForSubscriptionId(subId); 95 Objects.requireNonNull(mTelephonyManager); 96 97 final int slotIndex = SubscriptionManager.getSlotIndex(subId); 98 mCarrierPrivilegesCallback = 99 new CarrierPrivilegesCallback() { 100 /* 101 * onCarrierServiceChanged() returns a @Nullable carrierServicePackageName, 102 * and TM#getCarrierServicePackageNameForLogicalSlot() requires 103 * using shell permission identity to get READ_PRIVILEGED_PHONE_STATE, which 104 * could clobber actual CTS package values. As such, we have to track both 105 * the carrier service package name, and that it has truly been set 106 * (including being set to null). The associated onCarrierServiceChanged 107 * callback will always be fired upon registration in the enclosing 108 * CarrierPrivilegeChangeMonitor constructor. 109 */ 110 private boolean mHasReceivedCarrierServicePackageName = false; 111 private String mCarrierServicePackage = null; 112 113 @Override 114 public void onCarrierPrivilegesChanged( 115 Set<String> privilegedPackageNames, Set<Integer> privilegedUids) { 116 verifyStateAndFireLatch(); 117 } 118 119 @Override 120 public void onCarrierServiceChanged( 121 String carrierServicePackageName, int carrierServiceUid) { 122 mCarrierServicePackage = carrierServicePackageName; 123 mHasReceivedCarrierServicePackageName = true; 124 verifyStateAndFireLatch(); 125 } 126 127 private void verifyStateAndFireLatch() { 128 if (mTelephonyManager.hasCarrierPrivileges() != gainCarrierPrivileges) { 129 return; 130 } 131 132 boolean isCurrentApp = 133 Objects.equals( 134 mCarrierServicePackage, mContext.getOpPackageName()); 135 if (!mHasReceivedCarrierServicePackageName 136 || isCurrentApp != overrideCarrierServicePackage) { 137 return; // Conditions not yet satisfied; return. 138 } 139 140 mLatch.countDown(); 141 } 142 }; 143 144 // Run with shell identify only when caller is not Shell to avoid overriding current 145 // SHELL permissions 146 if (mIsShell) { 147 mTelephonyManager.registerCarrierPrivilegesCallback( 148 slotIndex, mContext.getMainExecutor(), mCarrierPrivilegesCallback); 149 } else { 150 runWithShellPermissionIdentity(() -> { 151 mTelephonyManager.registerCarrierPrivilegesCallback( 152 slotIndex, 153 mContext.getMainExecutor(), 154 mCarrierPrivilegesCallback); 155 }, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 156 } 157 } 158 159 @Override close()160 public void close() { 161 if (mTelephonyManager == null) return; 162 163 if (mIsShell) { 164 mTelephonyManager.unregisterCarrierPrivilegesCallback(mCarrierPrivilegesCallback); 165 } else { 166 runWithShellPermissionIdentity( 167 () -> mTelephonyManager.unregisterCarrierPrivilegesCallback( 168 mCarrierPrivilegesCallback), 169 Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 170 } 171 } 172 waitForCarrierPrivilegeChanged()173 public void waitForCarrierPrivilegeChanged() throws Exception { 174 if (!mLatch.await(5, TimeUnit.SECONDS)) { 175 throw new IllegalStateException("Failed to update carrier privileges"); 176 } 177 } 178 } 179 getTelephonyManager(Context c, int subId)180 private static TelephonyManager getTelephonyManager(Context c, int subId) { 181 return c.getSystemService(TelephonyManager.class).createForSubscriptionId(subId); 182 } 183 hasCarrierPrivileges(Context c, int subId)184 private static boolean hasCarrierPrivileges(Context c, int subId) { 185 // Synchronously check for carrier privileges. Checking certificates MAY be incorrect if 186 // broadcasts are delayed. 187 return getTelephonyManager(c, subId).hasCarrierPrivileges(); 188 } 189 isCarrierServicePackage(Context c, int subId, boolean isShell)190 private static boolean isCarrierServicePackage(Context c, int subId, boolean isShell) { 191 // Synchronously check if the calling package is the carrier service package. 192 String carrierServicePackageName = null; 193 if (isShell) { 194 carrierServicePackageName = 195 getTelephonyManager(c, subId) 196 .getCarrierServicePackageNameForLogicalSlot( 197 SubscriptionManager.getSlotIndex(subId)); 198 } else { 199 carrierServicePackageName = runWithShellPermissionIdentity(() -> { 200 return getTelephonyManager(c, subId) 201 .getCarrierServicePackageNameForLogicalSlot( 202 SubscriptionManager.getSlotIndex(subId)); 203 }, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 204 } 205 206 return Objects.equals(c.getOpPackageName(), carrierServicePackageName); 207 } 208 getCertHashForThisPackage(final Context c)209 private static String getCertHashForThisPackage(final Context c) throws Exception { 210 final PackageInfo pkgInfo = c.getPackageManager() 211 .getPackageInfo(c.getOpPackageName(), PackageManager.GET_SIGNATURES); 212 final MessageDigest md = MessageDigest.getInstance("SHA-256"); 213 final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray()); 214 return UiccUtil.bytesToHexString(certHash); 215 } 216 changeCarrierPrivileges( Context c, int subId, boolean gainCarrierPrivileges, boolean overrideCarrierServicePackage, boolean isShell)217 private static void changeCarrierPrivileges( 218 Context c, 219 int subId, 220 boolean gainCarrierPrivileges, 221 boolean overrideCarrierServicePackage, 222 boolean isShell) 223 throws Exception { 224 if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { 225 throw new IllegalStateException("CarrierPrivilegeUtils requires at least SDK 33"); 226 } 227 228 if (hasCarrierPrivileges(c, subId) == gainCarrierPrivileges 229 && isCarrierServicePackage(c, subId, isShell) == overrideCarrierServicePackage) { 230 Log.w( 231 TAG, 232 "Carrier privileges already " 233 + (gainCarrierPrivileges ? "granted" : "revoked") 234 + "or carrier service already " 235 + (overrideCarrierServicePackage ? "overridden" : "cleared") 236 + "; bug?"); 237 return; 238 } 239 240 final String certHash = getCertHashForThisPackage(c); 241 final PersistableBundle carrierConfigs; 242 243 if (gainCarrierPrivileges) { 244 carrierConfigs = new PersistableBundle(); 245 carrierConfigs.putStringArray( 246 CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, 247 new String[] {certHash}); 248 } else { 249 carrierConfigs = null; 250 } 251 252 final CarrierConfigManager configManager = c.getSystemService(CarrierConfigManager.class); 253 254 try (CarrierPrivilegeChangeMonitor monitor = 255 new CarrierPrivilegeChangeMonitor( 256 c, subId, gainCarrierPrivileges, overrideCarrierServicePackage, isShell)) { 257 // If the caller is the shell, it's dangerous to adopt shell permission identity for 258 // the CarrierConfig override (as it will override the existing shell permissions). 259 if (isShell) { 260 configManager.overrideConfig(subId, carrierConfigs); 261 } else { 262 runWithShellPermissionIdentity(() -> { 263 configManager.overrideConfig(subId, carrierConfigs); 264 }, android.Manifest.permission.MODIFY_PHONE_STATE); 265 } 266 267 if (overrideCarrierServicePackage) { 268 runShellCommand( 269 "cmd phone set-carrier-service-package-override -s " 270 + subId 271 + " " 272 + c.getOpPackageName()); 273 } else { 274 runShellCommand("cmd phone clear-carrier-service-package-override -s " + subId); 275 } 276 277 monitor.waitForCarrierPrivilegeChanged(); 278 } 279 } 280 281 /** 282 * Utility class to prevent nested calls 283 * 284 * <p>Unless refcounted, a nested call will clear privileges on the outer call. 285 */ 286 private static class NestedCallChecker implements AutoCloseable { 287 private static final AtomicBoolean sCheckBit = new AtomicBoolean(); 288 NestedCallChecker()289 private NestedCallChecker() { 290 if (!sCheckBit.compareAndSet(false /* expected */, true /* update */)) { 291 fail("Nested CarrierPrivilegeUtils calls are not supported"); 292 } 293 } 294 295 @Override close()296 public void close() { 297 sCheckBit.set(false); 298 } 299 } 300 301 /** Runs the provided action with the calling package granted carrier privileges. */ withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)302 public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action) 303 throws Exception { 304 try (NestedCallChecker checker = new NestedCallChecker()) { 305 changeCarrierPrivileges( 306 c, 307 subId, 308 true /* gainCarrierPrivileges */, 309 false /* overrideCarrierServicePackage */, 310 false /* isShell */); 311 action.run(); 312 } finally { 313 changeCarrierPrivileges( 314 c, 315 subId, 316 false /* gainCarrierPrivileges */, 317 false /* overrideCarrierServicePackage */, 318 false /* isShell */); 319 } 320 } 321 322 /** 323 * Runs the provided action with the calling package granted carrier privileges. 324 * 325 * <p>This variant of the method does NOT acquire shell identity to prevent overriding current 326 * shell permissions. The caller is expected to hold the READ_PRIVILEGED_PHONE_STATE permission. 327 */ withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)328 public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action) 329 throws Exception { 330 try (NestedCallChecker checker = new NestedCallChecker()) { 331 changeCarrierPrivileges( 332 c, 333 subId, 334 true /* gainCarrierPrivileges */, 335 false /* overrideCarrierServicePackage */, 336 true /* isShell */); 337 action.run(); 338 } finally { 339 changeCarrierPrivileges( 340 c, 341 subId, 342 false /* gainCarrierPrivileges */, 343 false /* overrideCarrierServicePackage */, 344 true /* isShell */); 345 } 346 } 347 348 /** Runs the provided action with the calling package granted carrier privileges. */ withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)349 public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action) 350 throws Exception { 351 try (NestedCallChecker checker = new NestedCallChecker()) { 352 changeCarrierPrivileges( 353 c, 354 subId, 355 true /* gainCarrierPrivileges */, 356 false /* overrideCarrierServicePackage */, 357 false /* isShell */); 358 return action.get(); 359 } finally { 360 changeCarrierPrivileges( 361 c, 362 subId, 363 false /* gainCarrierPrivileges */, 364 false /* overrideCarrierServicePackage */, 365 false /* isShell */); 366 } 367 } 368 369 /** 370 * Runs the provided action with the calling package set as the Carrier Service. 371 * 372 * <p>This will also run the action with carrier privileges, which is a necessary condition to 373 * be a carrier service. 374 */ asCarrierService(Context c, int subId, ThrowingRunnable action)375 public static void asCarrierService(Context c, int subId, ThrowingRunnable action) 376 throws Exception { 377 try (NestedCallChecker checker = new NestedCallChecker()) { 378 changeCarrierPrivileges( 379 c, 380 subId, 381 true /* gainCarrierPrivileges */, 382 true /* overrideCarrierServicePackage */, 383 false /* isShell */); 384 action.run(); 385 } finally { 386 changeCarrierPrivileges( 387 c, 388 subId, 389 false /* gainCarrierPrivileges */, 390 false /* overrideCarrierServicePackage */, 391 false /* isShell */); 392 } 393 } 394 395 /** 396 * Runs the provided action with the calling package set as the Carrier Service. 397 * 398 * <p>This will also run the action with carrier privileges, which is a necessary condition to 399 * be a carrier service. 400 * 401 * <p>This variant of the method does NOT acquire shell identity to prevent overriding current 402 * shell permissions. The caller is expected to hold the READ_PRIVILEGED_PHONE_STATE permission. 403 */ asCarrierServiceForShell(Context c, int subId, ThrowingRunnable action)404 public static void asCarrierServiceForShell(Context c, int subId, ThrowingRunnable action) 405 throws Exception { 406 try (NestedCallChecker checker = new NestedCallChecker()) { 407 changeCarrierPrivileges( 408 c, 409 subId, 410 true /* gainCarrierPrivileges */, 411 true /* overrideCarrierServicePackage */, 412 true /* isShell */); 413 action.run(); 414 } finally { 415 changeCarrierPrivileges( 416 c, 417 subId, 418 false /* gainCarrierPrivileges */, 419 false /* overrideCarrierServicePackage */, 420 true /* isShell */); 421 } 422 } 423 424 /** 425 * Runs the provided action with the calling package set as the Carrier Service. 426 * 427 * <p>This will also run the action with carrier privileges, which is a necessary condition to 428 * be a carrier service. 429 */ asCarrierService(Context c, int subId, ThrowingSupplier<R> action)430 public static <R> R asCarrierService(Context c, int subId, ThrowingSupplier<R> action) 431 throws Exception { 432 try (NestedCallChecker checker = new NestedCallChecker()) { 433 changeCarrierPrivileges( 434 c, 435 subId, 436 true /* gainCarrierPrivileges */, 437 true /* overrideCarrierServicePackage */, 438 false /* isShell */); 439 return action.get(); 440 } finally { 441 changeCarrierPrivileges( 442 c, 443 subId, 444 false /* gainCarrierPrivileges */, 445 false /* overrideCarrierServicePackage */, 446 false /* isShell */); 447 } 448 } 449 } 450