From 71a9bb51861736106919e97afec14a9bccdc4e10 Mon Sep 17 00:00:00 2001 From: Dmitry Muhomor Date: Fri, 7 Apr 2023 18:01:54 +0300 Subject: [PATCH] gmscompat: support bypassing permission requirements of GmsCore services --- core/java/android/app/ContextImpl.java | 6 ++ core/java/android/os/Binder.java | 12 ++++ .../internal/gmscompat/GmsCompatConfig.java | 19 ++++++ .../android/internal/gmscompat/GmsHooks.java | 64 +++++++++++++++++++ .../sysservice/GmcPackageManager.java | 28 +++++++- 5 files changed, 128 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 579671d1267b..966876c882f3 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2259,6 +2259,12 @@ public int checkSelfPermission(String permission) { return PERMISSION_DENIED; } + if (GmsCompat.isEnabled()) { + if (GmsHooks.shouldSpoofSelfPermissionCheck(permission)) { + return PERMISSION_GRANTED; + } + } + return checkPermission(permission, Process.myPid(), Process.myUid()); } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index b1199a6263b1..b57b4b8da646 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -684,9 +684,14 @@ public void attachInterface(@Nullable IInterface owner, @Nullable String descrip if (BinderRedirector.enabled()) { mPerformRedirectionCheck = "com.google.android.gms.common.internal.IGmsCallbacks".equals(descriptor); } + + if (GmsCompat.isGmsCore()) { + mIsGmsServiceBroker = GmsHooks.GMS_SERVICE_BROKER_INTERFACE_DESCRIPTOR.equals(descriptor); + } } private boolean mPerformRedirectionCheck; + private boolean mIsGmsServiceBroker; /** * Default implementation returns an empty interface name. @@ -1286,7 +1291,11 @@ private boolean execTransactInternal(int code, long dataObj, long replyObj, int final boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL) && (Binder.isStackTrackingEnabled() || Binder.isTracingEnabled(callingUid)); data.mPerformBinderRedirectionCheck = mPerformRedirectionCheck; + boolean onBeginGmsServiceBrokerCallRet = false; try { + if (mIsGmsServiceBroker) { + onBeginGmsServiceBrokerCallRet = GmsHooks.onBeginGmsServiceBrokerCall(code, data); + } final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher; if (heavyHitterWatcher != null) { // Notify the heavy hitter watcher, if it's enabled. @@ -1328,6 +1337,9 @@ private boolean execTransactInternal(int code, long dataObj, long replyObj, int res = true; } finally { data.mPerformBinderRedirectionCheck = false; + if (onBeginGmsServiceBrokerCallRet) { + GmsHooks.onEndGmsServiceBrokerCall(); + } if (tracingEnabled) { Trace.traceEnd(Trace.TRACE_TAG_AIDL); } diff --git a/core/java/com/android/internal/gmscompat/GmsCompatConfig.java b/core/java/com/android/internal/gmscompat/GmsCompatConfig.java index 5f8d45e1784d..2abdb73776e2 100644 --- a/core/java/com/android/internal/gmscompat/GmsCompatConfig.java +++ b/core/java/com/android/internal/gmscompat/GmsCompatConfig.java @@ -11,6 +11,7 @@ import android.os.Parcelable; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.SparseArray; import com.android.internal.gmscompat.flags.GmsFlag; @@ -26,6 +27,8 @@ public class GmsCompatConfig implements Parcelable { public final ArrayMap> forceDefaultFlagsMap = new ArrayMap<>(); // keys are package names, values are list of permissions self-checks of which should be spoofed public final ArrayMap> spoofSelfPermissionChecksMap = new ArrayMap<>(); + // keys are serviceIds, values are service permission requirements that need to be bypassed + public final SparseArray> gmsServiceBrokerPermissionBypasses = new SparseArray<>(); // set only in processes for which GmsCompat is enabled, to speed up lookups public ArraySet spoofSelfPermissionChecks; @@ -93,6 +96,15 @@ public void writeToParcel(Parcel p, int wtpFlags) { writeArrayMapStringStringList(forceDefaultFlagsMap, p); writeArrayMapStringStringList(spoofSelfPermissionChecksMap, p); + { + var map = gmsServiceBrokerPermissionBypasses; + int cnt = map.size(); + p.writeInt(cnt); + for (int i = 0; i < cnt; ++i) { + p.writeInt(map.keyAt(i)); + p.writeArraySet(map.valueAt(i)); + } + } } static void writeArrayMapStringStringList(ArrayMap> map, Parcel p) { @@ -141,6 +153,13 @@ public GmsCompatConfig createFromParcel(Parcel p) { readArrayMapStringStringList(p, r.forceDefaultFlagsMap); readArrayMapStringStringList(p, r.spoofSelfPermissionChecksMap); + { + int cnt = p.readInt(); + ClassLoader cl = String.class.getClassLoader(); + for (int i = 0; i < cnt; ++i) { + r.gmsServiceBrokerPermissionBypasses.put(p.readInt(), (ArraySet) p.readArraySet(cl)); + } + } if (GmsCompat.isEnabled()) { ArrayList perms = r.spoofSelfPermissionChecksMap.get(ActivityThread.currentPackageName()); diff --git a/core/java/com/android/internal/gmscompat/GmsHooks.java b/core/java/com/android/internal/gmscompat/GmsHooks.java index 9a8162eac533..568b67f8509c 100644 --- a/core/java/com/android/internal/gmscompat/GmsHooks.java +++ b/core/java/com/android/internal/gmscompat/GmsHooks.java @@ -51,6 +51,7 @@ import android.provider.Downloads; import android.provider.Settings; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.webkit.WebView; @@ -58,6 +59,7 @@ import com.android.internal.gmscompat.client.ClientPriorityManager; import com.android.internal.gmscompat.client.GmsCompatClientService; import com.android.internal.gmscompat.flags.GmsFlag; +import com.android.internal.gmscompat.sysservice.GmcPackageManager; import com.android.internal.gmscompat.util.GmcActivityUtils; import com.android.internal.gmscompat.util.GmsCoreActivityLauncher; @@ -104,6 +106,7 @@ public static void init(Context ctx, String packageName) { } configUpdateLock = new Object(); + tlPermissionsToSpoof = new ThreadLocal<>(); // Locking is needed to prevent a race that would occur if config is updated via // BinderGca2Gms#updateConfig in the time window between BinderGms2Gca#connect and setConfig() @@ -649,5 +652,66 @@ public static Service maybeInstantiateService(String className) { private static volatile SQLiteOpenHelper phenotypeDb; public static SQLiteOpenHelper getPhenotypeDb() { return phenotypeDb; } + private static ThreadLocal> tlPermissionsToSpoof; + + public static boolean shouldSpoofSelfPermissionCheck(String perm) { + ArraySet set = tlPermissionsToSpoof.get(); + if (set == null) { + return false; + } + + return set.contains(perm); + } + + public static final String GMS_SERVICE_BROKER_INTERFACE_DESCRIPTOR = + "com.google.android.gms.common.internal.IGmsServiceBroker"; + + public static boolean onBeginGmsServiceBrokerCall(int transactionCode, Parcel data) { + if (transactionCode != 46) { // getService() method + return false; + } + + try { + data.enforceInterface(GMS_SERVICE_BROKER_INTERFACE_DESCRIPTOR); + // IGmsCallbacks binder + data.readStrongBinder(); + + if (data.readInt() == 1) { // GetServiceRequest is present + // GetServiceRequest object header + data.readInt(); + data.readInt(); + + // version + data.readInt(); + data.readInt(); + + // id of serviceId property + data.readInt(); + + int serviceId = data.readInt(); + + ArraySet permsToSpoof = config().gmsServiceBrokerPermissionBypasses.get(serviceId); + if (permsToSpoof != null) { + Log.d(TAG, "start spoofing self permission checks for getService() call for API " + + serviceId + ", perms: " + Arrays.toString(permsToSpoof.toArray())); + tlPermissionsToSpoof.set(permsToSpoof); + // there's a second layer of caching inside GmsCore, need to notify permission + // change listener used by that cache + GmcPackageManager.notifyPermissionsChangeListeners(); + return true; + } + } + } finally { + data.setDataPosition(0); + } + + return false; + } + + public static void onEndGmsServiceBrokerCall() { + Log.d(TAG, "end self permission check spoofing"); + tlPermissionsToSpoof.set(null); + } + private GmsHooks() {} } diff --git a/core/java/com/android/internal/gmscompat/sysservice/GmcPackageManager.java b/core/java/com/android/internal/gmscompat/sysservice/GmcPackageManager.java index 1201ec02f0d4..c7ad83127155 100644 --- a/core/java/com/android/internal/gmscompat/sysservice/GmcPackageManager.java +++ b/core/java/com/android/internal/gmscompat/sysservice/GmcPackageManager.java @@ -31,8 +31,10 @@ import android.content.pm.PackageInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.VersionedPackage; +import android.os.Process; import android.os.UserHandle; import android.util.ArraySet; +import android.util.Log; import com.android.internal.gmscompat.GmsInfo; import com.android.internal.gmscompat.PlayStoreHooks; @@ -135,7 +137,31 @@ public boolean hasSystemFeature(String name) { // requires privileged OBSERVE_GRANT_REVOKE_PERMISSIONS permission @Override - public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) {} + public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) { + synchronized (onPermissionsChangedListeners) { + onPermissionsChangedListeners.add(listener); + } + } + + @Override + public void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener) { + synchronized (onPermissionsChangedListeners) { + onPermissionsChangedListeners.remove(listener); + } + } + + public static void notifyPermissionsChangeListeners() { + Log.d("GmcPackageManager", "notifyPermissionsChangeListeners"); + int myUid = Process.myUid(); + synchronized (onPermissionsChangedListeners) { + for (OnPermissionsChangedListener l : onPermissionsChangedListeners) { + l.onPermissionsChanged(myUid); + } + } + } + + private static final ArrayList onPermissionsChangedListeners = + new ArrayList<>(); // MATCH_ANY_USER flag requires privileged INTERACT_ACROSS_USERS permission