Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2019 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.server.policy;
     18 
     19 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
     20 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
     21 import static android.app.AppOpsManager.MODE_ALLOWED;
     22 import static android.app.AppOpsManager.MODE_DEFAULT;
     23 import static android.app.AppOpsManager.MODE_IGNORED;
     24 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
     25 import static android.app.AppOpsManager.OP_NONE;
     26 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
     27 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
     28 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
     29 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
     30 
     31 import static java.lang.Integer.min;
     32 
     33 import android.annotation.NonNull;
     34 import android.annotation.Nullable;
     35 import android.app.AppOpsManager;
     36 import android.content.Context;
     37 import android.content.pm.ApplicationInfo;
     38 import android.content.pm.PackageManager;
     39 import android.os.Build;
     40 import android.os.UserHandle;
     41 
     42 /**
     43  * The behavior of soft restricted permissions is different for each permission. This class collects
     44  * the policies in one place.
     45  *
     46  * This is the twin of
     47  * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy}
     48  */
     49 public abstract class SoftRestrictedPermissionPolicy {
     50     private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
     51             FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
     52                     | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
     53                     | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
     54 
     55     private static final SoftRestrictedPermissionPolicy DUMMY_POLICY =
     56             new SoftRestrictedPermissionPolicy() {
     57                 @Override
     58                 public int resolveAppOp() {
     59                     return OP_NONE;
     60                 }
     61 
     62                 @Override
     63                 public int getDesiredOpMode() {
     64                     return MODE_DEFAULT;
     65                 }
     66 
     67                 @Override
     68                 public boolean shouldSetAppOpIfNotDefault() {
     69                     return false;
     70                 }
     71 
     72                 @Override
     73                 public boolean canBeGranted() {
     74                     return true;
     75                 }
     76             };
     77 
     78     /**
     79      * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over
     80      * what to set, always compute the combined targetSDK.
     81      *
     82      * @param context A context
     83      * @param appInfo The app that is changed
     84      * @param user The user the app belongs to
     85      *
     86      * @return The minimum targetSDK of all apps sharing the uid of the app
     87      */
     88     private static int getMinimumTargetSDK(@NonNull Context context,
     89             @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) {
     90         PackageManager pm = context.getPackageManager();
     91 
     92         int minimumTargetSDK = appInfo.targetSdkVersion;
     93 
     94         String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
     95         if (uidPkgs != null) {
     96             for (String uidPkg : uidPkgs) {
     97                 if (!uidPkg.equals(appInfo.packageName)) {
     98                     ApplicationInfo uidPkgInfo;
     99                     try {
    100                         uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
    101                     } catch (PackageManager.NameNotFoundException e) {
    102                         continue;
    103                     }
    104 
    105                     minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion);
    106                 }
    107             }
    108         }
    109 
    110         return minimumTargetSDK;
    111     }
    112 
    113     /**
    114      * Get the policy for a soft restricted permission.
    115      *
    116      * @param context A context to use
    117      * @param appInfo The application the permission belongs to. Can be {@code null}, but then
    118      *                only {@link #resolveAppOp} will work.
    119      * @param user The user the app belongs to. Can be {@code null}, but then only
    120      *             {@link #resolveAppOp} will work.
    121      * @param permission The name of the permission
    122      *
    123      * @return The policy for this permission
    124      */
    125     public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
    126             @Nullable ApplicationInfo appInfo, @Nullable UserHandle user,
    127             @NonNull String permission) {
    128         switch (permission) {
    129             // Storage uses a special app op to decide the mount state and supports soft restriction
    130             // where the restricted state allows the permission but only for accessing the medial
    131             // collections.
    132             case READ_EXTERNAL_STORAGE: {
    133                 final int flags;
    134                 final boolean applyRestriction;
    135                 final boolean isWhiteListed;
    136                 final boolean hasRequestedLegacyExternalStorage;
    137                 final int targetSDK;
    138 
    139                 if (appInfo != null) {
    140                     PackageManager pm = context.getPackageManager();
    141                     flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
    142                     applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
    143                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
    144                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
    145 
    146                     boolean hasAnyRequestedLegacyExternalStorage =
    147                             appInfo.hasRequestedLegacyExternalStorage();
    148 
    149                     // hasRequestedLegacyExternalStorage is per package. To make sure two apps in
    150                     // the same shared UID do not fight over what to set, always compute the
    151                     // combined hasRequestedLegacyExternalStorage
    152                     String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
    153                     if (uidPkgs != null) {
    154                         for (String uidPkg : uidPkgs) {
    155                             if (!uidPkg.equals(appInfo.packageName)) {
    156                                 ApplicationInfo uidPkgInfo;
    157                                 try {
    158                                     uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
    159                                 } catch (PackageManager.NameNotFoundException e) {
    160                                     continue;
    161                                 }
    162 
    163                                 hasAnyRequestedLegacyExternalStorage |=
    164                                         uidPkgInfo.hasRequestedLegacyExternalStorage();
    165                             }
    166                         }
    167                     }
    168 
    169                     hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage;
    170                 } else {
    171                     flags = 0;
    172                     applyRestriction = false;
    173                     isWhiteListed = false;
    174                     hasRequestedLegacyExternalStorage = false;
    175                     targetSDK = 0;
    176                 }
    177 
    178                 return new SoftRestrictedPermissionPolicy() {
    179                     @Override
    180                     public int resolveAppOp() {
    181                         return OP_LEGACY_STORAGE;
    182                     }
    183 
    184                     @Override
    185                     public int getDesiredOpMode() {
    186                         if (applyRestriction) {
    187                             return MODE_DEFAULT;
    188                         } else if (hasRequestedLegacyExternalStorage) {
    189                             return MODE_ALLOWED;
    190                         } else {
    191                             return MODE_IGNORED;
    192                         }
    193                     }
    194 
    195                     @Override
    196                     public boolean shouldSetAppOpIfNotDefault() {
    197                         // Do not switch from allowed -> ignored as this would mean to retroactively
    198                         // turn on isolated storage. This will make the app loose all its files.
    199                         return getDesiredOpMode() != MODE_IGNORED;
    200                     }
    201 
    202                     @Override
    203                     public boolean canBeGranted() {
    204                         if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) {
    205                             return true;
    206                         } else {
    207                             return false;
    208                         }
    209                     }
    210                 };
    211             }
    212             case WRITE_EXTERNAL_STORAGE: {
    213                 final boolean isWhiteListed;
    214                 final int targetSDK;
    215 
    216                 if (appInfo != null) {
    217                     final int flags = context.getPackageManager().getPermissionFlags(permission,
    218                             appInfo.packageName, user);
    219                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
    220                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
    221                 } else {
    222                     isWhiteListed = false;
    223                     targetSDK = 0;
    224                 }
    225 
    226                 return new SoftRestrictedPermissionPolicy() {
    227                     @Override
    228                     public int resolveAppOp() {
    229                         return OP_NONE;
    230                     }
    231 
    232                     @Override
    233                     public int getDesiredOpMode() {
    234                         return MODE_DEFAULT;
    235                     }
    236 
    237                     @Override
    238                     public boolean shouldSetAppOpIfNotDefault() {
    239                         return false;
    240                     }
    241 
    242                     @Override
    243                     public boolean canBeGranted() {
    244                         return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
    245                     }
    246                 };
    247             }
    248             default:
    249                 return DUMMY_POLICY;
    250         }
    251     }
    252 
    253     /**
    254      * @return An app op to be changed based on the state of the permission or
    255      * {@link AppOpsManager#OP_NONE} if not app-op should be set.
    256      */
    257     public abstract int resolveAppOp();
    258 
    259     /**
    260      * @return The mode the {@link #resolveAppOp() app op} should be in.
    261      */
    262     public abstract @AppOpsManager.Mode int getDesiredOpMode();
    263 
    264     /**
    265      * @return If the {@link #resolveAppOp() app op} should be set even if the app-op is currently
    266      * not {@link AppOpsManager#MODE_DEFAULT}.
    267      */
    268     public abstract boolean shouldSetAppOpIfNotDefault();
    269 
    270     /**
    271      * @return If the permission can be granted
    272      */
    273     public abstract boolean canBeGranted();
    274 }
    275