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

Commit e91488c1 authored by Kweku Adams's avatar Kweku Adams Committed by Automerger Merge Worker
Browse files

Merge "Revert^2 "Require network permission for networked jobs."" into udc-dev...

Merge "Revert^2 "Require network permission for networked jobs."" into udc-dev am: 80901248 am: e32b277b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22313282



Change-Id: Ie3c1931e652597b59f12125e821da4efdbee001f
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents cae773e9 e32b277b
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -1382,6 +1382,12 @@ public class JobInfo implements Parcelable {
         * Calling this method will override any requirements previously defined
         * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
         * want to call one of these methods.
         *
         * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
         * an app must hold the {@link android.Manifest.permission#INTERNET} and
         * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
         * schedule a job that requires a network.
         *
         * <p class="note">
         * When your job executes in
         * {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1438,6 +1444,11 @@ public class JobInfo implements Parcelable {
         * otherwise you'll use the default network which may not meet this
         * constraint.
         *
         * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
         * an app must hold the {@link android.Manifest.permission#INTERNET} and
         * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
         * schedule a job that requires a network.
         *
         * @param networkRequest The detailed description of the kind of network
         *            this job requires, or {@code null} if no specific kind of
         *            network is required. Defining a {@link NetworkSpecifier}
+4 −0
Original line number Diff line number Diff line
@@ -481,6 +481,10 @@ public class JobParameters implements Parcelable {
     * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
     * a metered network when there is a surplus of metered data available.
     *
     * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
     * this will return {@code null} if the app does not hold the permissions specified in
     * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
     *
     * @return the network that should be used to perform any network requests
     *         for this job, or {@code null} if this job didn't set any required
     *         network type or if the job executed when there was no available network to use.
+85 −30
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -190,6 +191,14 @@ public class JobSchedulerService extends com.android.server.SystemService
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;

    /**
     * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling
     * a job with a connectivity constraint.
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L;

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static Clock sSystemClock = Clock.systemUTC();

@@ -299,6 +308,14 @@ public class JobSchedulerService extends com.android.server.SystemService
    private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
            new RemoteCallbackList<>();

    /**
     * Cache of grant status of permissions, keyed by UID->PID->permission name. A missing value
     * means the state has not been queried.
     */
    @GuardedBy("mPermissionCache")
    private final SparseArray<SparseArrayMap<String, Boolean>> mPermissionCache =
            new SparseArray<>();

    private final CountQuotaTracker mQuotaTracker;
    private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
    private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -1042,6 +1059,10 @@ public class JobSchedulerService extends com.android.server.SystemService
            final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);

            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
                synchronized (mPermissionCache) {
                    // Something changed. Better clear the cached permission set.
                    mPermissionCache.remove(pkgUid);
                }
                // Purge the app's jobs if the whole package was just disabled.  When this is
                // the case the component name will be a bare package name.
                if (pkgName != null && pkgUid != -1) {
@@ -1106,17 +1127,19 @@ public class JobSchedulerService extends com.android.server.SystemService
                    Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
                }
            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                synchronized (mPermissionCache) {
                    // Something changed. Better clear the cached permission set.
                    mPermissionCache.remove(pkgUid);
                }
                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                    final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                    synchronized (mLock) {
                        mUidToPackageCache.remove(uid);
                    }
                } else {
                    synchronized (mJobSchedulerStub.mPersistCache) {
                        mJobSchedulerStub.mPersistCache.remove(pkgUid);
                        mUidToPackageCache.remove(pkgUid);
                    }
                }
            } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                synchronized (mPermissionCache) {
                    mPermissionCache.remove(pkgUid);
                }
                if (DEBUG) {
                    Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")");
                }
@@ -1155,6 +1178,14 @@ public class JobSchedulerService extends com.android.server.SystemService
                    }
                }
                mConcurrencyManager.onUserRemoved(userId);
                synchronized (mPermissionCache) {
                    for (int u = mPermissionCache.size() - 1; u >= 0; --u) {
                        final int uid = mPermissionCache.keyAt(u);
                        if (userId == UserHandle.getUserId(uid)) {
                            mPermissionCache.removeAt(u);
                        }
                    }
                }
            } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
                // Has this package scheduled any jobs, such that we will take action
                // if it were to be force-stopped?
