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.runWithShellPermissionIdentity; 22 23 import android.Manifest; 24 import android.content.Context; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.os.PersistableBundle; 28 import android.telephony.CarrierConfigManager; 29 import android.telephony.SubscriptionManager; 30 import android.telephony.TelephonyManager; 31 import android.util.Log; 32 33 import java.security.MessageDigest; 34 import java.util.Objects; 35 import java.util.concurrent.CountDownLatch; 36 import java.util.concurrent.TimeUnit; 37 38 /** 39 * Utility to execute a code block with carrier privileges. 40 * 41 * <p>The utility methods contained in this class will release carrier privileges once the specified 42 * task is completed. 43 * 44 * <p>Example: 45 * <pre> 46 * CarrierPrivilegeUtils.withCarrierPrivileges(c, subId, () -> telephonyManager.setFoo(bar)); 47 * </pre> 48 * 49 * @see {@link TelephonyManager#hasCarrierPrivileges()} 50 */ 51 public final class CarrierPrivilegeUtils { 52 private static final String TAG = CarrierPrivilegeUtils.class.getSimpleName(); 53 54 private static class CarrierPrivilegeChangeMonitor implements AutoCloseable { 55 private final CountDownLatch mLatch = new CountDownLatch(1); 56 private final Context mContext; 57 private final int mSubId; 58 private final boolean mGain; 59 private final boolean mIsShell; 60 private final TelephonyManager mTelephonyManager; 61 private final CarrierPrivilegesCallback mCarrierPrivilegesCallback; 62 63 /** 64 * Construct a {@link CarrierPrivilegesCallback} to monitor carrier privileges change. 65 * @param c context 66 * @param subId subscriptionId to listen to 67 * @param gain true if wait to grant carrier privileges, false if wait to revoke 68 * @param isShell true if the caller is Shell 69 */ CarrierPrivilegeChangeMonitor(Context c, int subId, boolean gain, boolean isShell)70 CarrierPrivilegeChangeMonitor(Context c, int subId, boolean gain, boolean isShell) { 71 mContext = c; 72 mSubId = subId; 73 mGain = gain; 74 mIsShell = isShell; 75 mTelephonyManager = mContext.getSystemService( 76 TelephonyManager.class).createForSubscriptionId(subId); 77 Objects.requireNonNull(mTelephonyManager); 78 79 mCarrierPrivilegesCallback = (privilegedPackageNames, privilegedUids) -> { 80 if (mTelephonyManager.hasCarrierPrivileges() == mGain) { 81 mLatch.countDown(); 82 } 83 }; 84 85 // Run with shell identify only when caller is not Shell to avoid overriding current 86 // SHELL permissions 87 if (mIsShell) { 88 mTelephonyManager.registerCarrierPrivilegesCallback( 89 SubscriptionManager.getSlotIndex(subId), 90 mContext.getMainExecutor(), 91 mCarrierPrivilegesCallback); 92 } else { 93 runWithShellPermissionIdentity(() -> { 94 mTelephonyManager.registerCarrierPrivilegesCallback( 95 SubscriptionManager.getSlotIndex(subId), 96 mContext.getMainExecutor(), 97 mCarrierPrivilegesCallback); 98 }, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 99 } 100 } 101 102 @Override close()103 public void close() { 104 if (mTelephonyManager == null) return; 105 106 if (mIsShell) { 107 mTelephonyManager.unregisterCarrierPrivilegesCallback(mCarrierPrivilegesCallback); 108 } else { 109 runWithShellPermissionIdentity( 110 () -> mTelephonyManager.unregisterCarrierPrivilegesCallback( 111 mCarrierPrivilegesCallback), 112 Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 113 } 114 } 115 waitForCarrierPrivilegeChanged()116 public void waitForCarrierPrivilegeChanged() throws Exception { 117 if (!mLatch.await(5, TimeUnit.SECONDS)) { 118 throw new IllegalStateException("Failed to update carrier privileges"); 119 } 120 } 121 } 122 hasCarrierPrivileges(Context c, int subId)123 private static boolean hasCarrierPrivileges(Context c, int subId) { 124 // Synchronously check for carrier privileges. Checking certificates MAY be incorrect if 125 // broadcasts are delayed. 126 return c.getSystemService(TelephonyManager.class) 127 .createForSubscriptionId(subId) 128 .hasCarrierPrivileges(); 129 } 130 getCertHashForThisPackage(final Context c)131 private static String getCertHashForThisPackage(final Context c) throws Exception { 132 final PackageInfo pkgInfo = c.getPackageManager() 133 .getPackageInfo(c.getOpPackageName(), PackageManager.GET_SIGNATURES); 134 final MessageDigest md = MessageDigest.getInstance("SHA-256"); 135 final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray()); 136 return UiccUtil.bytesToHexString(certHash); 137 } 138 changeCarrierPrivileges(Context c, int subId, boolean gain, boolean isShell)139 private static void changeCarrierPrivileges(Context c, int subId, boolean gain, boolean isShell) 140 throws Exception { 141 if (hasCarrierPrivileges(c, subId) == gain) { 142 Log.w(TAG, "Carrier privileges already " + (gain ? "granted" : "revoked") + "; bug?"); 143 return; 144 } 145 146 final String certHash = getCertHashForThisPackage(c); 147 final PersistableBundle carrierConfigs; 148 149 if (gain) { 150 carrierConfigs = new PersistableBundle(); 151 carrierConfigs.putStringArray( 152 CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, 153 new String[] {certHash}); 154 } else { 155 carrierConfigs = null; 156 } 157 158 final CarrierConfigManager configManager = c.getSystemService(CarrierConfigManager.class); 159 160 try (CarrierPrivilegeChangeMonitor monitor = 161 new CarrierPrivilegeChangeMonitor(c, subId, gain, isShell)) { 162 // If the caller is the shell, it's dangerous to adopt shell permission identity for 163 // the CarrierConfig override (as it will override the existing shell permissions). 164 if (isShell) { 165 configManager.overrideConfig(subId, carrierConfigs); 166 } else { 167 runWithShellPermissionIdentity(() -> { 168 configManager.overrideConfig(subId, carrierConfigs); 169 }, android.Manifest.permission.MODIFY_PHONE_STATE); 170 } 171 172 monitor.waitForCarrierPrivilegeChanged(); 173 } 174 } 175 withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)176 public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action) 177 throws Exception { 178 try { 179 changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */); 180 action.run(); 181 } finally { 182 changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */); 183 } 184 } 185 186 /** Completes the provided action while assuming the caller is the Shell. */ withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)187 public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action) 188 throws Exception { 189 try { 190 changeCarrierPrivileges(c, subId, true /* gain */, true /* isShell */); 191 action.run(); 192 } finally { 193 changeCarrierPrivileges(c, subId, false /* lose */, true /* isShell */); 194 } 195 } 196 withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)197 public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action) 198 throws Exception { 199 try { 200 changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */); 201 return action.get(); 202 } finally { 203 changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */); 204 } 205 } 206 } 207