• 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.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