blob: e05511681ba84a9ea8dfb235c375e382fa728255 [file] [log] [blame]
Winsond9d17362019-10-02 12:41:29 -07001/*
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
17package com.android.server.om;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.om.OverlayInfo;
22import android.content.om.OverlayableInfo;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageInfo;
25import android.net.Uri;
26import android.os.Process;
27import android.os.RemoteException;
28import android.text.TextUtils;
29
30import com.android.internal.util.ArrayUtils;
31import com.android.internal.util.CollectionUtils;
32import com.android.server.SystemConfig;
33
34import java.io.IOException;
35import java.util.List;
36import java.util.Map;
37
38/**
39 * Performs verification that a calling UID can act on a target package's overlayable.
40 *
41 * @hide
42 */
43public class OverlayActorEnforcer {
44
45 private final VerifyCallback mVerifyCallback;
46
47 public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) {
48 mVerifyCallback = verifyCallback;
49 }
50
51 void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName,
52 int callingUid, int userId) throws SecurityException {
53 ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId);
54 if (actorState == ActorState.ALLOWED) {
55 return;
56 }
57
58 String targetOverlayableName = overlayInfo.targetOverlayableName;
59 throw new SecurityException("UID" + callingUid + " is not allowed to call "
60 + methodName + " for "
61 + (TextUtils.isEmpty(targetOverlayableName) ? "" : (targetOverlayableName + " in "))
62 + overlayInfo.targetPackageName + " because " + actorState
63 );
64 }
65
66 /**
67 * An actor is valid if any of the following is true:
68 * - is {@link Process#ROOT_UID}, {@link Process#SYSTEM_UID}
69 * - is the target overlay package
70 * - has the CHANGE_OVERLAY_PACKAGES permission and an actor is not defined
71 * - is the same the as the package defined in {@link SystemConfig#getNamedActors()} for a given
72 * namespace and actor name
73 *
74 * @return true if the actor is allowed to act on the target overlayInfo
75 */
76 private ActorState isAllowedActor(String methodName, OverlayInfo overlayInfo,
77 int callingUid, int userId) {
78 switch (callingUid) {
79 case Process.ROOT_UID:
80 case Process.SYSTEM_UID:
81 return ActorState.ALLOWED;
82 }
83
84 String[] callingPackageNames = mVerifyCallback.getPackagesForUid(callingUid);
85 if (ArrayUtils.isEmpty(callingPackageNames)) {
86 return ActorState.NO_PACKAGES_FOR_UID;
87 }
88
89 // A target is always an allowed actor for itself
90 String targetPackageName = overlayInfo.targetPackageName;
91 if (ArrayUtils.contains(callingPackageNames, targetPackageName)) {
92 return ActorState.ALLOWED;
93 }
94
95 String targetOverlayableName = overlayInfo.targetOverlayableName;
96
97 if (TextUtils.isEmpty(targetOverlayableName)) {
98 try {
99 if (mVerifyCallback.doesTargetDefineOverlayable(targetPackageName, userId)) {
100 return ActorState.MISSING_TARGET_OVERLAYABLE_NAME;
101 } else {
102 // If there's no overlayable defined, fallback to the legacy permission check
103 try {
104 mVerifyCallback.enforcePermission(
105 android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
106
107 // If the previous method didn't throw, check passed
108 return ActorState.ALLOWED;
109 } catch (SecurityException e) {
110 return ActorState.MISSING_LEGACY_PERMISSION;
111 }
112 }
113 } catch (RemoteException | IOException e) {
114 return ActorState.ERROR_READING_OVERLAYABLE;
115 }
116 }
117
118 OverlayableInfo targetOverlayable;
119 try {
120 targetOverlayable = mVerifyCallback.getOverlayableForTarget(targetPackageName,
121 targetOverlayableName, userId);
122 } catch (IOException e) {
123 return ActorState.UNABLE_TO_GET_TARGET;
124 }
125
126 if (targetOverlayable == null) {
127 return ActorState.MISSING_OVERLAYABLE;
128 }
129
130 String actor = targetOverlayable.actor;
131 if (TextUtils.isEmpty(actor)) {
132 // If there's no actor defined, fallback to the legacy permission check
133 try {
134 mVerifyCallback.enforcePermission(
135 android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
136
137 // If the previous method didn't throw, check passed
138 return ActorState.ALLOWED;
139 } catch (SecurityException e) {
140 return ActorState.MISSING_LEGACY_PERMISSION;
141 }
142 }
143
144 Map<String, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
145 if (namedActors.isEmpty()) {
146 return ActorState.NO_NAMED_ACTORS;
147 }
148
149 Uri actorUri = Uri.parse(actor);
150
151 String actorScheme = actorUri.getScheme();
152 List<String> actorPathSegments = actorUri.getPathSegments();
153 if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
154 return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME;
155 }
156
157 String actorNamespace = actorUri.getAuthority();
158 Map<String, String> namespace = namedActors.get(actorNamespace);
159 if (namespace == null) {
160 return ActorState.MISSING_NAMESPACE;
161 }
162
163 String actorName = actorPathSegments.get(0);
164 String packageName = namespace.get(actorName);
165 if (TextUtils.isEmpty(packageName)) {
166 return ActorState.MISSING_ACTOR_NAME;
167 }
168
169 PackageInfo packageInfo = mVerifyCallback.getPackageInfo(packageName, userId);
170 if (packageInfo == null) {
171 return ActorState.MISSING_APP_INFO;
172 }
173
174 ApplicationInfo appInfo = packageInfo.applicationInfo;
175 if (appInfo == null) {
176 return ActorState.MISSING_APP_INFO;
177 }
178
179 // Currently only pre-installed apps can be actors
180 if (!appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
181 return ActorState.ACTOR_NOT_PREINSTALLED;
182 }
183
184 if (ArrayUtils.contains(callingPackageNames, packageName)) {
185 return ActorState.ALLOWED;
186 }
187
188 return ActorState.INVALID_ACTOR;
189 }
190
191 /**
192 * For easier logging/debugging, a set of all possible failure/success states when running
193 * enforcement.
194 */
195 private enum ActorState {
196 ALLOWED,
197 INVALID_ACTOR,
198 MISSING_NAMESPACE,
199 MISSING_PACKAGE,
200 MISSING_APP_INFO,
201 ACTOR_NOT_PREINSTALLED,
202 NO_PACKAGES_FOR_UID,
203 MISSING_ACTOR_NAME,
204 ERROR_READING_OVERLAYABLE,
205 MISSING_TARGET_OVERLAYABLE_NAME,
206 MISSING_OVERLAYABLE,
207 INVALID_OVERLAYABLE_ACTOR_NAME,
208 NO_NAMED_ACTORS,
209 UNABLE_TO_GET_TARGET,
210 MISSING_LEGACY_PERMISSION
211 }
212
213 /**
214 * Delegate to the system for querying information about packages.
215 */
216 public interface VerifyCallback {
217
218 /**
219 * Read from the APK and AndroidManifest of a package to return the overlayable defined for
220 * a given name.
221 *
222 * @throws IOException if the target can't be read
223 */
224 @Nullable
225 OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
226 @Nullable String targetOverlayableName, int userId)
227 throws IOException;
228
229 /**
230 * @see android.content.pm.PackageManager#getPackagesForUid(int)
231 */
232 @Nullable
233 String[] getPackagesForUid(int uid);
234
235 /**
236 * @param userId user to filter package visibility by
237 * @see android.content.pm.PackageManager#getPackageInfo(String, int)
238 */
239 @Nullable
240 PackageInfo getPackageInfo(@NonNull String packageName, int userId);
241
242 /**
243 * @return map of system pre-defined, uniquely named actors; keys are namespace,
244 * value maps actor name to package name
245 */
246 @NonNull
247 Map<String, ? extends Map<String, String>> getNamedActors();
248
249 /**
250 * @return true if the target package has declared an overlayable
251 */
252 boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
253 throws RemoteException, IOException;
254
255 /**
256 * @throws SecurityException containing message if the caller doesn't have the given
257 * permission
258 */
259 void enforcePermission(String permission, String message) throws SecurityException;
260 }
261}