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

Commit 0c8637c0 authored by Adam Bookatz's avatar Adam Bookatz
Browse files

Atom for when fgs accesses appop

statsd logs when an sensitive appop is accessed while a foreground
service is held. These appops include
OP_FINE_LOCATION
OP_COARSE_LOCATION
OP_RECORD_AUDIO
OP_CAMERA

It logs the number of times each of these appops is requested per session
during which the uid holds any foreground service.
Appops requested while the app's process state is TOP are ignored.

Also, the pre-existing ForegroundServiceStateChanged atom has an
additional field that logs whether the fgs is considered 'in-use' in the
context of being allowed while-in-use permissions.

Bug: 149497535
Test: atest UidAtomTests#testForegroundServiceState UidAtomTests#testForegroundServiceAccessAppOp
Test: manually monitor: adb shell cmd stats print-logs && adb logcat -v uid -s statsd | grep "statsd  : {" | egrep '\((60|256)\)'

Change-Id: I991a427dc2ab00399188b10b266ab2d9aa92696d
parent 152b09f1
Loading
Loading
Loading
Loading
+47 −2
Original line number Diff line number Diff line
@@ -393,6 +393,8 @@ message Atom {
        WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"];
        AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"];
        SnapshotMergeReported snapshot_merge_reported = 255;
        ForegroundServiceAppOpSessionEnded foreground_service_app_op_session_ended =
            256  [(module) = "framework"];
        SdkExtensionStatus sdk_extension_status = 354;
    }

@@ -3283,12 +3285,12 @@ message OverlayStateChanged {
    ];
}

/*
/**
 * Logs foreground service starts and stops.
 * Note that this is not when a service starts or stops, but when it is
 * considered foreground.
 * Logged from
 *     //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
 *     frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
 */
message ForegroundServiceStateChanged {
    optional int32 uid = 1 [(is_uid) = true];
@@ -3300,6 +3302,49 @@ message ForegroundServiceStateChanged {
        EXIT = 2;
    }
    optional State state = 3;

    // Whether the fgs is allowed while-in-use permissions, i.e. is considered 'in-use' to the user.
    // (If the fgs was started while the app wasn't TOP it usually will be denied these permissions)
    optional bool allow_while_in_use_permission = 4;
}

/**
 * Logs the number of times a uid accesses a sensitive AppOp during a foreground service session.
 * A foreground service session is any continuous period during which the uid holds at least one
 * foreground service; the atom will be pushed when the uid no longer holds any foreground services.
 * Accesses initiated while the uid is in the TOP state are ignored.
 * Sessions with no attempted accesses are not logged.
 * Logged from
 *     frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
 */
message ForegroundServiceAppOpSessionEnded {
    optional int32 uid = 1 [(is_uid) = true];

    // The operation's name.
    // To the extent possible, preserve the mapping from AppOpsManager.OP_ constants.
    // Only these named ops are actually logged.
    enum AppOpName {
        OP_NONE = -1; // Also represents UNKNOWN.
        OP_COARSE_LOCATION = 0;
        OP_FINE_LOCATION = 1;
        OP_CAMERA = 26;
        OP_RECORD_AUDIO = 27;
    }
    optional AppOpName app_op_name = 2 [default = OP_NONE];

    // The uid's permission mode for accessing the AppOp during this fgs session.
    enum Mode {
        MODE_UNKNOWN = 0;
        MODE_ALLOWED = 1; // Always allowed
        MODE_IGNORED = 2; // Denied
        MODE_FOREGROUND = 3; // Allow-while-in-use (or allowed-one-time)
    }
    optional Mode app_op_mode = 3;

    // Number of times this AppOp was requested and allowed.
    optional int32 count_ops_accepted = 4;
    // Number of times this AppOp was requested but denied.
    optional int32 count_ops_rejected = 5;
}

