From 470c8f6a3f5a8f6d2c877019d1c22fc619eccad6 Mon Sep 17 00:00:00 2001 From: Soonil Nagarkar Date: Wed, 4 Mar 2020 12:18:57 -0800 Subject: Give location providers a known identity Providers no longer need the ability to specify additional packages, this can be replaced with tracking the provider identity. In addition, move CallerIdentity into the client space, and fix some minor GNSS bugs. Bug: 149839935 Test: atest LocationProviderTest Change-Id: Ide107abc61531946b20d2ac50cd9555fc215ef03 --- .../java/android/location/ILocationManager.aidl | 2 +- .../java/android/location/LocationManager.java | 80 +++++-- .../android/location/LocationManagerInternal.java | 13 +- .../location/util/identity/CallerIdentity.java | 261 +++++++++++++++++++++ .../util/listeners/AbstractListenerManager.java | 31 ++- .../location/ILocationProviderManager.aidl | 2 +- 6 files changed, 365 insertions(+), 24 deletions(-) create mode 100644 location/java/android/location/util/identity/CallerIdentity.java (limited to 'location/java') diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 6be770ea2c7b..5a7c91caaf76 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -95,7 +95,7 @@ interface ILocationManager List getProviders(in Criteria criteria, boolean enabledOnly); String getBestProvider(in Criteria criteria, boolean enabledOnly); ProviderProperties getProviderProperties(String provider); - boolean isProviderPackage(String packageName); + boolean isProviderPackage(String provider, String packageName); List getProviderPackages(String provider); void setExtraLocationControllerPackage(String packageName); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 7523f07674ea..0bf7add422cf 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -64,7 +64,6 @@ import com.android.internal.location.ProviderProperties; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledRunnable; -import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; @@ -313,10 +312,9 @@ public class LocationManager { private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000; - private final Context mContext; - + final Context mContext; @UnsupportedAppUsage - private final ILocationManager mService; + final ILocationManager mService; @GuardedBy("mListeners") private final ArrayMap mListeners = @@ -1403,15 +1401,36 @@ public class LocationManager { * otherwise. * * @hide + * @deprecated Prefer {@link #isProviderPackage(String, String)} instead. */ + @Deprecated @SystemApi @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String packageName) { try { - return mService.isProviderPackage(packageName); + return mService.isProviderPackage(null, packageName); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return false; + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if the given provider corresponds to the given package name. If the given + * provider is null, this will return true if any provider corresponds to the given package + * name. + * + * @param provider a provider listed by {@link #getAllProviders()} or null + * @param packageName the package name to test if it is a provider + * @return true if the given arguments correspond to a provider + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) + public boolean isProviderPackage(@Nullable String provider, @NonNull String packageName) { + try { + return mService.isProviderPackage(provider, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -1420,16 +1439,17 @@ public class LocationManager { * and an empty list if no package is associated with the provider. * * @hide + * @deprecated Prefer {@link #isProviderPackage(String, String)} instead. */ @TestApi + @Deprecated @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) @Nullable public List getProviderPackages(@NonNull String provider) { try { return mService.getProviderPackages(provider); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return Collections.emptyList(); + throw e.rethrowFromSystemServer(); } } @@ -2470,7 +2490,7 @@ public class LocationManager { @Nullable private ICancellationSignal mRemoteCancellationSignal; - private GetCurrentLocationTransport(Executor executor, Consumer consumer) { + GetCurrentLocationTransport(Executor executor, Consumer consumer) { Preconditions.checkArgument(executor != null, "illegal null executor"); Preconditions.checkArgument(consumer != null, "illegal null consumer"); mExecutor = executor; @@ -2595,7 +2615,7 @@ public class LocationManager { private final LocationListener mListener; @Nullable private volatile Executor mExecutor = null; - private LocationListenerTransport(@NonNull LocationListener listener) { + LocationListenerTransport(@NonNull LocationListener listener) { Preconditions.checkArgument(listener != null, "invalid null listener"); mListener = listener; } @@ -2769,7 +2789,7 @@ public class LocationManager { private final OnNmeaMessageListener mListener; - private NmeaAdapter(OnNmeaMessageListener listener) { + NmeaAdapter(OnNmeaMessageListener listener) { mListener = listener; } @@ -2783,7 +2803,7 @@ public class LocationManager { private final GpsStatus.Listener mGpsListener; - private GpsAdapter(GpsStatus.Listener gpsListener) { + GpsAdapter(GpsStatus.Listener gpsListener) { mGpsListener = gpsListener; } @@ -2813,8 +2833,11 @@ public class LocationManager { private @Nullable IGnssStatusListener mListenerTransport; - private volatile @Nullable GnssStatus mGnssStatus; - private volatile int mTtff; + volatile @Nullable GnssStatus mGnssStatus; + volatile int mTtff; + + GnssStatusListenerManager() { + } public GnssStatus getGnssStatus() { return mGnssStatus; @@ -2862,6 +2885,9 @@ public class LocationManager { } private class GnssStatusListener extends IGnssStatusListener.Stub { + GnssStatusListener() { + } + @Override public void onGnssStarted() { deliverToListeners(GnssStatus.Callback::onStarted); @@ -2905,6 +2931,9 @@ public class LocationManager { @Nullable private IGnssMeasurementsListener mListenerTransport; + GnssMeasurementsListenerManager() { + } + @Override protected boolean registerService(GnssRequest request) { Preconditions.checkState(mListenerTransport == null); @@ -2947,6 +2976,9 @@ public class LocationManager { } private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub { + GnssMeasurementsListener() { + } + @Override public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { deliverToListeners((callback) -> callback.onGnssMeasurementsReceived(event)); @@ -2965,6 +2997,9 @@ public class LocationManager { @Nullable private IGnssNavigationMessageListener mListenerTransport; + GnssNavigationMessageListenerManager() { + } + @Override protected boolean registerService(Void ignored) { Preconditions.checkState(mListenerTransport == null); @@ -2994,6 +3029,9 @@ public class LocationManager { } private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub { + GnssNavigationMessageListener() { + } + @Override public void onGnssNavigationMessageReceived(GnssNavigationMessage event) { deliverToListeners((listener) -> listener.onGnssNavigationMessageReceived(event)); @@ -3012,6 +3050,9 @@ public class LocationManager { @Nullable private IGnssAntennaInfoListener mListenerTransport; + GnssAntennaInfoListenerManager() { + } + @Override protected boolean registerService(Void ignored) { Preconditions.checkState(mListenerTransport == null); @@ -3041,6 +3082,9 @@ public class LocationManager { } private class GnssAntennaInfoListener extends IGnssAntennaInfoListener.Stub { + GnssAntennaInfoListener() { + } + @Override public void onGnssAntennaInfoReceived(List infos) { deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos)); @@ -3055,6 +3099,9 @@ public class LocationManager { @Nullable private IBatchedLocationCallback mListenerTransport; + BatchedLocationCallbackManager() { + } + @Override protected boolean registerService(Void ignored) { Preconditions.checkState(mListenerTransport == null); @@ -3083,6 +3130,9 @@ public class LocationManager { } private class BatchedLocationCallback extends IBatchedLocationCallback.Stub { + BatchedLocationCallback() { + } + @Override public void onLocationBatch(List locations) { deliverToListeners((listener) -> listener.onLocationBatch(locations)); diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index 6006d5079b07..542737b479e2 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -18,6 +18,8 @@ package android.location; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.location.util.identity.CallerIdentity; /** * Location manager local system service interface. @@ -36,13 +38,14 @@ public abstract class LocationManagerInternal { public abstract boolean isProviderEnabledForUser(@NonNull String provider, int userId); /** - * Returns true if the given package belongs to a location provider, and so should be afforded - * some special privileges. + * Returns true if the given identity is a location provider. * - * @param packageName The package name to check - * @return True is the given package belongs to a location provider, false otherwise + * @param provider The provider to check, or null to check every provider + * @param identity The identity to match + * @return True if the given identity matches either the given location provider or any + * provider, and false otherwise */ - public abstract boolean isProviderPackage(@NonNull String packageName); + public abstract boolean isProvider(@Nullable String provider, @NonNull CallerIdentity identity); /** * Should only be used by GNSS code. diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java new file mode 100644 index 000000000000..15641eb3bb6f --- /dev/null +++ b/location/java/android/location/util/identity/CallerIdentity.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location.util.identity; + +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.os.Binder; +import android.os.Process; +import android.os.UserHandle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Identifying information on a caller. + * + * @hide + */ +public final class CallerIdentity { + + public static final int PERMISSION_NONE = 0; + public static final int PERMISSION_COARSE = 1; + public static final int PERMISSION_FINE = 2; + + @IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE}) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionLevel {} + + /** + * Converts the given permission level to the corresponding permission. + */ + public static String asPermission(@PermissionLevel int permissionLevel) { + switch (permissionLevel) { + case PERMISSION_COARSE: + return ACCESS_COARSE_LOCATION; + case PERMISSION_FINE: + return ACCESS_FINE_LOCATION; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Converts the given permission level to the corresponding appop. + */ + public static int asAppOp(@PermissionLevel int permissionLevel) { + switch (permissionLevel) { + case PERMISSION_COARSE: + return AppOpsManager.OP_COARSE_LOCATION; + case PERMISSION_FINE: + return AppOpsManager.OP_FINE_LOCATION; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Construct a CallerIdentity for test purposes. + */ + @VisibleForTesting + public static CallerIdentity forTest(int uid, int pid, String packageName, + @Nullable String featureId, @PermissionLevel int permissionLevel) { + + return new CallerIdentity(uid, pid, packageName, featureId, + permissionLevel); + } + + /** + * Creates a CallerIdentity for the current process and context. + */ + public static CallerIdentity fromContext(Context context) { + return new CallerIdentity(Process.myUid(), Process.myPid(), + context.getPackageName(), context.getFeatureId(), + getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid())); + } + + /** + * Creates a CallerIdentity from the current binder identity, using the given package and + * feature id. The package will be checked to enforce it belongs to the calling uid, and a + * security exception will be thrown if it is invalid. + */ + public static CallerIdentity fromBinder(Context context, String packageName, + @Nullable String featureId) { + int uid = Binder.getCallingUid(); + if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) { + throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid); + } + + return fromBinderUnsafe(context, packageName, featureId); + } + + /** + * Creates a CallerIdentity from the current binder identity, using the given package and + * feature id. The package will not be checked to enforce that it belongs to the calling uid - + * this method should only be used if the package will be validated by some other means, such as + * an appops call. + */ + public static CallerIdentity fromBinderUnsafe(Context context, String packageName, + @Nullable String featureId) { + return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), + packageName, featureId, + getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid())); + } + + /** + * Throws a security exception if the caller does not hold a location permission. + */ + public static void enforceCallingOrSelfLocationPermission(Context context, + @PermissionLevel int desiredPermissionLevel) { + enforceLocationPermission(Binder.getCallingUid(), + getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid()), + desiredPermissionLevel); + } + + /** + * Returns false if the caller does not hold a location permission, true otherwise. + */ + public static boolean checkCallingOrSelfLocationPermission(Context context, + @PermissionLevel int desiredPermissionLevel) { + return checkLocationPermission( + getPermissionLevel(context, Binder.getCallingPid(), Binder.getCallingUid()), + desiredPermissionLevel); + } + + private static void enforceLocationPermission(int uid, @PermissionLevel int permissionLevel, + @PermissionLevel int desiredPermissionLevel) { + if (checkLocationPermission(permissionLevel, desiredPermissionLevel)) { + return; + } + + if (desiredPermissionLevel == PERMISSION_COARSE) { + throw new SecurityException("uid " + uid + " does not have " + ACCESS_COARSE_LOCATION + + " or " + ACCESS_FINE_LOCATION + "."); + } else if (desiredPermissionLevel == PERMISSION_FINE) { + throw new SecurityException("uid " + uid + " does not have " + ACCESS_FINE_LOCATION + + "."); + } + } + + private static boolean checkLocationPermission(@PermissionLevel int permissionLevel, + @PermissionLevel int desiredPermissionLevel) { + return permissionLevel >= desiredPermissionLevel; + } + + private static @PermissionLevel int getPermissionLevel(Context context, int pid, int uid) { + if (context.checkPermission(ACCESS_FINE_LOCATION, pid, uid) == PERMISSION_GRANTED) { + return PERMISSION_FINE; + } + if (context.checkPermission(ACCESS_COARSE_LOCATION, pid, uid) == PERMISSION_GRANTED) { + return PERMISSION_COARSE; + } + + return PERMISSION_NONE; + } + + /** The calling UID. */ + public final int uid; + + /** The calling PID. */ + public final int pid; + + /** The calling user. */ + public final int userId; + + /** The calling package name. */ + public final String packageName; + + /** The calling feature id. */ + public final @Nullable String featureId; + + /** + * The calling location permission level. This field should only be used for validating + * permissions for API access. It should not be used for validating permissions for location + * access - that must be done through appops. + */ + public final @PermissionLevel int permissionLevel; + + private CallerIdentity(int uid, int pid, String packageName, + @Nullable String featureId, @PermissionLevel int permissionLevel) { + this.uid = uid; + this.pid = pid; + this.userId = UserHandle.getUserId(uid); + this.packageName = Objects.requireNonNull(packageName); + this.featureId = featureId; + this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE, + PERMISSION_FINE, "permissionLevel"); + } + + /** + * Throws a security exception if the CallerIdentity does not hold a location permission. + */ + public void enforceLocationPermission(@PermissionLevel int desiredPermissionLevel) { + enforceLocationPermission(uid, permissionLevel, desiredPermissionLevel); + } + + @Override + public String toString() { + int length = 10 + packageName.length(); + if (featureId != null) { + length += featureId.length(); + } + + StringBuilder builder = new StringBuilder(length); + builder.append(pid).append("/").append(packageName); + if (featureId != null) { + builder.append("["); + if (featureId.startsWith(packageName)) { + builder.append(featureId.substring(packageName.length())); + } else { + builder.append(featureId); + } + builder.append("]"); + } + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CallerIdentity)) { + return false; + } + CallerIdentity that = (CallerIdentity) o; + return uid == that.uid + && pid == that.pid + && packageName.equals(that.packageName) + && Objects.equals(featureId, that.featureId); + } + + @Override + public int hashCode() { + return Objects.hash(uid, pid, packageName, featureId); + } +} diff --git a/location/java/android/location/util/listeners/AbstractListenerManager.java b/location/java/android/location/util/listeners/AbstractListenerManager.java index facb74766f2c..09c1eb2ae153 100644 --- a/location/java/android/location/util/listeners/AbstractListenerManager.java +++ b/location/java/android/location/util/listeners/AbstractListenerManager.java @@ -156,8 +156,15 @@ public abstract class AbstractListenerManager packageNames); + void onSetFeatureId(String featureId); @UnsupportedAppUsage void onSetAllowed(boolean allowed); -- cgit v1.2.3-59-g8ed1b