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

Commit 7871be0c authored by Beatrice Marchegiani's avatar Beatrice Marchegiani Committed by Android (Google) Code Review
Browse files

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

parents f1c4b2ef 73f0bfe6
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
@@ -1382,12 +1382,6 @@ 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
@@ -1444,11 +1438,6 @@ 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}
+0 −4
Original line number Diff line number Diff line
@@ -481,10 +481,6 @@ 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.
+30 −85
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ 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;
@@ -191,14 +190,6 @@ 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();

@@ -308,14 +299,6 @@ 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 =
@@ -1059,10 +1042,6 @@ 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) {
@@ -1127,19 +1106,17 @@ 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(pkgUid);
                        mUidToPackageCache.remove(uid);
                    }
                } else {
                    synchronized (mJobSchedulerStub.mPersistCache) {
                        mJobSchedulerStub.mPersistCache.remove(pkgUid);
                    }
            } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                synchronized (mPermissionCache) {
                    mPermissionCache.remove(pkgUid);
                }
            } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                if (DEBUG) {
                    Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")");
                }
@@ -1178,14 +1155,6 @@ 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?
@@ -3778,39 +3747,19 @@ public class JobSchedulerService extends com.android.server.SystemService
        }
    }

    /**
     * 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
     */
    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;
        }
    }

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

        // 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)
@@ -3835,33 +3784,31 @@ 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
            return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED);
                    int result = getContext().checkPermission(
                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
                    canPersist = (result == PackageManager.PERMISSION_GRANTED);
                    mPersistCache.put(uid, canPersist);
                }
            }
            return canPersist;
        }

        private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
@@ -4080,8 +4027,6 @@ 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;
+1 −44
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ 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;
@@ -40,7 +39,6 @@ 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;
@@ -341,13 +339,12 @@ 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,
                    passedNetwork);
                    job.network);
            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
            mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
            mMaxExecutionTimeMillis =
@@ -507,37 +504,6 @@ 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()) {
@@ -637,15 +603,6 @@ 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) {