1 /*
2  * Copyright 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.net.module.util;
18 
19 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
20 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
21 import static android.Manifest.permission.NETWORK_STACK;
22 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
23 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
24 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
25 
26 import android.annotation.CheckResult;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.content.Context;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.PackageManager;
32 import android.os.Binder;
33 import android.os.UserHandle;
34 
35 import java.io.PrintWriter;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.List;
40 
41 /**
42  * Collection of permission utilities.
43  * @hide
44  */
45 public final class PermissionUtils {
46     /**
47      * Return true if the context has one of given permission.
48      */
49     @CheckResult
hasAnyPermissionOf(@onNull Context context, @NonNull String... permissions)50     public static boolean hasAnyPermissionOf(@NonNull Context context,
51                                              @NonNull String... permissions) {
52         for (String permission : permissions) {
53             if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
54                 return true;
55             }
56         }
57         return false;
58     }
59 
60     /**
61      * Return true if the context has one of given permission that is allowed
62      * for a particular process and user ID running in the system.
63      */
64     @CheckResult
hasAnyPermissionOf(@onNull Context context, int pid, int uid, @NonNull String... permissions)65     public static boolean hasAnyPermissionOf(@NonNull Context context,
66                                              int pid, int uid, @NonNull String... permissions) {
67         for (String permission : permissions) {
68             if (context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
69                 return true;
70             }
71         }
72         return false;
73     }
74 
75     /**
76      * Enforce permission check on the context that should have one of given permission.
77      */
enforceAnyPermissionOf(@onNull Context context, @NonNull String... permissions)78     public static void enforceAnyPermissionOf(@NonNull Context context,
79             @NonNull String... permissions) {
80         if (!hasAnyPermissionOf(context, permissions)) {
81             throw new SecurityException("Requires one of the following permissions: "
82                     + String.join(", ", permissions) + ".");
83         }
84     }
85 
86     /**
87      * If the NetworkStack, MAINLINE_NETWORK_STACK are not allowed for a particular process, throw a
88      * {@link SecurityException}.
89      *
90      * @param context {@link android.content.Context} for the process.
91      */
enforceNetworkStackPermission(final @NonNull Context context)92     public static void enforceNetworkStackPermission(final @NonNull Context context) {
93         enforceNetworkStackPermissionOr(context);
94     }
95 
96     /**
97      * If the NetworkStack, MAINLINE_NETWORK_STACK or other specified permissions are not allowed
98      * for a particular process, throw a {@link SecurityException}.
99      *
100      * @param context {@link android.content.Context} for the process.
101      * @param otherPermissions The set of permissions that could be the candidate permissions , or
102      *                         empty string if none of other permissions needed.
103      */
enforceNetworkStackPermissionOr(final @NonNull Context context, final @NonNull String... otherPermissions)104     public static void enforceNetworkStackPermissionOr(final @NonNull Context context,
105             final @NonNull String... otherPermissions) {
106         ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions));
107         permissions.add(NETWORK_STACK);
108         permissions.add(PERMISSION_MAINLINE_NETWORK_STACK);
109         enforceAnyPermissionOf(context, permissions.toArray(new String[0]));
110     }
111 
112     /**
113      * If the CONNECTIVITY_USE_RESTRICTED_NETWORKS is not allowed for a particular process, throw a
114      * {@link SecurityException}.
115      *
116      * @param context {@link android.content.Context} for the process.
117      * @param message A message to include in the exception if it is thrown.
118      */
enforceRestrictedNetworkPermission( final @NonNull Context context, final @Nullable String message)119     public static void enforceRestrictedNetworkPermission(
120             final @NonNull Context context, final @Nullable String message) {
121         context.enforceCallingOrSelfPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, message);
122     }
123 
124     /**
125      * If the ACCESS_NETWORK_STATE is not allowed for a particular process, throw a
126      * {@link SecurityException}.
127      *
128      * @param context {@link android.content.Context} for the process.
129      * @param message A message to include in the exception if it is thrown.
130      */
enforceAccessNetworkStatePermission( final @NonNull Context context, final @Nullable String message)131     public static void enforceAccessNetworkStatePermission(
132             final @NonNull Context context, final @Nullable String message) {
133         context.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, message);
134     }
135 
136     /**
137      * Return true if the context has DUMP permission.
138      */
139     @CheckResult
hasDumpPermission(Context context, String tag, PrintWriter pw)140     public static boolean hasDumpPermission(Context context, String tag, PrintWriter pw) {
141         if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
142                 != PERMISSION_GRANTED) {
143             pw.println("Permission Denial: can't dump " + tag + " from from pid="
144                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
145                     + " due to missing android.permission.DUMP permission");
146             return false;
147         } else {
148             return true;
149         }
150     }
151 
152     /**
153      * Enforce that a given feature is available and if not, throw an
154      * {@link UnsupportedOperationException}.
155      *
156      * @param context {@link android.content.Context} for the process.
157      * @param feature the feature name to enforce.
158      * @param errorMessage an optional error message to include.
159      */
enforceSystemFeature(final @NonNull Context context, final @NonNull String feature, final @Nullable String errorMessage)160     public static void enforceSystemFeature(final @NonNull Context context,
161             final @NonNull String feature, final @Nullable String errorMessage) {
162         final boolean hasSystemFeature =
163                 context.getPackageManager().hasSystemFeature(feature);
164         if (!hasSystemFeature) {
165             if (null == errorMessage) {
166                 throw new UnsupportedOperationException();
167             }
168             throw new UnsupportedOperationException(errorMessage);
169         }
170     }
171 
172     /**
173      * Get the list of granted permissions for a package info.
174      *
175      * PackageInfo contains the list of requested permissions, and their state (whether they
176      * were granted or not, in particular) as a parallel array. Most users care only about
177      * granted permissions. This method returns the list of them.
178      *
179      * @param packageInfo the package info for the relevant uid.
180      * @return the list of granted permissions.
181      */
getGrantedPermissions(final @NonNull PackageInfo packageInfo)182     public static List<String> getGrantedPermissions(final @NonNull PackageInfo packageInfo) {
183         if (null == packageInfo.requestedPermissions) return Collections.emptyList();
184         final ArrayList<String> result = new ArrayList<>(packageInfo.requestedPermissions.length);
185         for (int i = 0; i < packageInfo.requestedPermissions.length; ++i) {
186             if (0 != (REQUESTED_PERMISSION_GRANTED & packageInfo.requestedPermissionsFlags[i])) {
187                 result.add(packageInfo.requestedPermissions[i]);
188             }
189         }
190         return result;
191     }
192 
193     /**
194      * Enforces that the given package name belongs to the given uid.
195      *
196      * @param context {@link android.content.Context} for the process.
197      * @param uid User ID to check the package ownership for.
198      * @param packageName Package name to verify.
199      * @throws SecurityException If the package does not belong to the specified uid.
200      */
enforcePackageNameMatchesUid( @onNull Context context, int uid, @Nullable String packageName)201     public static void enforcePackageNameMatchesUid(
202             @NonNull Context context, int uid, @Nullable String packageName) {
203         final UserHandle user = UserHandle.getUserHandleForUid(uid);
204         if (getAppUid(context, packageName, user) != uid) {
205             throw new SecurityException(packageName + " does not belong to uid " + uid);
206         }
207     }
208 
getAppUid(Context context, final String app, final UserHandle user)209     private static int getAppUid(Context context, final String app, final UserHandle user) {
210         final PackageManager pm =
211                 context.createContextAsUser(user, 0 /* flags */).getPackageManager();
212         final long token = Binder.clearCallingIdentity();
213         try {
214             return pm.getPackageUid(app, 0 /* flags */);
215         } catch (PackageManager.NameNotFoundException e) {
216             return -1;
217         } finally {
218             Binder.restoreCallingIdentity(token);
219         }
220     }
221 }
222