• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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