/**
+11 −6
Original line number Diff line number Diff line
@@ -6910,11 +6910,7 @@ public class AppOpsManager {
     * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
     */
    public int unsafeCheckOpRaw(@NonNull String op, int uid, @NonNull String packageName) {
        try {
            return mService.checkOperationRaw(strOpToOp(op), uid, packageName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return unsafeCheckOpRawNoThrow(op, uid, packageName);
    }

    /**
@@ -6923,8 +6919,17 @@ public class AppOpsManager {
     * {@link #MODE_FOREGROUND}.
     */
    public int unsafeCheckOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
        return unsafeCheckOpRawNoThrow(strOpToOp(op), uid, packageName);
    }

    /**
     * Returns the <em>raw</em> mode associated with the op.
     * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
     * @hide
     */
    public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) {
        try {
            return mService.checkOperationRaw(strOpToOp(op), uid, packageName);
            return mService.checkOperationRaw(op, uid, packageName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+217 −3
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_E
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -87,11 +88,13 @@ import android.util.EventLog;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.webkit.WebViewZygote;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
@@ -176,6 +179,10 @@ public final class ActiveServices {
    /** Temporary list for holding the results of calls to {@link #collectPackageServicesLocked} */
    private ArrayList<ServiceRecord> mTmpCollectionResults = null;

    /** Mapping from uid to their foreground service AppOpCallbacks (if they have one). */
    @GuardedBy("mAm")
    private final SparseArray<AppOpCallback> mFgsAppOpCallbacks = new SparseArray<>();

    /**
     * For keeping ActiveForegroundApps retaining state while the screen is off.
     */
@@ -1454,7 +1461,9 @@ public final class ActiveServices {
                                null, true, false, "");
                        FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                                r.appInfo.uid, r.shortInstanceName,
                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER);
                                FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
                                r.mAllowWhileInUsePermissionInFgs);
                        registerAppOpCallbackLocked(r);
                        mAm.updateForegroundServiceUsageStats(r.name, r.userId, true);
                    }
                    r.postNotification();
@@ -1503,9 +1512,11 @@ public final class ActiveServices {
                mAm.mAppOpsService.finishOperation(
                        AppOpsManager.getToken(mAm.mAppOpsService),
                        AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
                unregisterAppOpCallbackLocked(r);
                FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                        r.appInfo.uid, r.shortInstanceName,
                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
                        FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                        r.mAllowWhileInUsePermissionInFgs);
                mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
                if (r.app != null) {
                    mAm.updateLruProcessLocked(r.app, false, null);
@@ -1526,6 +1537,207 @@ public final class ActiveServices {
        }
    }

    /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */
    private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) {
        if (r.app == null) {
            return;
        }
        final int uid = r.appInfo.uid;
        AppOpCallback callback = mFgsAppOpCallbacks.get(uid);
        if (callback == null) {
            callback = new AppOpCallback(r.app, mAm.getAppOpsManager());
            mFgsAppOpCallbacks.put(uid, callback);
        }
        callback.registerLocked();
    }

    /** Unregisters a foreground service's AppOpCallback. */
    private void unregisterAppOpCallbackLocked(@NonNull ServiceRecord r) {
        final int uid = r.appInfo.uid;
        final AppOpCallback callback = mFgsAppOpCallbacks.get(uid);
        if (callback != null) {
            callback.unregisterLocked();
            if (callback.isObsoleteLocked()) {
                mFgsAppOpCallbacks.remove(uid);
            }
        }
    }

    /**
     * For monitoring when {@link #LOGGED_AP_OPS} AppOps occur by an app while it is holding
     * at least one foreground service and is not also in the TOP state.
     * Once the uid no longer holds any foreground services, this callback becomes stale
     * (marked by {@link #isObsoleteLocked()}) and must no longer be used.
     *
     * Methods that end in Locked should only be called while the mAm lock is held.
     */
    private static final class AppOpCallback {
        /** AppOps that should be logged if they occur during a foreground service. */
        private static final int[] LOGGED_AP_OPS = new int[] {
                AppOpsManager.OP_COARSE_LOCATION,
                AppOpsManager.OP_FINE_LOCATION,
                AppOpsManager.OP_RECORD_AUDIO,
                AppOpsManager.OP_CAMERA
        };

        private final ProcessRecord mProcessRecord;

        /** Count of acceptances per appop (for LOGGED_AP_OPS) during this fgs session. */
        @GuardedBy("mCounterLock")
        private final SparseIntArray mAcceptedOps = new SparseIntArray();
        /** Count of rejections per appop (for LOGGED_AP_OPS) during this fgs session. */
        @GuardedBy("mCounterLock")
        private final SparseIntArray mRejectedOps = new SparseIntArray();

        /** Lock for the purposes of mAcceptedOps and mRejectedOps. */
        private final Object mCounterLock = new Object();

        /**
         * AppOp Mode (e.g. {@link AppOpsManager#MODE_ALLOWED} per op.
         * This currently cannot change without the process being killed, so they are constants.
         */
        private final SparseIntArray mAppOpModes = new SparseIntArray();

        /**
         * Number of foreground services currently associated with this AppOpCallback (i.e.
         * currently held for this uid).
         */
        @GuardedBy("mAm")
        private int mNumFgs = 0;

        /**
         * Indicates that this Object is stale and must not be used.
         * Specifically, when mNumFgs decreases down to 0, the callbacks will be unregistered and
         * this AppOpCallback is unusable.
         */
        @GuardedBy("mAm")
        private boolean mDestroyed = false;

        private final AppOpsManager mAppOpsManager;

        AppOpCallback(@NonNull ProcessRecord r, @NonNull AppOpsManager appOpsManager) {
            mProcessRecord = r;
            mAppOpsManager = appOpsManager;
            for (int op : LOGGED_AP_OPS) {
                int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, r.uid, r.info.packageName);
                mAppOpModes.put(op, mode);
            }
        }

        private final AppOpsManager.OnOpNotedListener mOpNotedCallback =
                new AppOpsManager.OnOpNotedListener() {
                    @Override
                    public void onOpNoted(int op, int uid, String pkgName, int result) {
                        if (uid == mProcessRecord.uid && isNotTop()) {
                            incrementOpCount(op, result == AppOpsManager.MODE_ALLOWED);
                        }
                    }
        };

        private final AppOpsManager.OnOpActiveChangedInternalListener mOpActiveCallback =
                new AppOpsManager.OnOpActiveChangedInternalListener() {
                    @Override
                    public void onOpActiveChanged(int op, int uid, String pkgName, boolean active) {
                        if (uid == mProcessRecord.uid && active && isNotTop()) {
                            incrementOpCount(op, true);
                        }
                    }
        };

        private boolean isNotTop() {
            return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP;
        }

        private void incrementOpCount(int op, boolean allowed) {
            synchronized (mCounterLock) {
                final SparseIntArray counter = allowed ? mAcceptedOps : mRejectedOps;
                final int index = counter.indexOfKey(op);
                if (index < 0) {
                    counter.put(op, 1);
                } else {
                    counter.setValueAt(index, counter.valueAt(index) + 1);
                }
            }
        }

        void registerLocked() {
            if (isObsoleteLocked()) {
                Slog.wtf(TAG, "Trying to register on a stale AppOpCallback.");
                return;
            }
            mNumFgs++;
            if (mNumFgs == 1) {
                mAppOpsManager.startWatchingNoted(LOGGED_AP_OPS, mOpNotedCallback);
                mAppOpsManager.startWatchingActive(LOGGED_AP_OPS, mOpActiveCallback);
            }
        }

        void unregisterLocked() {
            mNumFgs--;
            if (mNumFgs <= 0) {
                mDestroyed = true;
                logFinalValues();
                mAppOpsManager.stopWatchingNoted(mOpNotedCallback);
                mAppOpsManager.stopWatchingActive(mOpActiveCallback);
            }
        }

        /**
         * Indicates that all foreground services for this uid are now over and the callback is
         * stale and must never be used again.
         */
        boolean isObsoleteLocked() {
            return mDestroyed;
        }

        private void logFinalValues() {
            synchronized (mCounterLock) {
                for (int op : LOGGED_AP_OPS) {
                    final int acceptances = mAcceptedOps.get(op);
                    final int rejections = mRejectedOps.get(op);
                    if (acceptances > 0 ||  rejections > 0) {
                        FrameworkStatsLog.write(
                                FrameworkStatsLog.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED,
                                mProcessRecord.uid, opToEnum(op),
                                modeToEnum(mAppOpModes.get(op)),
                                acceptances, rejections
                        );
                    }
                }
            }
        }

        /** Maps AppOp mode to atoms.proto enum. */
        private static int modeToEnum(int mode) {
            switch (mode) {
                case AppOpsManager.MODE_ALLOWED: return FrameworkStatsLog
                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_ALLOWED;
                case AppOpsManager.MODE_IGNORED: return FrameworkStatsLog
                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_IGNORED;
                case AppOpsManager.MODE_FOREGROUND: return FrameworkStatsLog
                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_FOREGROUND;
                default: return FrameworkStatsLog
                        .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_UNKNOWN;
            }
        }
    }

    /** Maps AppOp op value to atoms.proto enum. */
    private static int opToEnum(int op) {
        switch (op) {
            case AppOpsManager.OP_COARSE_LOCATION: return FrameworkStatsLog
                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_COARSE_LOCATION;
            case AppOpsManager.OP_FINE_LOCATION: return FrameworkStatsLog
                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_FINE_LOCATION;
            case AppOpsManager.OP_CAMERA: return FrameworkStatsLog
                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_CAMERA;
            case AppOpsManager.OP_RECORD_AUDIO: return FrameworkStatsLog
                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_RECORD_AUDIO;
            default: return FrameworkStatsLog
                    .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_NONE;
        }
    }

    private void cancelForegroundNotificationLocked(ServiceRecord r) {
        if (r.foregroundId != 0) {
            // First check to see if this app has any other active foreground services
@@ -3135,9 +3347,11 @@ public final class ActiveServices {
            mAm.mAppOpsService.finishOperation(
                    AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null);
            unregisterAppOpCallbackLocked(r);
            FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
                    r.appInfo.uid, r.shortInstanceName,
                    FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT);
                    FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT,
                    r.mAllowWhileInUsePermissionInFgs);
            mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
        }