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.om;
18 
19 import android.annotation.NonNull;
20 import android.content.om.OverlayInfo;
21 import android.content.om.OverlayableInfo;
22 import android.net.Uri;
23 import android.os.Process;
24 import android.text.TextUtils;
25 import android.util.Pair;
26 import android.util.Slog;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.ArrayUtils;
30 import com.android.internal.util.CollectionUtils;
31 
32 import java.io.IOException;
33 import java.util.List;
34 import java.util.Map;
35 
36 /**
37  * Performs verification that a calling UID can act on a target package's overlayable.
38  *
39  * Actors requirements are specified in {@link android.content.om.OverlayManager}.
40  *
41  * @hide
42  */
43 public class OverlayActorEnforcer {
44 
45     private final PackageManagerHelper mPackageManager;
46 
47     /**
48      * @return nullable actor result with {@link ActorState} failure status
49      */
getPackageNameForActor(@onNull String actorUriString, @NonNull Map<String, Map<String, String>> namedActors)50     static Pair<String, ActorState> getPackageNameForActor(@NonNull String actorUriString,
51             @NonNull Map<String, Map<String, String>> namedActors) {
52         Uri actorUri = Uri.parse(actorUriString);
53 
54         String actorScheme = actorUri.getScheme();
55         List<String> actorPathSegments = actorUri.getPathSegments();
56         if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
57             return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME);
58         }
59 
60         if (namedActors.isEmpty()) {
61             return Pair.create(null, ActorState.NO_NAMED_ACTORS);
62         }
63 
64         String actorNamespace = actorUri.getAuthority();
65         Map<String, String> namespace = namedActors.get(actorNamespace);
66         if (ArrayUtils.isEmpty(namespace)) {
67             return Pair.create(null, ActorState.MISSING_NAMESPACE);
68         }
69 
70         String actorName = actorPathSegments.get(0);
71         String packageName = namespace.get(actorName);
72         if (TextUtils.isEmpty(packageName)) {
73             return Pair.create(null, ActorState.MISSING_ACTOR_NAME);
74         }
75 
76         return Pair.create(packageName, ActorState.ALLOWED);
77     }
78 
OverlayActorEnforcer(@onNull PackageManagerHelper packageManager)79     public OverlayActorEnforcer(@NonNull PackageManagerHelper packageManager) {
80         mPackageManager = packageManager;
81     }
82 
enforceActor(@onNull OverlayInfo overlayInfo, @NonNull String methodName, int callingUid, int userId)83     void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName,
84             int callingUid, int userId) throws SecurityException {
85         final ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId);
86         if (actorState == ActorState.ALLOWED) {
87             return;
88         }
89 
90         final String targetOverlayableName = overlayInfo.targetOverlayableName;
91         final String errorMessage = "UID" + callingUid + " is not allowed to call " + methodName
92                 + " for "
93                 + (TextUtils.isEmpty(targetOverlayableName) ? "" : (targetOverlayableName + " in "))
94                 + overlayInfo.targetPackageName + " for user " + userId;
95         Slog.w(OverlayManagerService.TAG, errorMessage + " because " + actorState);
96         throw new SecurityException(errorMessage);
97     }
98 
99     /**
100      * See {@link OverlayActorEnforcer} class comment for actor requirements.
101      * @return true if the actor is allowed to act on the target overlayInfo
102      */
103     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
isAllowedActor(String methodName, OverlayInfo overlayInfo, int callingUid, int userId)104     public ActorState isAllowedActor(String methodName, OverlayInfo overlayInfo,
105             int callingUid, int userId) {
106         // Checked first to avoid package not found errors, which are ignored for calls from shell
107         switch (callingUid) {
108             case Process.ROOT_UID:
109             case Process.SYSTEM_UID:
110                 return ActorState.ALLOWED;
111         }
112 
113         final String targetPackageName = overlayInfo.targetPackageName;
114         var targetPkgState = mPackageManager.getPackageStateForUser(targetPackageName, userId);
115         var targetPkg = targetPkgState == null ? null : targetPkgState.getAndroidPackage();
116         if (targetPkg == null) {
117             return ActorState.TARGET_NOT_FOUND;
118         }
119 
120         if (targetPkg.isDebuggable()) {
121             return ActorState.ALLOWED;
122         }
123 
124         String[] callingPackageNames = mPackageManager.getPackagesForUid(callingUid);
125         if (ArrayUtils.isEmpty(callingPackageNames)) {
126             return ActorState.NO_PACKAGES_FOR_UID;
127         }
128 
129         // A target is always an allowed actor for itself
130         if (ArrayUtils.contains(callingPackageNames, targetPackageName)) {
131             return ActorState.ALLOWED;
132         }
133 
134         String targetOverlayableName = overlayInfo.targetOverlayableName;
135 
136         if (TextUtils.isEmpty(targetOverlayableName)) {
137             try {
138                 if (mPackageManager.doesTargetDefineOverlayable(targetPackageName, userId)) {
139                     return ActorState.MISSING_TARGET_OVERLAYABLE_NAME;
140                 } else {
141                     // If there's no overlayable defined, fallback to the legacy permission check
142                     try {
143                         mPackageManager.enforcePermission(
144                                 android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
145 
146                         // If the previous method didn't throw, check passed
147                         return ActorState.ALLOWED;
148                     } catch (SecurityException e) {
149                         return ActorState.MISSING_LEGACY_PERMISSION;
150                     }
151                 }
152             } catch (IOException e) {
153                 return ActorState.ERROR_READING_OVERLAYABLE;
154             }
155         }
156 
157         OverlayableInfo targetOverlayable;
158         try {
159             targetOverlayable = mPackageManager.getOverlayableForTarget(targetPackageName,
160                     targetOverlayableName, userId);
161         } catch (IOException e) {
162             return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE;
163         }
164 
165         if (targetOverlayable == null) {
166             return ActorState.MISSING_OVERLAYABLE;
167         }
168 
169         String actor = targetOverlayable.actor;
170         if (TextUtils.isEmpty(actor)) {
171             // If there's no actor defined, fallback to the legacy permission check
172             try {
173                 mPackageManager.enforcePermission(
174                         android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
175 
176                 // If the previous method didn't throw, check passed
177                 return ActorState.ALLOWED;
178             } catch (SecurityException e) {
179                 return ActorState.MISSING_LEGACY_PERMISSION;
180             }
181         }
182 
183         Map<String, Map<String, String>> namedActors = mPackageManager.getNamedActors();
184         Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
185         ActorState actorUriState = actorUriPair.second;
186         if (actorUriState != ActorState.ALLOWED) {
187             return actorUriState;
188         }
189 
190         String actorPackageName = actorUriPair.first;
191         var actorPackageState = mPackageManager.getPackageStateForUser(actorPackageName, userId);
192         if (actorPackageState == null || actorPackageState.getAndroidPackage() == null) {
193             return ActorState.ACTOR_NOT_FOUND;
194         }
195 
196         // Currently only pre-installed apps can be actors
197         if (!actorPackageState.isSystem()) {
198             return ActorState.ACTOR_NOT_PREINSTALLED;
199         }
200 
201         if (ArrayUtils.contains(callingPackageNames, actorPackageName)) {
202             return ActorState.ALLOWED;
203         }
204 
205         return ActorState.INVALID_ACTOR;
206     }
207 
208     /**
209      * For easier logging/debugging, a set of all possible failure/success states when running
210      * enforcement.
211      *
212      * The ordering of this enum should be maintained in the order that cases are checked in code,
213      * as this ordering is used inside OverlayActorEnforcerTests.
214      */
215     public enum ActorState {
216         TARGET_NOT_FOUND,
217         NO_PACKAGES_FOR_UID,
218         MISSING_TARGET_OVERLAYABLE_NAME,
219         MISSING_LEGACY_PERMISSION,
220         ERROR_READING_OVERLAYABLE,
221         UNABLE_TO_GET_TARGET_OVERLAYABLE,
222         MISSING_OVERLAYABLE,
223         INVALID_OVERLAYABLE_ACTOR_NAME,
224         NO_NAMED_ACTORS,
225         MISSING_NAMESPACE,
226         MISSING_ACTOR_NAME,
227         ACTOR_NOT_FOUND,
228         ACTOR_NOT_PREINSTALLED,
229         INVALID_ACTOR,
230         ALLOWED
231     }
232 }
233