Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8d2ed506 authored by Svet Ganov's avatar Svet Ganov
Browse files

Runtime permission attribution improvements

When an app is proxying access to runtime permission protected
data it needs to check whether the calling app has a permission
to the data it is about to proxy which leaves a trace in app ops
that the requesting app perofmed a data access. However, then the
app doing the work needs to get the protected data itself from the
OS which access gets attributed only to itself. As a result there
are two data accesses in app ops where only the first one is a
proxy one that app A got access to Foo through app B - that is the
one we want to show in the permission tracking UIs - and one
for the data access - that is the one we would want to blame on
the calling app, and in fact, these two accesses should be one -
that app A accessed Foo though B. This limitation requires fragile
one off workarounds where both accesses use the same attribution
tag and sys UI has hardcoded rules to dedupe. Since this is not
documented we cannot expect that the ecosystem would reliably
do this workaround in apps that that the workaround in the OS
would be respected by every OEM.

This change adds a mechaism to resolve this issue. It allows for
an app to create an attribution context for another app and then
any private data access thorugh this context would result in a
single app op blame that A accessed Foo though B, i.e. we no longer
have double accounting. Also this can be nested through apps, e.g.
app A asks app B which asks app C for contacts. In this case app
B creates an attribution context for app A and calls into app C
which creates an attribution context for app B. When app C gets
contacts the entire attribution chain would get a porper, single
blame: that C accessed the data, that B got the data from C, and
that A got the data form B. Furthermore, this mechanism ensures
that apps cannot forget to check permissions for the caller
before proxying private data. In our example B and C don't need
to check the permisisons for A and B, respectively, since the
permisisons for the entire attribution chain are checked before
data delivery. Attribution chains are not forgeable preventing
a bad actor to create an arbitrary one - each attribution is
created by the app it refers to and points to a chain of
attributions created by their corresponding apps.

This change also fixes a bug where all content provider accesses
were double counted in app ops due to double noting. While at
this it also fixes that apps can now access their own last ops.
There was a bug where one could not pass null getting the attributed
ops from a historical package ops while this is a valid use case
since if there is no attribution everything is mapped to the null
tag. There were some app op APIs not being piped thorough the app
ops delegate and by extension through the app ops policy. Also
now that we have nice way to express the permission chain in a
call we no longer need the special casing in activity manager to
handle content provider accesses through the OS. Fixed a bug
where we don't properly handle the android.os.shell calls with
an invlaid tag which was failing while the shell can do any tag.

Finally, to ensure the mechanims is validated and works end-to-end
we are adding support for a voice recognizer to blame the client
app for the mic access. The recognition service can create a blaming
context when opening the mic and if the mic is open, which would
do all permission checks, we would not do so again. Since changes
to PermissionChercker for handling attribution sources were made
the CL also hooks up renounced permissoins in the request permission
flow and in the permission checks.

bug:158792096
bug:180647319

Test:atest CtsPermissionsTestCases
     atest CtsPermissions2TestCases
     atest CtsPermissions3TestCases
     atest CtsPermissions4TestCases
     atest CtsPermissions5TestCases
     atest CtsAppOpsTestCases
     atest CtsAppOps2TestCases

Change-Id: Ib04585515d3dc3956966005ae9d94955b2f3ee08
parent e3585e10
Loading
Loading
Loading
Loading
+16 −8
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.commands.content;
import android.app.ActivityManager;
import android.app.ContentProviderHolder;
import android.app.IActivityManager;
import android.content.AttributionSource;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.IContentProvider;
@@ -562,7 +563,8 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            provider.insert(resolveCallingPackage(), null, mUri, mContentValues, mExtras);
            provider.insert(new AttributionSource(Binder.getCallingUid(),
                    resolveCallingPackage(), null), mUri, mContentValues, mExtras);
        }
    }

@@ -576,7 +578,8 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            provider.delete(resolveCallingPackage(), null, mUri, mExtras);
            provider.delete(new AttributionSource(Binder.getCallingUid(),
                    resolveCallingPackage(), null), mUri, mExtras);
        }
    }

@@ -593,7 +596,8 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            Bundle result = provider.call(null, null, mUri.getAuthority(), mMethod, mArg, mExtras);
            Bundle result = provider.call(new AttributionSource(Binder.getCallingUid(),
                    resolveCallingPackage(), null), mUri.getAuthority(), mMethod, mArg, mExtras);
            if (result != null) {
                result.size(); // unpack
            }
