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.Manifest.permission.WRITE_MEDIA_STORAGE;
22 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
23 import static android.app.AppOpsManager.OP_NONE;
24 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
25 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
26 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
27 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
28 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
29 
30 import static java.lang.Integer.min;
31 
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.app.AppOpsManager;
35 import android.content.Context;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.os.Build;
39 import android.os.UserHandle;
40 import android.os.storage.StorageManager;
41 import android.os.storage.StorageManagerInternal;
42 import android.provider.DeviceConfig;
43 
44 import com.android.server.LocalServices;
45 import com.android.server.pm.pkg.AndroidPackage;
46 
47 import java.util.Arrays;
48 import java.util.HashSet;
49 
50 /**
51  * The behavior of soft restricted permissions is different for each permission. This class collects
52  * the policies in one place.
53  *
54  * This is the twin of
55  * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy}
56  */
57 public abstract class SoftRestrictedPermissionPolicy {
58     private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
59             FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
60                     | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
61                     | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
62 
63     private static final SoftRestrictedPermissionPolicy DUMMY_POLICY =
64             new SoftRestrictedPermissionPolicy() {
65                 @Override
66                 public boolean mayGrantPermission() {
67                     return true;
68                 }
69             };
70 
71     private static final HashSet<String> sForcedScopedStorageAppWhitelist = new HashSet<>(
72             Arrays.asList(getForcedScopedStorageAppWhitelist()));
73 
74     /**
75      * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over
76      * what to set, always compute the combined targetSDK.
77      *
78      * @param context A context
79      * @param appInfo The app that is changed
80      * @param user The user the app belongs to
81      *
82      * @return The minimum targetSDK of all apps sharing the uid of the app
83      */
getMinimumTargetSDK(@onNull Context context, @NonNull ApplicationInfo appInfo, @NonNull UserHandle user)84     private static int getMinimumTargetSDK(@NonNull Context context,
85             @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) {
86         PackageManager pm = context.getPackageManager();
87 
88         int minimumTargetSDK = appInfo.targetSdkVersion;
89 
90         String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
91         if (uidPkgs != null) {
92             for (String uidPkg : uidPkgs) {
93                 if (!uidPkg.equals(appInfo.packageName)) {
94                     ApplicationInfo uidPkgInfo;
95                     try {
96                         uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
97                     } catch (PackageManager.NameNotFoundException e) {
98                         continue;
99                     }
100 
101                     minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion);
102                 }
103             }
104         }
105 
106         return minimumTargetSDK;
107     }
108 
109     /**
110      * Get the policy for a soft restricted permission.
111      *
112      * @param context A context to use
113      * @param appInfo The application the permission belongs to.
114      * @param user The user the app belongs to.
115      * @param permission The name of the permission
116      *
117      * @return The policy for this permission
118      */
forPermission(@onNull Context context, @Nullable ApplicationInfo appInfo, @Nullable AndroidPackage pkg, @Nullable UserHandle user, @NonNull String permission)119     public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
120             @Nullable ApplicationInfo appInfo, @Nullable AndroidPackage pkg,
121             @Nullable UserHandle user, @NonNull String permission) {
122         switch (permission) {
123             // Storage uses a special app op to decide the mount state and supports soft restriction
124             // where the restricted state allows the permission but only for accessing the medial
125             // collections.
126             case READ_EXTERNAL_STORAGE: {
127                 final boolean isWhiteListed;
128                 boolean shouldApplyRestriction;
129                 final int targetSDK;
130                 final boolean hasLegacyExternalStorage;
131                 final boolean hasRequestedLegacyExternalStorage;
132                 final boolean hasRequestedPreserveLegacyExternalStorage;
133                 final boolean hasWriteMediaStorageGrantedForUid;
134                 final boolean isForcedScopedStorage;
135 
136                 if (appInfo != null) {
137                     PackageManager pm = context.getPackageManager();
138                     StorageManagerInternal smInternal =
139                             LocalServices.getService(StorageManagerInternal.class);
140                     int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
141                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
142                     hasLegacyExternalStorage = smInternal.hasLegacyExternalStorage(appInfo.uid);
143                     hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
144                             appInfo.uid, context);
145                     hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid(
146                             appInfo.uid, context);
147                     hasRequestedPreserveLegacyExternalStorage =
148                             pkg.hasPreserveLegacyExternalStorage();
149                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
150 
151                     shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
152                     isForcedScopedStorage = sForcedScopedStorageAppWhitelist
153                             .contains(appInfo.packageName);
154                 } else {
155                     isWhiteListed = false;
156                     shouldApplyRestriction = false;
157                     targetSDK = 0;
158                     hasLegacyExternalStorage = false;
159                     hasRequestedLegacyExternalStorage = false;
160                     hasRequestedPreserveLegacyExternalStorage = false;
161                     hasWriteMediaStorageGrantedForUid = false;
162                     isForcedScopedStorage = false;
163                 }
164 
165                 // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode
166                 // to prevent apps losing files in legacy storage, because we are holding the
167                 // package manager lock here. If we ever remove this policy that check should be
168                 // removed as well.
169                 return new SoftRestrictedPermissionPolicy() {
170                     @Override
171                     public boolean mayGrantPermission() {
172                         return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
173                     }
174                     @Override
175                     public int getExtraAppOpCode() {
176                         return OP_LEGACY_STORAGE;
177                     }
178                     @Override
179                     public boolean mayAllowExtraAppOp() {
180                         // The only way to get LEGACY_STORAGE (if you didn't already have it)
181                         // is that all of the following must be true:
182                         // 1. The flag shouldn't be restricted
183                         if (shouldApplyRestriction) {
184                             return false;
185                         }
186 
187                         // 2. The app shouldn't be in sForcedScopedStorageAppWhitelist
188                         if (isForcedScopedStorage) {
189                             return false;
190                         }
191 
192                         // 3. The app targetSDK should be less than R
193                         if (targetSDK >= Build.VERSION_CODES.R) {
194                             return false;
195                         }
196 
197                         // 4. The app has WRITE_MEDIA_STORAGE,
198                         //    OR the app already has legacy external storage
199                         //    OR the app requested legacy external storage
200                         return hasWriteMediaStorageGrantedForUid || hasLegacyExternalStorage
201                                 || hasRequestedLegacyExternalStorage;
202                     }
203                     @Override
204                     public boolean mayDenyExtraAppOpIfGranted() {
205                         // If you're an app targeting < R, you can keep the app op for
206                         // as long as you meet the conditions required to acquire it.
207                         if (targetSDK < Build.VERSION_CODES.R) {
208                             return !mayAllowExtraAppOp();
209                         }
210 
211                         // For an app targeting R, the only way to lose LEGACY_STORAGE if you
212                         // already had it is in one or more of the following conditions:
213                         // 1. The flag became restricted
214                         if (shouldApplyRestriction) {
215                             return true;
216                         }
217 
218                         // The package is now a part of the forced scoped storage whitelist
219                         if (isForcedScopedStorage) {
220                             return true;
221                         }
222 
223                         // The package doesn't request legacy storage to be preserved
224                         if (!hasRequestedPreserveLegacyExternalStorage) {
225                             return true;
226                         }
227 
228                         return false;
229                     }
230                 };
231             }
232             case WRITE_EXTERNAL_STORAGE: {
233                 final boolean isWhiteListed;
234                 final int targetSDK;
235 
236                 if (appInfo != null) {
237                     final int flags = context.getPackageManager().getPermissionFlags(permission,
238                             appInfo.packageName, user);
239                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
240                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
241                 } else {
242                     isWhiteListed = false;
243                     targetSDK = 0;
244                 }
245 
246                 return new SoftRestrictedPermissionPolicy() {
247                     @Override
248                     public boolean mayGrantPermission() {
249                         return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
250                     }
251                 };
252             }
253             default:
254                 return DUMMY_POLICY;
255         }
256     }
257 
258     private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) {
259         PackageManager packageManager = context.getPackageManager();
260         String[] packageNames = packageManager.getPackagesForUid(uid);
261         if (packageNames == null) {
262             return false;
263         }
264         UserHandle user = UserHandle.getUserHandleForUid(uid);
265         for (String packageName : packageNames) {
266             ApplicationInfo applicationInfo;
267             try {
268                 applicationInfo = packageManager.getApplicationInfoAsUser(packageName, 0, user);
269             } catch (PackageManager.NameNotFoundException e) {
270                 continue;
271             }
272             if (applicationInfo.hasRequestedLegacyExternalStorage()) {
273                 return true;
274             }
275         }
276         return false;
277     }
278 
279     private static boolean hasWriteMediaStorageGrantedForUid(int uid, @NonNull Context context) {
280         PackageManager packageManager = context.getPackageManager();
281         String[] packageNames = packageManager.getPackagesForUid(uid);
282         if (packageNames == null) {
283             return false;
284         }
285 
286         for (String packageName : packageNames) {
287             if (packageManager.checkPermission(WRITE_MEDIA_STORAGE, packageName)
288                     == PERMISSION_GRANTED) {
289                 return true;
290             }
291         }
292         return false;
293     }
294 
295     private static String[] getForcedScopedStorageAppWhitelist() {
296         final String rawList = DeviceConfig.getString(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
297                 StorageManager.PROP_FORCED_SCOPED_STORAGE_WHITELIST, /*defaultValue*/"");
298         if (rawList == null || rawList.equals("")) {
299             return new String[0];
300         }
301         return rawList.split(",");
302     }
303 
304     /**
305      * @return If the permission can be granted
306      */
307     public abstract boolean mayGrantPermission();
308 
309     /**
310      * @return An app op to be changed based on the state of the permission or
311      * {@link AppOpsManager#OP_NONE} if not app-op should be set.
312      */
313     public int getExtraAppOpCode() {
314         return OP_NONE;
315     }
316 
317     /**
318      * @return Whether the {@link #getExtraAppOpCode() app op} may be granted.
319      */
320     public boolean mayAllowExtraAppOp() {
321         return false;
322     }
323 
324     /**
325      * @return Whether the {@link #getExtraAppOpCode() app op} may be denied if was granted.
326      */
327     public boolean mayDenyExtraAppOpIfGranted() {
328         return false;
329     }
330 }
331