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