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