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