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.component.ComponentParseUtils.flag;
20 import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.IntentFilter;
25 import android.content.pm.PathPermission;
26 import android.content.pm.ProviderInfo;
27 import android.content.pm.parsing.result.ParseInput;
28 import android.content.pm.parsing.result.ParseResult;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.content.res.XmlResourceParser;
32 import android.os.Build;
33 import android.os.PatternMatcher;
34 import android.util.Slog;
35 
36 import com.android.internal.R;
37 import com.android.server.pm.pkg.parsing.ParsingPackage;
38 import com.android.server.pm.pkg.parsing.ParsingUtils;
39 
40 import org.xmlpull.v1.XmlPullParser;
41 import org.xmlpull.v1.XmlPullParserException;
42 
43 import java.io.IOException;
44 import java.util.Objects;
45 
46 /** @hide */
47 public class ParsedProviderUtils {
48 
49     private static final String TAG = ParsingUtils.TAG;
50 
51     @NonNull
parseProvider(String[] separateProcesses, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)52     public static ParseResult<ParsedProvider> parseProvider(String[] separateProcesses,
53             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
54             boolean useRoundIcon, @Nullable String defaultSplitName, @NonNull ParseInput input)
55             throws IOException, XmlPullParserException {
56         String authority;
57         boolean visibleToEphemeral;
58 
59         final int targetSdkVersion = pkg.getTargetSdkVersion();
60         final String packageName = pkg.getPackageName();
61         final ParsedProviderImpl provider = new ParsedProviderImpl();
62         final String tag = parser.getName();
63 
64         TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestProvider);
65         try {
66             ParseResult<ParsedProviderImpl> result =
67                     ParsedMainComponentUtils.parseMainComponent(provider, tag, separateProcesses,
68                             pkg, sa, flags, useRoundIcon, defaultSplitName, input,
69                             R.styleable.AndroidManifestProvider_banner,
70                             R.styleable.AndroidManifestProvider_description,
71                             R.styleable.AndroidManifestProvider_directBootAware,
72                             R.styleable.AndroidManifestProvider_enabled,
73                             R.styleable.AndroidManifestProvider_icon,
74                             R.styleable.AndroidManifestProvider_label,
75                             R.styleable.AndroidManifestProvider_logo,
76                             R.styleable.AndroidManifestProvider_name,
77                             R.styleable.AndroidManifestProvider_process,
78                             R.styleable.AndroidManifestProvider_roundIcon,
79                             R.styleable.AndroidManifestProvider_splitName,
80                             R.styleable.AndroidManifestProvider_attributionTags);
81             if (result.isError()) {
82                 return input.error(result);
83             }
84 
85             authority = sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_authorities, 0);
86 
87             // For compatibility, applications targeting API level 16 or lower
88             // should have their content providers exported by default, unless they
89             // specify otherwise.
90             provider.setSyncable(sa.getBoolean(
91                     R.styleable.AndroidManifestProvider_syncable, false))
92                     .setExported(sa.getBoolean(R.styleable.AndroidManifestProvider_exported,
93                             targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1));
94 
95             String permission = sa.getNonConfigurationString(
96                     R.styleable.AndroidManifestProvider_permission, 0);
97             String readPermission = sa.getNonConfigurationString(
98                     R.styleable.AndroidManifestProvider_readPermission, 0);
99             if (readPermission == null) {
100                 readPermission = permission;
101             }
102             if (readPermission == null) {
103                 provider.setReadPermission(pkg.getPermission());
104             } else {
105                 provider.setReadPermission(readPermission);
106             }
107             String writePermission = sa.getNonConfigurationString(
108                     R.styleable.AndroidManifestProvider_writePermission, 0);
109             if (writePermission == null) {
110                 writePermission = permission;
111             }
112             if (writePermission == null) {
113                 provider.setWritePermission(pkg.getPermission());
114             } else {
115                 provider.setWritePermission(writePermission);
116             }
117 
118             provider.setGrantUriPermissions(
119                     sa.getBoolean(R.styleable.AndroidManifestProvider_grantUriPermissions, false))
120                     .setForceUriPermissions(
121                             sa.getBoolean(R.styleable.AndroidManifestProvider_forceUriPermissions,
122                                     false))
123                     .setMultiProcess(
124                             sa.getBoolean(R.styleable.AndroidManifestProvider_multiprocess, false))
125                     .setInitOrder(sa.getInt(R.styleable.AndroidManifestProvider_initOrder, 0))
126                     .setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER,
127                             R.styleable.AndroidManifestProvider_singleUser, sa));
128 
129             visibleToEphemeral = sa.getBoolean(
130                     R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
131             if (visibleToEphemeral) {
132                 provider.setFlags(provider.getFlags() | ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP);
133                 pkg.setVisibleToInstantApps(true);
134             }
135         } finally {
136             sa.recycle();
137         }
138 
139         if (pkg.isSaveStateDisallowed()) {
140             // A heavy-weight application can not have providers in its main process
141             if (Objects.equals(provider.getProcessName(), packageName)) {
142                 return input.error("Heavy-weight applications can not have providers"
143                         + " in main process");
144             }
145         }
146 
147         if (authority == null) {
148             return input.error("<provider> does not include authorities attribute");
149         }
150         if (authority.length() <= 0) {
151             return input.error("<provider> has empty authorities attribute");
152         }
153         provider.setAuthority(authority);
154 
155         return parseProviderTags(pkg, tag, res, parser, visibleToEphemeral, provider, input);
156     }
157 
158     @NonNull
parseProviderTags(ParsingPackage pkg, String tag, Resources res, XmlResourceParser parser, boolean visibleToEphemeral, ParsedProviderImpl provider, ParseInput input)159     private static ParseResult<ParsedProvider> parseProviderTags(ParsingPackage pkg, String tag,
160             Resources res, XmlResourceParser parser, boolean visibleToEphemeral,
161             ParsedProviderImpl provider, ParseInput input)
162             throws XmlPullParserException, IOException {
163         final int depth = parser.getDepth();
164         int type;
165         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
166                 && (type != XmlPullParser.END_TAG
167                 || parser.getDepth() > depth)) {
168             if (type != XmlPullParser.START_TAG) {
169                 continue;
170             }
171 
172             String name = parser.getName();
173             final ParseResult result;
174             switch (name) {
175                 case "intent-filter":
176                     ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
177                             .parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral,
178                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
179                                     false /*allowImplicitEphemeralVisibility*/,
180                                     false /*failOnNoActions*/, input);
181                     result = intentResult;
182                     if (intentResult.isSuccess()) {
183                         ParsedIntentInfoImpl intent = intentResult.getResult();
184                         IntentFilter intentFilter = intent.getIntentFilter();
185                         provider.setOrder(Math.max(intentFilter.getOrder(), provider.getOrder()));
186                         provider.addIntent(intent);
187                     }
188                     break;
189                 case "meta-data":
190                     result = ParsedComponentUtils.addMetaData(provider, pkg, res, parser, input);
191                     break;
192                 case "property":
193                     result = ParsedComponentUtils.addProperty(provider, pkg, res, parser, input);
194                     break;
195                 case "grant-uri-permission": {
196                     result = parseGrantUriPermission(provider, pkg, res, parser, input);
197                     break;
198                 }
199                 case "path-permission": {
200                     result = parsePathPermission(provider, pkg, res, parser, input);
201                     break;
202                 }
203                 default:
204                     result = ParsingUtils.unknownTag(tag, pkg, parser, input);
205                     break;
206             }
207 
208             if (result.isError()) {
209                 return input.error(result);
210             }
211         }
212 
213         return input.success(provider);
214     }
215 
216     @NonNull
parseGrantUriPermission(ParsedProviderImpl provider, ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input)217     private static ParseResult<ParsedProvider> parseGrantUriPermission(ParsedProviderImpl provider,
218             ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
219         TypedArray sa = resources.obtainAttributes(parser,
220                 R.styleable.AndroidManifestGrantUriPermission);
221         try {
222             String name = parser.getName();
223             // Pattern has priority over pre/suffix over literal path
224             PatternMatcher pa = null;
225             String str = sa.getNonConfigurationString(
226                     R.styleable.AndroidManifestGrantUriPermission_pathAdvancedPattern, 0);
227             if (str != null) {
228                 pa = new PatternMatcher(str, PatternMatcher.PATTERN_ADVANCED_GLOB);
229             } else {
230                 str = sa.getNonConfigurationString(
231                         R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0);
232                 if (str != null) {
233                     pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
234                 } else {
235                     str = sa.getNonConfigurationString(
236                             R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0);
237                     if (str != null) {
238                         pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
239                     } else {
240                         str = sa.getNonConfigurationString(
241                                 R.styleable.AndroidManifestGrantUriPermission_pathSuffix, 0);
242                         if (str != null) {
243                             pa = new PatternMatcher(str, PatternMatcher.PATTERN_SUFFIX);
244                         } else {
245                             str = sa.getNonConfigurationString(
246                                     R.styleable.AndroidManifestGrantUriPermission_path, 0);
247                             if (str != null) {
248                                 pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
249                             }
250                         }
251                     }
252                 }
253             }
254 
255             if (pa != null) {
256                 provider.addUriPermissionPattern(pa);
257                 provider.setGrantUriPermissions(true);
258             } else {
259                 if (RIGID_PARSER) {
260                     return input.error("No path, pathPrefix, or pathPattern for <path-permission>");
261                 }
262 
263                 Slog.w(TAG, "Unknown element under <path-permission>: " + name + " at "
264                         + pkg.getBaseApkPath() + " " + parser.getPositionDescription());
265             }
266 
267             return input.success(provider);
268         } finally {
269             sa.recycle();
270         }
271     }
272 
273     @NonNull
parsePathPermission(ParsedProviderImpl provider, ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input)274     private static ParseResult<ParsedProvider> parsePathPermission(ParsedProviderImpl provider,
275             ParsingPackage pkg, Resources resources, XmlResourceParser parser, ParseInput input) {
276         TypedArray sa = resources.obtainAttributes(parser,
277                 R.styleable.AndroidManifestPathPermission);
278         try {
279             String name = parser.getName();
280 
281             String permission = sa.getNonConfigurationString(
282                     R.styleable.AndroidManifestPathPermission_permission, 0);
283             String readPermission = sa.getNonConfigurationString(
284                     R.styleable.AndroidManifestPathPermission_readPermission, 0);
285             if (readPermission == null) {
286                 readPermission = permission;
287             }
288             String writePermission = sa.getNonConfigurationString(
289                     R.styleable.AndroidManifestPathPermission_writePermission, 0);
290             if (writePermission == null) {
291                 writePermission = permission;
292             }
293 
294             boolean havePerm = false;
295             if (readPermission != null) {
296                 readPermission = readPermission.intern();
297                 havePerm = true;
298             }
299             if (writePermission != null) {
300                 writePermission = writePermission.intern();
301                 havePerm = true;
302             }
303 
304             if (!havePerm) {
305                 if (RIGID_PARSER) {
306                     return input.error(
307                             "No readPermission or writePermission for <path-permission>");
308                 }
309                 Slog.w(TAG, "No readPermission or writePermission for <path-permission>: "
310                         + name + " at " + pkg.getBaseApkPath() + " "
311                         + parser.getPositionDescription());
312                 return input.success(provider);
313             }
314 
315             // Advanced has priority over simply over prefix over literal
316             PathPermission pa = null;
317             String path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathAdvancedPattern, 0);
318             if (path != null) {
319                 pa = new PathPermission(path, PatternMatcher.PATTERN_ADVANCED_GLOB, readPermission,
320                         writePermission);
321             } else {
322                 path = sa.getNonConfigurationString(R.styleable.AndroidManifestPathPermission_pathPattern, 0);
323                 if (path != null) {
324                     pa = new PathPermission(path, PatternMatcher.PATTERN_SIMPLE_GLOB,
325                             readPermission, writePermission);
326                 } else {
327                     path = sa.getNonConfigurationString(
328                             R.styleable.AndroidManifestPathPermission_pathPrefix, 0);
329                     if (path != null) {
330                         pa = new PathPermission(path, PatternMatcher.PATTERN_PREFIX, readPermission,
331                                 writePermission);
332                     } else {
333                         path = sa.getNonConfigurationString(
334                                 R.styleable.AndroidManifestPathPermission_pathSuffix, 0);
335                         if (path != null) {
336                             pa = new PathPermission(path, PatternMatcher.PATTERN_SUFFIX,
337                                     readPermission, writePermission);
338                         } else {
339                             path = sa.getNonConfigurationString(
340                                     R.styleable.AndroidManifestPathPermission_path, 0);
341                             if (path != null) {
342                                 pa = new PathPermission(path, PatternMatcher.PATTERN_LITERAL,
343                                         readPermission, writePermission);
344                             }
345                         }
346                     }
347                 }
348             }
349 
350             if (pa != null) {
351                 provider.addPathPermission(pa);
352             } else {
353                 if (RIGID_PARSER) {
354                     return input.error(
355                             "No path, pathPrefix, or pathPattern for <path-permission>");
356                 }
357 
358                 Slog.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>: "
359                         + name + " at " + pkg.getBaseApkPath()
360                         + " "
361                         + parser.getPositionDescription());
362             }
363 
364             return input.success(provider);
365         } finally {
366             sa.recycle();
367         }
368     }
369 }
370