• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.internal.pm.pkg.component;
18 
19 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
20 import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
21 
22 import android.annotation.NonNull;
23 import android.content.pm.PermissionInfo;
24 import android.content.pm.parsing.result.ParseInput;
25 import android.content.pm.parsing.result.ParseResult;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.content.res.XmlResourceParser;
29 import android.os.Build;
30 import android.permission.flags.Flags;
31 import android.util.ArrayMap;
32 import android.util.EventLog;
33 import android.util.Slog;
34 
35 import com.android.internal.R;
36 import com.android.internal.pm.pkg.parsing.ParsingPackage;
37 import com.android.internal.pm.pkg.parsing.ParsingUtils;
38 
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.IOException;
42 import java.util.List;
43 import java.util.Objects;
44 
45 /**
46  * @hide
47  */
48 public class ParsedPermissionUtils {
49 
50     private static final String TAG = ParsingUtils.TAG;
51 
52     @NonNull
parsePermission(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input, int flags)53     public static ParseResult<ParsedPermission> parsePermission(ParsingPackage pkg, Resources res,
54             XmlResourceParser parser, boolean useRoundIcon, ParseInput input, int flags)
55             throws IOException, XmlPullParserException {
56         String packageName = pkg.getPackageName();
57         ParsedPermissionImpl permission = new ParsedPermissionImpl();
58         String tag = "<" + parser.getName() + ">";
59         ParseResult<ParsedPermissionImpl> result;
60 
61         try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission)) {
62             result = ParsedComponentUtils.parseComponent(
63                     permission, tag, pkg, sa, useRoundIcon, input,
64                     R.styleable.AndroidManifestPermission_banner,
65                     R.styleable.AndroidManifestPermission_description,
66                     R.styleable.AndroidManifestPermission_icon,
67                     R.styleable.AndroidManifestPermission_label,
68                     R.styleable.AndroidManifestPermission_logo,
69                     R.styleable.AndroidManifestPermission_name,
70                     R.styleable.AndroidManifestPermission_roundIcon);
71             if (result.isError()) {
72                 return input.error(result);
73             }
74 
75             int maxSdkVersion = sa.getInt(R.styleable.AndroidManifestPermission_maxSdkVersion, -1);
76             if ((maxSdkVersion != -1) && (maxSdkVersion < Build.VERSION.SDK_INT)) {
77                 return input.success(null);
78             }
79 
80             if (sa.hasValue(
81                     R.styleable.AndroidManifestPermission_backgroundPermission)) {
82                 final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0;
83                 final boolean canUseBackgroundPermissionAttr =
84                     "android".equals(packageName) ||
85                         (Flags.replaceBodySensorPermissionEnabled() && isApkInApex);
86                 if (canUseBackgroundPermissionAttr) {
87                     permission.setBackgroundPermission(sa.getNonResourceString(
88                         R.styleable.AndroidManifestPermission_backgroundPermission));
89                 } else {
90                     String allowedPackages = "'android'"
91                         + (Flags.replaceBodySensorPermissionEnabled() ? " and APK_IN_APEX" : "");
92                     Slog.w(TAG, packageName + " defines a background permission. Only the "
93                         + allowedPackages + " packages can do that.");
94                 }
95             }
96 
97             // Note: don't allow this value to be a reference to a resource
98             // that may change.
99             permission.setGroup(sa.getNonResourceString(
100                     R.styleable.AndroidManifestPermission_permissionGroup))
101                     .setRequestRes(sa.getResourceId(
102                             R.styleable.AndroidManifestPermission_request, 0))
103                     .setProtectionLevel(sa.getInt(
104                             R.styleable.AndroidManifestPermission_protectionLevel,
105                             PermissionInfo.PROTECTION_NORMAL))
106                     .setFlags(sa.getInt(
107                             R.styleable.AndroidManifestPermission_permissionFlags, 0));
108 
109             final int knownCertsResource = sa.getResourceId(
110                     R.styleable.AndroidManifestPermission_knownCerts, 0);
111             if (knownCertsResource != 0) {
112                 // The knownCerts attribute supports both a string array resource as well as a
113                 // string resource for the case where the permission should only be granted to a
114                 // single known signer.
115                 final String resourceType = res.getResourceTypeName(knownCertsResource);
116                 if (resourceType.equals("array")) {
117                     final String[] knownCerts = res.getStringArray(knownCertsResource);
118                     if (knownCerts != null) {
119                         permission.setKnownCerts(knownCerts);
120                     }
121                 } else {
122                     final String knownCert = res.getString(knownCertsResource);
123                     if (knownCert != null) {
124                         permission.setKnownCert(knownCert);
125                     }
126                 }
127                 if (permission.getKnownCerts().isEmpty()) {
128                     Slog.w(TAG, packageName + " defines a knownSigner permission but"
129                             + " the provided knownCerts resource is null");
130                 }
131             } else {
132                 // If the knownCerts resource ID is null check if the app specified a string
133                 // value for the attribute representing a single trusted signer.
134                 final String knownCert = sa.getString(
135                         R.styleable.AndroidManifestPermission_knownCerts);
136                 if (knownCert != null) {
137                     permission.setKnownCert(knownCert);
138                 }
139             }
140 
141             // For now only platform runtime permissions can be restricted
142             if (!isRuntime(permission) || !"android".equals(permission.getPackageName())) {
143                 permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_HARD_RESTRICTED);
144                 permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_SOFT_RESTRICTED);
145             } else {
146                 // The platform does not get to specify conflicting permissions
147                 if ((permission.getFlags() & PermissionInfo.FLAG_HARD_RESTRICTED) != 0
148                         && (permission.getFlags() & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) {
149                     throw new IllegalStateException("Permission cannot be both soft and hard"
150                             + " restricted: " + permission.getName());
151                 }
152             }
153         }
154 
155         permission.setProtectionLevel(
156                 PermissionInfo.fixProtectionLevel(permission.getProtectionLevel()));
157 
158         final int otherProtectionFlags = getProtectionFlags(permission)
159                 & ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT
160                 | PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY);
161         if (otherProtectionFlags != 0
162                 && getProtection(permission) != PermissionInfo.PROTECTION_SIGNATURE
163                 && getProtection(permission) != PermissionInfo.PROTECTION_INTERNAL) {
164             return input.error("<permission> protectionLevel specifies a non-instant, non-appop,"
165                     + " non-runtimeOnly flag but is not based on signature or internal type");
166         }
167 
168         result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
169         if (result.isError()) {
170             return input.error(result);
171         }
172 
173         return input.success(result.getResult());
174     }
175 
176     @NonNull
parsePermissionTree(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)177     public static ParseResult<ParsedPermission> parsePermissionTree(ParsingPackage pkg, Resources res,
178             XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
179             throws IOException, XmlPullParserException {
180         ParsedPermissionImpl permission = new ParsedPermissionImpl();
181         String tag = "<" + parser.getName() + ">";
182         ParseResult<ParsedPermissionImpl> result;
183 
184         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionTree);
185         try {
186             result = ParsedComponentUtils.parseComponent(
187                     permission, tag, pkg, sa, useRoundIcon, input,
188                     R.styleable.AndroidManifestPermissionTree_banner,
189                     NOT_SET /*descriptionAttr*/,
190                     R.styleable.AndroidManifestPermissionTree_icon,
191                     R.styleable.AndroidManifestPermissionTree_label,
192                     R.styleable.AndroidManifestPermissionTree_logo,
193                     R.styleable.AndroidManifestPermissionTree_name,
194                     R.styleable.AndroidManifestPermissionTree_roundIcon);
195             if (result.isError()) {
196                 return input.error(result);
197             }
198         } finally {
199             sa.recycle();
200         }
201 
202         int index = permission.getName().indexOf('.');
203         if (index > 0) {
204             index = permission.getName().indexOf('.', index + 1);
205         }
206         if (index < 0) {
207             return input.error("<permission-tree> name has less than three segments: "
208                     + permission.getName());
209         }
210 
211         permission.setProtectionLevel(PermissionInfo.PROTECTION_NORMAL)
212                 .setTree(true);
213 
214         result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
215         if (result.isError()) {
216             return input.error(result);
217         }
218 
219         return input.success(result.getResult());
220     }
221 
222     @NonNull
parsePermissionGroup(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)223     public static ParseResult<ParsedPermissionGroup> parsePermissionGroup(ParsingPackage pkg,
224             Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
225             throws IOException, XmlPullParserException {
226         ParsedPermissionGroupImpl
227                 permissionGroup = new ParsedPermissionGroupImpl();
228         String tag = "<" + parser.getName() + ">";
229 
230         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionGroup);
231         try {
232             ParseResult<ParsedPermissionGroupImpl> result = ParsedComponentUtils.parseComponent(
233                     permissionGroup, tag, pkg, sa, useRoundIcon, input,
234                     R.styleable.AndroidManifestPermissionGroup_banner,
235                     R.styleable.AndroidManifestPermissionGroup_description,
236                     R.styleable.AndroidManifestPermissionGroup_icon,
237                     R.styleable.AndroidManifestPermissionGroup_label,
238                     R.styleable.AndroidManifestPermissionGroup_logo,
239                     R.styleable.AndroidManifestPermissionGroup_name,
240                     R.styleable.AndroidManifestPermissionGroup_roundIcon);
241             if (result.isError()) {
242                 return input.error(result);
243             }
244 
245             // @formatter:off
246             permissionGroup.setRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
247                     .setBackgroundRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
248                     .setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
249                     .setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0))
250                     .setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0))
251                     .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0));
252             // @formatter:on
253         } finally {
254             sa.recycle();
255         }
256 
257         ParseResult<ParsedPermissionGroupImpl> result = ComponentParseUtils.parseAllMetaData(pkg,
258                 res, parser, tag, permissionGroup, input);
259         if (result.isError()) {
260             return input.error(result);
261         }
262 
263         return input.success(result.getResult());
264     }
265 
isRuntime(@onNull ParsedPermission permission)266     public static boolean isRuntime(@NonNull ParsedPermission permission) {
267         return getProtection(permission) == PermissionInfo.PROTECTION_DANGEROUS;
268     }
269 
isAppOp(@onNull ParsedPermission permission)270     public static boolean isAppOp(@NonNull ParsedPermission permission) {
271         return (permission.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
272     }
273 
274     @PermissionInfo.Protection
getProtection(@onNull ParsedPermission permission)275     public static int getProtection(@NonNull ParsedPermission permission) {
276         return permission.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE;
277     }
278 
getProtectionFlags(@onNull ParsedPermission permission)279     public static int getProtectionFlags(@NonNull ParsedPermission permission) {
280         return permission.getProtectionLevel() & ~PermissionInfo.PROTECTION_MASK_BASE;
281     }
282 
calculateFootprint(@onNull ParsedPermission permission)283     public static int calculateFootprint(@NonNull ParsedPermission permission) {
284         int size = permission.getName().length();
285         CharSequence nonLocalizedLabel = permission.getNonLocalizedLabel();
286         if (nonLocalizedLabel != null) {
287             size += nonLocalizedLabel.length();
288         }
289         return size;
290     }
291 
292     /**
293      * Determines if a duplicate permission is malformed .i.e. defines different protection level
294      * or group.
295      */
isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2)296     private static boolean isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2) {
297         // Since a permission tree is also added as a permission with normal protection
298         // level, we need to skip if the parsedPermission is a permission tree.
299         if (p1 == null || p2 == null || p1.isTree() || p2.isTree()) {
300             return false;
301         }
302 
303         if (p1.getProtectionLevel() != p2.getProtectionLevel()) {
304             return true;
305         }
306         if (!Objects.equals(p1.getGroup(), p2.getGroup())) {
307             return true;
308         }
309 
310         return false;
311     }
312 
313     /**
314      * @return {@code true} if the package declares malformed duplicate permissions.
315      */
declareDuplicatePermission(@onNull ParsingPackage pkg)316     public static boolean declareDuplicatePermission(@NonNull ParsingPackage pkg) {
317         final List<ParsedPermission> permissions = pkg.getPermissions();
318         final int size = permissions.size();
319         if (size > 0) {
320             final ArrayMap<String, ParsedPermission> checkDuplicatePerm = new ArrayMap<>(size);
321             for (int i = 0; i < size; i++) {
322                 final ParsedPermission parsedPermission = permissions.get(i);
323                 final String name = parsedPermission.getName();
324                 final ParsedPermission perm = checkDuplicatePerm.get(name);
325                 if (isMalformedDuplicate(parsedPermission, perm)) {
326                     // Fix for b/213323615
327                     EventLog.writeEvent(0x534e4554, "213323615");
328                     return true;
329                 }
330                 checkDuplicatePerm.put(name, parsedPermission);
331             }
332         }
333         return false;
334     }
335 }
336