@@ -3748,18 +3779,38 @@ public class JobSchedulerService extends com.android.server.SystemService
    }

    /**
     * Binder stub trampoline implementation
     * Returns whether the app has the permission granted.
     * This currently only works for normal permissions and <b>DOES NOT</b> work for runtime
     * permissions.
     * TODO: handle runtime permissions
     */
    final class JobSchedulerStub extends IJobScheduler.Stub {
    private boolean hasPermission(int uid, int pid, @NonNull String permission) {
        synchronized (mPermissionCache) {
            SparseArrayMap<String, Boolean> pidPermissions = mPermissionCache.get(uid);
            if (pidPermissions == null) {
                pidPermissions = new SparseArrayMap<>();
                mPermissionCache.put(uid, pidPermissions);
            }
            final Boolean cached = pidPermissions.get(pid, permission);
            if (cached != null) {
                return cached;
            }

            final int result = getContext().checkPermission(permission, pid, uid);
            final boolean permissionGranted = (result == PackageManager.PERMISSION_GRANTED);
            pidPermissions.add(pid, permission, permissionGranted);
            return permissionGranted;
        }
    }

    /**
         * Cache determination of whether a given app can persist jobs
         * key is uid of the calling app; value is undetermined/true/false
     * Binder stub trampoline implementation
     */
        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();

    final class JobSchedulerStub extends IJobScheduler.Stub {
        // Enforce that only the app itself (or shared uid participant) can schedule a
        // job that runs one of the app's services, as well as verifying that the
        // named service properly requires the BIND_JOB_SERVICE permission
        // TODO(141645789): merge enforceValidJobRequest() with validateJob()
        private void enforceValidJobRequest(int uid, int pid, JobInfo job) {
            final PackageManager pm = getContext()
                    .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
@@ -3784,31 +3835,33 @@ public class JobSchedulerService extends com.android.server.SystemService
                throw new IllegalArgumentException(
                        "Tried to schedule job for non-existent component: " + service);
            }
            // If we get this far we're good to go; all we need to do now is check
            // whether the app is allowed to persist its scheduled work.
            if (job.isPersisted() && !canPersistJobs(pid, uid)) {
                throw new IllegalArgumentException("Requested job cannot be persisted without"
                        + " holding android.permission.RECEIVE_BOOT_COMPLETED permission");
            }
            if (job.getRequiredNetwork() != null
                    && CompatChanges.isChangeEnabled(
                            REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
                // All networking, including with the local network and even local to the device,
                // requires the INTERNET permission.
                if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) {
                    throw new SecurityException(Manifest.permission.INTERNET
                            + " required for jobs with a connectivity constraint");
                }
                if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) {
                    throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE
                            + " required for jobs with a connectivity constraint");
                }
            }
        }

        private boolean canPersistJobs(int pid, int uid) {
            // If we get this far we're good to go; all we need to do now is check
            // whether the app is allowed to persist its scheduled work.
            final boolean canPersist;
            synchronized (mPersistCache) {
                Boolean cached = mPersistCache.get(uid);
                if (cached != null) {
                    canPersist = cached.booleanValue();
                } else {
            // Persisting jobs is tantamount to running at boot, so we permit
            // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
            // permission
                    int result = getContext().checkPermission(
                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
                    canPersist = (result == PackageManager.PERMISSION_GRANTED);
                    mPersistCache.put(uid, canPersist);
                }
            }
            return canPersist;
            return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED);
        }

        private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
@@ -4027,6 +4080,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                        + " not permitted to schedule jobs for other apps");
            }

            enforceValidJobRequest(callerUid, callerPid, job);

            int result = validateJob(job, callerUid, callerPid, userId, packageName, null);
            if (result != JobScheduler.RESULT_SUCCESS) {
                return result;
+44 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.job.JobInfo.getPriorityString;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.Manifest;
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,6 +40,7 @@ import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.net.Network;
import android.net.Uri;
@@ -339,12 +341,13 @@ public final class JobServiceContext implements ServiceConnection {
                job.changedAuthorities.toArray(triggeredAuthorities);
            }
            final JobInfo ji = job.getJob();
            final Network passedNetwork = canGetNetworkInformation(job) ? job.network : null;
            mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(),
                    ji.getExtras(),
                    ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                    isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
                    job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities,
                    job.network);
                    passedNetwork);
            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
            mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
            mMaxExecutionTimeMillis =
@@ -504,6 +507,37 @@ public final class JobServiceContext implements ServiceConnection {
        }
    }

    private boolean canGetNetworkInformation(@NonNull JobStatus job) {
        if (job.getJob().getRequiredNetwork() == null) {
            // The job never had a network constraint, so we're not going to give it a network
            // object. Add this check as an early return to avoid wasting cycles doing permission
            // checks for this job.
            return false;
        }
        // The calling app is doing the work, so use its UID, not the source UID.
        final int uid = job.getUid();
        if (CompatChanges.isChangeEnabled(
                JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
            final String pkgName = job.getServiceComponent().getPackageName();
            if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) {
                return false;
            }
            if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) {
                return false;
            }
        }

        return true;
    }

    private boolean hasPermissionForDelivery(int uid, @NonNull String pkgName,
            @NonNull String permission) {
        final int result = PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
                PermissionChecker.PID_UNKNOWN, uid, pkgName, /* attributionTag */ null,
                "network info via JS");
        return result == PermissionChecker.PERMISSION_GRANTED;
    }

    @EconomicPolicy.AppAction
    private static int getStartActionId(@NonNull JobStatus job) {
        switch (job.getEffectivePriority()) {
@@ -603,6 +637,15 @@ public final class JobServiceContext implements ServiceConnection {
    }

    void informOfNetworkChangeLocked(Network newNetwork) {
        if (newNetwork != null && mRunningJob != null && !canGetNetworkInformation(mRunningJob)) {
            // The app can't get network information, so there's no point informing it of network
            // changes. This case may happen if an app had scheduled network job and then
            // started targeting U+ without requesting the required network permissions.
            if (DEBUG) {
                Slog.d(TAG, "Skipping network change call because of missing permissions");
            }
            return;
        }
        if (mVerb != VERB_EXECUTING) {
            Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob);
            if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) {