@@ -620,7 +624,9 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "r", null, null)) {
            try (ParcelFileDescriptor fd = provider.openFile(
                    new AttributionSource(Binder.getCallingUid(),
                    resolveCallingPackage(), null), mUri, "r", null)) {
                FileUtils.copy(fd.getFileDescriptor(), FileDescriptor.out);
            }
        }
@@ -633,7 +639,8 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            try (ParcelFileDescriptor fd = provider.openFile(null, null, mUri, "w", null, null)) {
            try (ParcelFileDescriptor fd = provider.openFile(new AttributionSource(
                    Binder.getCallingUid(), resolveCallingPackage(), null), mUri, "w", null)) {
                FileUtils.copy(FileDescriptor.in, fd.getFileDescriptor());
            }
        }
@@ -651,8 +658,8 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            Cursor cursor = provider.query(resolveCallingPackage(), null, mUri, mProjection,
                    mExtras, null);
            Cursor cursor = provider.query(new AttributionSource(Binder.getCallingUid(),
                    resolveCallingPackage(), null), mUri, mProjection, mExtras, null);
            if (cursor == null) {
                System.out.println("No result found.");
                return;
@@ -716,7 +723,8 @@ public class Content {

        @Override
        public void onExecute(IContentProvider provider) throws Exception {
            provider.update(resolveCallingPackage(), null, mUri, mValues, mExtras);
            provider.update(new AttributionSource(Binder.getCallingUid(),
                    resolveCallingPackage(), null), mUri, mValues, mExtras);
        }
    }

+18 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import android.app.ContentProviderHolder;
import android.app.IActivityManager;
import android.app.UiAutomation;
import android.content.AttributionSource;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
@@ -28,6 +29,7 @@ import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -67,7 +69,8 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge {
                    throw new IllegalStateException("Could not find provider: " + providerName);
                }
                provider = holder.provider;
                cursor = provider.query(null, null, Settings.Secure.CONTENT_URI,
                cursor = provider.query(new AttributionSource(Binder.getCallingUid(),
                        resolveCallingPackage(), null), Settings.Secure.CONTENT_URI,
                        new String[] {
                            Settings.Secure.VALUE
                        },
@@ -123,4 +126,18 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge {
        }
        return ret;
    }

    private static String resolveCallingPackage() {
        switch (Binder.getCallingUid()) {
            case Process.ROOT_UID: {
                return "root";
            }
            case Process.SHELL_UID: {
                return "com.android.shell";
            }
            default: {
                return null;
            }
        }
    }
}
+26 −3
Original line number Diff line number Diff line
@@ -9907,6 +9907,27 @@ package android.content {
    method @Deprecated public void setUpdateThrottle(long);
  }
  public final class AttributionSource implements android.os.Parcelable {
    method public boolean checkCallingUid();
    method public int describeContents();
    method public void enforceCallingUid();
    method @Nullable public String getAttributionTag();
    method @Nullable public android.content.AttributionSource getNext();
    method @Nullable public String getPackageName();
    method public int getUid();
    method public boolean isTrusted(@NonNull android.content.Context);
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
  }
  public static final class AttributionSource.Builder {
    ctor public AttributionSource.Builder(int);
    method @NonNull public android.content.AttributionSource build();
    method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@NonNull String);
    method @NonNull public android.content.AttributionSource.Builder setNext(@NonNull android.content.AttributionSource);
    method @NonNull public android.content.AttributionSource.Builder setPackageName(@NonNull String);
  }
  public abstract class BroadcastReceiver {
    ctor public BroadcastReceiver();
    method public final void abortBroadcast();
@@ -10076,6 +10097,7 @@ package android.content {
    method public abstract int delete(@NonNull android.net.Uri, @Nullable String, @Nullable String[]);
    method public int delete(@NonNull android.net.Uri, @Nullable android.os.Bundle);
    method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
    method @Nullable public final android.content.AttributionSource getCallingAttributionSource();
    method @Nullable public final String getCallingAttributionTag();
    method @Nullable public final String getCallingPackage();
    method @Nullable public final String getCallingPackageUnchecked();
@@ -10438,6 +10460,7 @@ package android.content {
    method public abstract android.content.Context getApplicationContext();
    method public abstract android.content.pm.ApplicationInfo getApplicationInfo();
    method public abstract android.content.res.AssetManager getAssets();
    method @NonNull public android.content.AttributionSource getAttributionSource();
    method @Nullable public String getAttributionTag();
    method public abstract java.io.File getCacheDir();
    method public abstract ClassLoader getClassLoader();
@@ -10643,8 +10666,7 @@ package android.content {
  public final class ContextParams {
    method @Nullable public String getAttributionTag();
    method @Nullable public String getReceiverAttributionTag();
    method @Nullable public String getReceiverPackage();
    method @Nullable public android.content.AttributionSource getNextAttributionSource();
  }
  public static final class ContextParams.Builder {
@@ -10652,7 +10674,7 @@ package android.content {
    ctor public ContextParams.Builder(@NonNull android.content.ContextParams);
    method @NonNull public android.content.ContextParams build();
    method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
    method @NonNull public android.content.ContextParams.Builder setReceiverPackage(@Nullable String, @Nullable String);
    method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
  }
  public class ContextWrapper extends android.content.Context {
@@ -39082,6 +39104,7 @@ package android.speech {
    method public void bufferReceived(byte[]) throws android.os.RemoteException;
    method public void endOfSpeech() throws android.os.RemoteException;
    method public void error(int) throws android.os.RemoteException;
    method @NonNull public android.content.AttributionSource getCallingAttributionSource();
    method public int getCallingUid();
    method public void partialResults(android.os.Bundle) throws android.os.RemoteException;
    method public void readyForSpeech(android.os.Bundle) throws android.os.RemoteException;
+11 −3
Original line number Diff line number Diff line
@@ -596,7 +596,7 @@ package android.app {
  public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
    method public int describeContents();
    method @Nullable public android.app.AppOpsManager.AttributedHistoricalOps getAttributedOps(@NonNull String);
    method @Nullable public android.app.AppOpsManager.AttributedHistoricalOps getAttributedOps(@Nullable String);
    method @NonNull public android.app.AppOpsManager.AttributedHistoricalOps getAttributedOpsAt(@IntRange(from=0) int);
    method @IntRange(from=0) public int getAttributedOpsCount();
    method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
@@ -2217,6 +2217,14 @@ package android.content {
    method @NonNull public java.io.File getDeviceProtectedDataDirForUser(@NonNull android.os.UserHandle);
  }
  public final class AttributionSource implements android.os.Parcelable {
    method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set<java.lang.String> getRenouncedPermissions();
  }
  public static final class AttributionSource.Builder {
    method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.AttributionSource.Builder setRenouncedPermissions(@NonNull java.util.Set<java.lang.String>);
  }
  public abstract class BroadcastReceiver {
    method @NonNull public final android.os.UserHandle getSendingUser();
  }
@@ -2291,11 +2299,11 @@ package android.content {
  }
  public final class ContextParams {
    method @Nullable @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set<java.lang.String> getRenouncedPermissions();
    method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public java.util.Set<java.lang.String> getRenouncedPermissions();
  }
  public static final class ContextParams.Builder {
    method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.ContextParams.Builder setRenouncedPermissions(@Nullable java.util.Set<java.lang.String>);
    method @NonNull @RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS) public android.content.ContextParams.Builder setRenouncedPermissions(@NonNull java.util.Set<java.lang.String>);
  }
  public class ContextWrapper extends android.content.Context {
+6 −0
Original line number Diff line number Diff line
@@ -220,6 +220,7 @@ package android.app {
    method @RequiresPermission("android.permission.MANAGE_APPOPS") public void rebootHistory(long);
    method @RequiresPermission("android.permission.MANAGE_APPOPS") public void reloadNonHistoricalState();
    method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetHistoryParameters();
    method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetPackageOpsNoHistory(@NonNull String);
    method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int);
    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int);
    method public static int strOpToOp(@NonNull String);
@@ -654,6 +655,11 @@ package android.bluetooth {

package android.content {

  public final class AttributionSource implements android.os.Parcelable {
    ctor public AttributionSource(int, @Nullable String, @Nullable String);
    ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable android.content.AttributionSource);
  }

  public final class AutofillOptions implements android.os.Parcelable {
    ctor public AutofillOptions(int, boolean);
    method public int describeContents();
Loading