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