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.location;
18 
19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
20 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
22 
23 import android.annotation.IntDef;
24 import android.annotation.Nullable;
25 import android.app.AppOpsManager;
26 import android.content.Context;
27 import android.os.Binder;
28 import android.os.UserHandle;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.util.ArrayUtils;
32 import com.android.internal.util.Preconditions;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.util.Objects;
37 
38 /**
39  * Represents the calling process's uid, pid, and package name.
40  */
41 public final class CallerIdentity {
42 
43     public static final int PERMISSION_NONE = 0;
44     public static final int PERMISSION_COARSE = 1;
45     public static final int PERMISSION_FINE = 2;
46 
47     @IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
48     @Retention(RetentionPolicy.SOURCE)
49     public @interface PermissionLevel {}
50 
51     /**
52      * Converts the given permission level to the corresponding permission.
53      */
asPermission(@ermissionLevel int permissionLevel)54     public static String asPermission(@PermissionLevel int permissionLevel) {
55         switch (permissionLevel) {
56             case PERMISSION_COARSE:
57                 return ACCESS_COARSE_LOCATION;
58             case PERMISSION_FINE:
59                 return ACCESS_FINE_LOCATION;
60             default:
61                 throw new IllegalArgumentException();
62         }
63     }
64 
65     /**
66      * Converts the given permission level to the corresponding appop.
67      */
asAppOp(@ermissionLevel int permissionLevel)68     public static int asAppOp(@PermissionLevel int permissionLevel) {
69         switch (permissionLevel) {
70             case PERMISSION_COARSE:
71                 return AppOpsManager.OP_COARSE_LOCATION;
72             case PERMISSION_FINE:
73                 return AppOpsManager.OP_FINE_LOCATION;
74             default:
75                 throw new IllegalArgumentException();
76         }
77     }
78 
79     /**
80      * Creates a CallerIdentity from the current binder identity, using the given package and
81      * feature id. The package will be checked to enforce it belongs to the calling uid, and a
82      * security exception will be thrown if it is invalid.
83      */
fromBinder(Context context, String packageName, @Nullable String featureId)84     public static CallerIdentity fromBinder(Context context, String packageName,
85             @Nullable String featureId) {
86         return fromBinder(context, packageName, featureId, null);
87     }
88 
89     /**
90      * Creates a CallerIdentity from the current binder identity, using the given package, feature
91      * id, and listener id. The package will be checked to enforce it belongs to the calling uid,
92      * and a security exception will be thrown if it is invalid.
93      */
fromBinder(Context context, String packageName, @Nullable String featureId, @Nullable String listenerId)94     public static CallerIdentity fromBinder(Context context, String packageName,
95             @Nullable String featureId, @Nullable String listenerId) {
96         int uid = Binder.getCallingUid();
97         if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) {
98             throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
99         }
100 
101         return fromBinderUnsafe(context, packageName, featureId, listenerId);
102     }
103 
104     /**
105      * Creates a CallerIdentity from the current binder identity, using the given package and
106      * feature id. The package will not be checked to enforce that it belongs to the calling uid -
107      * this method should only be used if the package will be validated by some other means, such as
108      * an appops call.
109      */
fromBinderUnsafe(Context context, String packageName, @Nullable String featureId)110     public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
111             @Nullable String featureId) {
112         return fromBinderUnsafe(context, packageName, featureId, null);
113     }
114 
115     /**
116      * Creates a CallerIdentity from the current binder identity, using the given package, feature
117      * id, and listener id. The package will not be checked to enforce that it belongs to the
118      * calling uid - this method should only be used if the package will be validated by some other
119      * means, such as an appops call.
120      */
fromBinderUnsafe(Context context, String packageName, @Nullable String featureId, @Nullable String listenerId)121     public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
122             @Nullable String featureId, @Nullable String listenerId) {
123         return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
124                 UserHandle.getCallingUserId(), packageName, featureId, listenerId,
125                 getBinderPermissionLevel(context));
126     }
127 
128     /**
129      * Throws a security exception if the caller does not hold a location permission.
130      */
enforceCallingOrSelfLocationPermission(Context context)131     public static void enforceCallingOrSelfLocationPermission(Context context) {
132         enforceLocationPermission(Binder.getCallingUid(), getBinderPermissionLevel(context));
133     }
134 
135     /**
136      * Returns false if the caller does not hold a location permission, true otherwise.
137      */
checkCallingOrSelfLocationPermission(Context context)138     public static boolean checkCallingOrSelfLocationPermission(Context context) {
139         return checkLocationPermission(getBinderPermissionLevel(context));
140     }
141 
enforceLocationPermission(int uid, @PermissionLevel int permissionLevel)142     private static void enforceLocationPermission(int uid, @PermissionLevel int permissionLevel) {
143         if (checkLocationPermission(permissionLevel)) {
144             return;
145         }
146 
147         throw new SecurityException("uid " + uid + " does not have " + ACCESS_COARSE_LOCATION
148                 + " or " + ACCESS_FINE_LOCATION + ".");
149     }
150 
checkLocationPermission(@ermissionLevel int permissionLevel)151     private static boolean checkLocationPermission(@PermissionLevel int permissionLevel) {
152         return permissionLevel >= PERMISSION_COARSE;
153     }
154 
getBinderPermissionLevel(Context context)155     private static @PermissionLevel int getBinderPermissionLevel(Context context) {
156         if (context.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
157             return PERMISSION_FINE;
158         }
159         if (context.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED) {
160             return PERMISSION_COARSE;
161         }
162 
163         return PERMISSION_NONE;
164     }
165 
166     /** The calling UID. */
167     public final int uid;
168 
169     /** The calling PID. */
170     public final int pid;
171 
172     /** The calling user. */
173     public final int userId;
174 
175     /** The calling package name. */
176     public final String packageName;
177 
178     /** The calling feature id. */
179     public final @Nullable String featureId;
180 
181     /** The calling listener id. */
182     public final @Nullable String listenerId;
183 
184     /**
185      * The calling location permission level. This field should only be used for validating
186      * permissions for API access. It should not be used for validating permissions for location
187      * access - that must be done through appops.
188      */
189     public final @PermissionLevel int permissionLevel;
190 
191     @VisibleForTesting
CallerIdentity(int uid, int pid, int userId, String packageName, @Nullable String featureId, @PermissionLevel int permissionLevel)192     public CallerIdentity(int uid, int pid, int userId, String packageName,
193             @Nullable String featureId, @PermissionLevel int permissionLevel) {
194         this(uid, pid, userId, packageName, featureId, null, permissionLevel);
195     }
196 
CallerIdentity(int uid, int pid, int userId, String packageName, @Nullable String featureId, @Nullable String listenerId, @PermissionLevel int permissionLevel)197     private CallerIdentity(int uid, int pid, int userId, String packageName,
198             @Nullable String featureId, @Nullable String listenerId,
199             @PermissionLevel int permissionLevel) {
200         this.uid = uid;
201         this.pid = pid;
202         this.userId = userId;
203         this.packageName = Objects.requireNonNull(packageName);
204         this.featureId = featureId;
205         this.listenerId = listenerId;
206         this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE,
207                 PERMISSION_FINE, "permissionLevel");
208     }
209 
210     /**
211      * Throws a security exception if the CallerIdentity does not hold a location permission.
212      */
enforceLocationPermission()213     public void enforceLocationPermission() {
214         enforceLocationPermission(uid, permissionLevel);
215     }
216 
217     @Override
toString()218     public String toString() {
219         int length = 10 + packageName.length();
220         if (featureId != null) {
221             length += featureId.length();
222         }
223 
224         StringBuilder builder = new StringBuilder(length);
225         builder.append(pid).append("/").append(packageName);
226         if (featureId != null) {
227             builder.append("[");
228             if (featureId.startsWith(packageName)) {
229                 builder.append(featureId.substring(packageName.length()));
230             } else {
231                 builder.append(featureId);
232             }
233             builder.append("]");
234         }
235         return builder.toString();
236     }
237 
238     @Override
equals(Object o)239     public boolean equals(Object o) {
240         if (this == o) {
241             return true;
242         }
243         if (!(o instanceof CallerIdentity)) {
244             return false;
245         }
246         CallerIdentity that = (CallerIdentity) o;
247         return uid == that.uid
248                 && pid == that.pid
249                 && packageName.equals(that.packageName)
250                 && Objects.equals(featureId, that.featureId)
251                 && Objects.equals(listenerId, that.listenerId);
252     }
253 
254     @Override
hashCode()255     public int hashCode() {
256         return Objects.hash(uid, pid, packageName, featureId);
257     }
258 }
259