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( 176 "Unable to update carrier privileges. Phone process may be dead."); 177 } 178 } 179 } 180 getTelephonyManager(Context c, int subId)181 private static TelephonyManager getTelephonyManager(Context c, int subId) { 182 return c.getSystemService(TelephonyManager.class).createForSubscriptionId(subId); 183 } 184 hasCarrierPrivileges(Context c, int subId)185 private static boolean hasCarrierPrivileges(Context c, int subId) { 186 // Synchronously check for carrier privileges. Checking certificates MAY be incorrect if 187 // broadcasts are delayed. 188 return getTelephonyManager(c, subId).hasCarrierPrivileges(); 189 } 190 isCarrierServicePackage(Context c, int subId, boolean isShell)191 private static boolean isCarrierServicePackage(Context c, int subId, boolean isShell) { 192 // Synchronously check if the calling package is the carrier service package. 193 String carrierServicePackageName = null; 194 if (isShell) { 195 carrierServicePackageName = 196 getTelephonyManager(c, subId) 197 .getCarrierServicePackageNameForLogicalSlot( 198 SubscriptionManager.getSlotIndex(subId)); 199 } else { 200 carrierServicePackageName = runWithShellPermissionIdentity(() -> { 201 return getTelephonyManager(c, subId) 202 .getCarrierServicePackageNameForLogicalSlot( 203 SubscriptionManager.getSlotIndex(subId)); 204 }, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 205 } 206 207 return Objects.equals(c.getOpPackageName(), carrierServicePackageName); 208 } 209 getCertHashForThisPackage(final Context c)210 private static String getCertHashForThisPackage(final Context c) throws Exception { 211 final PackageInfo pkgInfo = c.getPackageManager() 212 .getPackageInfo(c.getOpPackageName(), PackageManager.GET_SIGNATURES); 213 final MessageDigest md = MessageDigest.getInstance("SHA-256"); 214 final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray()); 215 return UiccUtil.bytesToHexString(certHash); 216 } 217 changeCarrierPrivileges( Context c, int subId, boolean gainCarrierPrivileges, boolean overrideCarrierServicePackage, boolean isShell)218 private static void changeCarrierPrivileges( 219 Context c, 220 int subId, 221 boolean gainCarrierPrivileges, 222 boolean overrideCarrierServicePackage, 223 boolean isShell) 224 throws Exception { 225 if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { 226 throw new IllegalStateException("CarrierPrivilegeUtils requires at least SDK 33"); 227 } 228 229 if (hasCarrierPrivileges(c, subId) == gainCarrierPrivileges 230 && isCarrierServicePackage(c, subId, isShell) == overrideCarrierServicePackage) { 231 Log.w( 232 TAG, 233 "Carrier privileges already " 234 + (gainCarrierPrivileges ? "granted" : "revoked") 235 + "or carrier service already " 236 + (overrideCarrierServicePackage ? "overridden" : "cleared") 237 + "; bug?"); 238 return; 239 } 240 241 final String certHash = getCertHashForThisPackage(c); 242 final PersistableBundle carrierConfigs; 243 244 if (gainCarrierPrivileges) { 245 carrierConfigs = new PersistableBundle(); 246 carrierConfigs.putStringArray( 247 CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, 248 new String[] {certHash}); 249 } else { 250 carrierConfigs = null; 251 } 252 253 final CarrierConfigManager configManager = c.getSystemService(CarrierConfigManager.class); 254 255 try (CarrierPrivilegeChangeMonitor monitor = 256 new CarrierPrivilegeChangeMonitor( 257 c, subId, gainCarrierPrivileges, overrideCarrierServicePackage, isShell)) { 258 // If the caller is the shell, it's dangerous to adopt shell permission identity for 259 // the CarrierConfig override (as it will override the existing shell permissions). 260 if (isShell) { 261 configManager.overrideConfig(subId, carrierConfigs); 262 } else { 263 runWithShellPermissionIdentity(() -> { 264 configManager.overrideConfig(subId, carrierConfigs); 265 }, android.Manifest.permission.MODIFY_PHONE_STATE); 266 } 267 268 if (overrideCarrierServicePackage) { 269 runShellCommand( 270 "cmd phone set-carrier-service-package-override -s " 271 + subId 272 + " " 273 + c.getOpPackageName()); 274 } else { 275 runShellCommand("cmd phone clear-carrier-service-package-override -s " + subId); 276 } 277 278 monitor.waitForCarrierPrivilegeChanged(); 279 } 280 } 281 282 /** 283 * Utility class to prevent nested calls 284 * 285 * <p>Unless refcounted, a nested call will clear privileges on the outer call. 286 */ 287 private static class NestedCallChecker implements AutoCloseable { 288 private static final AtomicBoolean sCheckBit = new AtomicBoolean(); 289 NestedCallChecker()290 private NestedCallChecker() { 291 if (!sCheckBit.compareAndSet(false /* expected */, true /* update */)) { 292 fail("Nested CarrierPrivilegeUtils calls are not supported"); 293 } 294 } 295 296 @Override close()297 public void close() { 298 sCheckBit.set(false); 299 } 300 } 301 302 /** Runs the provided action with the calling package granted carrier privileges. */ withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)303 public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action) 304 throws Exception { 305 try (NestedCallChecker checker = new NestedCallChecker()) { 306 changeCarrierPrivileges( 307 c, 308 subId, 309 true /* gainCarrierPrivileges */, 310 false /* overrideCarrierServicePackage */, 311 false /* isShell */); 312 action.run(); 313 } finally { 314 changeCarrierPrivileges( 315 c, 316 subId, 317 false /* gainCarrierPrivileges */, 318 false /* overrideCarrierServicePackage */, 319 false /* isShell */); 320 } 321 } 322 323 /** 324 * Runs the provided action with the calling package granted carrier privileges. 325 * 326 * <p>This variant of the method does NOT acquire shell identity to prevent overriding current 327 * shell permissions. The caller is expected to hold the READ_PRIVILEGED_PHONE_STATE permission. 328 */ withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)329 public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action) 330 throws Exception { 331 try (NestedCallChecker checker = new NestedCallChecker()) { 332 changeCarrierPrivileges( 333 c, 334 subId, 335 true /* gainCarrierPrivileges */, 336 false /* overrideCarrierServicePackage */, 337 true /* isShell */); 338 action.run(); 339 } finally { 340 changeCarrierPrivileges( 341 c, 342 subId, 343 false /* gainCarrierPrivileges */, 344 false /* overrideCarrierServicePackage */, 345 true /* isShell */); 346 } 347 } 348 349 /** Runs the provided action with the calling package granted carrier privileges. */ withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)350 public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action) 351 throws Exception { 352 try (NestedCallChecker checker = new NestedCallChecker()) { 353 changeCarrierPrivileges( 354 c, 355 subId, 356 true /* gainCarrierPrivileges */, 357 false /* overrideCarrierServicePackage */, 358 false /* isShell */); 359 return action.get(); 360 } finally { 361 changeCarrierPrivileges( 362 c, 363 subId, 364 false /* gainCarrierPrivileges */, 365 false /* overrideCarrierServicePackage */, 366 false /* isShell */); 367 } 368 } 369 370 /** 371 * Runs the provided action with the calling package set as the Carrier Service. 372 * 373 * <p>This will also run the action with carrier privileges, which is a necessary condition to 374 * be a carrier service. 375 */ asCarrierService(Context c, int subId, ThrowingRunnable action)376 public static void asCarrierService(Context c, int subId, ThrowingRunnable action) 377 throws Exception { 378 try (NestedCallChecker checker = new NestedCallChecker()) { 379 changeCarrierPrivileges( 380 c, 381 subId, 382 true /* gainCarrierPrivileges */, 383 true /* overrideCarrierServicePackage */, 384 false /* isShell */); 385 action.run(); 386 } finally { 387 changeCarrierPrivileges( 388 c, 389 subId, 390 false /* gainCarrierPrivileges */, 391 false /* overrideCarrierServicePackage */, 392 false /* isShell */); 393 } 394 } 395 396 /** 397 * Runs the provided action with the calling package set as the Carrier Service. 398 * 399 * <p>This will also run the action with carrier privileges, which is a necessary condition to 400 * be a carrier service. 401 * 402 * <p>This variant of the method does NOT acquire shell identity to prevent overriding current 403 * shell permissions. The caller is expected to hold the READ_PRIVILEGED_PHONE_STATE permission. 404 */ asCarrierServiceForShell(Context c, int subId, ThrowingRunnable action)405 public static void asCarrierServiceForShell(Context c, int subId, ThrowingRunnable action) 406 throws Exception { 407 try (NestedCallChecker checker = new NestedCallChecker()) { 408 changeCarrierPrivileges( 409 c, 410 subId, 411 true /* gainCarrierPrivileges */, 412 true /* overrideCarrierServicePackage */, 413 true /* isShell */); 414 action.run(); 415 } finally { 416 changeCarrierPrivileges( 417 c, 418 subId, 419 false /* gainCarrierPrivileges */, 420 false /* overrideCarrierServicePackage */, 421 true /* isShell */); 422 } 423 } 424 425 /** 426 * Runs the provided action with the calling package set as the Carrier Service. 427 * 428 * <p>This will also run the action with carrier privileges, which is a necessary condition to 429 * be a carrier service. 430 */ asCarrierService(Context c, int subId, ThrowingSupplier<R> action)431 public static <R> R asCarrierService(Context c, int subId, ThrowingSupplier<R> action) 432 throws Exception { 433 try (NestedCallChecker checker = new NestedCallChecker()) { 434 changeCarrierPrivileges( 435 c, 436 subId, 437 true /* gainCarrierPrivileges */, 438 true /* overrideCarrierServicePackage */, 439 false /* isShell */); 440 return action.get(); 441 } finally { 442 changeCarrierPrivileges( 443 c, 444 subId, 445 false /* gainCarrierPrivileges */, 446 false /* overrideCarrierServicePackage */, 447 false /* isShell */); 448 } 449 } 450 } 451