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      */
getMinimumTargetSDK(@onNull Context context, @NonNull ApplicationInfo appInfo, @NonNull UserHandle user)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      */
forPermission(@onNull Context context, @Nullable ApplicationInfo appInfo, @Nullable UserHandle user, @NonNull String permission)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