Merge "Fix issue #62390590: SecurityException in JobIntentService$..." into oc-dev

This commit is contained in:
TreeHugger Robot
2017-06-13 21:51:46 +00:00
committed by Android (Google) Code Review
12 changed files with 86 additions and 51 deletions

View File

@@ -29,7 +29,7 @@ import android.util.Slog;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
public class GrantedUriPermissions { public final class GrantedUriPermissions {
private final int mGrantFlags; private final int mGrantFlags;
private final int mSourceUserId; private final int mSourceUserId;
private final String mTag; private final String mTag;

View File

@@ -26,7 +26,7 @@ import android.os.UserHandle;
import java.io.PrintWriter; import java.io.PrintWriter;
public class JobSchedulerShellCommand extends ShellCommand { public final class JobSchedulerShellCommand extends ShellCommand {
public static final int CMD_ERR_NO_PACKAGE = -1000; public static final int CMD_ERR_NO_PACKAGE = -1000;
public static final int CMD_ERR_NO_JOB = -1001; public static final int CMD_ERR_NO_JOB = -1001;
public static final int CMD_ERR_CONSTRAINTS = -1002; public static final int CMD_ERR_CONSTRAINTS = -1002;

View File

@@ -55,13 +55,13 @@ import com.android.server.job.controllers.JobStatus;
* job lands, and again when it is complete. * job lands, and again when it is complete.
* - Cancelling is trickier, because there are also interactions from the client. It's possible * - Cancelling is trickier, because there are also interactions from the client. It's possible
* the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
* {@link #doCancelLocked(int)} after the client has already finished. This is handled by having * {@link #doCancelLocked} after the client has already finished. This is handled by having
* {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
* the context is still valid. * the context is still valid.
* To mitigate this, we avoid sending duplicate onStopJob() * To mitigate this, we avoid sending duplicate onStopJob()
* calls to the client after they've specified jobFinished(). * calls to the client after they've specified jobFinished().
*/ */
public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection { public final class JobServiceContext implements ServiceConnection {
private static final boolean DEBUG = JobSchedulerService.DEBUG; private static final boolean DEBUG = JobSchedulerService.DEBUG;
private static final String TAG = "JobServiceContext"; private static final String TAG = "JobServiceContext";
/** Amount of time a job is allowed to execute for before being considered timed-out. */ /** Amount of time a job is allowed to execute for before being considered timed-out. */
@@ -112,6 +112,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}. * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
*/ */
private JobStatus mRunningJob; private JobStatus mRunningJob;
private JobCallback mRunningCallback;
/** Used to store next job to run when current job is to be preempted. */ /** Used to store next job to run when current job is to be preempted. */
private int mPreferredUid; private int mPreferredUid;
IJobService service; IJobService service;
@@ -133,6 +134,36 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
// Debugging: time this job was last stopped. // Debugging: time this job was last stopped.
public long mStoppedTime; public long mStoppedTime;
final class JobCallback extends IJobCallback.Stub {
public String mStoppedReason;
public long mStoppedTime;
@Override
public void acknowledgeStartMessage(int jobId, boolean ongoing) {
doAcknowledgeStartMessage(this, jobId, ongoing);
}
@Override
public void acknowledgeStopMessage(int jobId, boolean reschedule) {
doAcknowledgeStopMessage(this, jobId, reschedule);
}
@Override
public JobWorkItem dequeueWork(int jobId) {
return doDequeueWork(this, jobId);
}
@Override
public boolean completeWork(int jobId, int workId) {
return doCompleteWork(this, jobId, workId);
}
@Override
public void jobFinished(int jobId, boolean reschedule) {
doJobFinished(this, jobId, reschedule);
}
}
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
JobPackageTracker tracker, Looper looper) { JobPackageTracker tracker, Looper looper) {
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
@@ -168,6 +199,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
mPreferredUid = NO_PREFERRED_UID; mPreferredUid = NO_PREFERRED_UID;
mRunningJob = job; mRunningJob = job;
mRunningCallback = new JobCallback();
final boolean isDeadlineExpired = final boolean isDeadlineExpired =
job.hasDeadlineConstraint() && job.hasDeadlineConstraint() &&
(job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime()); (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
@@ -182,7 +214,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
job.changedAuthorities.toArray(triggeredAuthorities); job.changedAuthorities.toArray(triggeredAuthorities);
} }
final JobInfo ji = job.getJob(); final JobInfo ji = job.getJob();
mParams = new JobParameters(this, job.getJobId(), ji.getExtras(), mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, triggeredUris, triggeredAuthorities); isDeadlineExpired, triggeredUris, triggeredAuthorities);
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
@@ -198,6 +230,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
} }
mRunningJob = null; mRunningJob = null;
mRunningCallback = null;
mParams = null; mParams = null;
mExecutionStartTimeElapsed = 0L; mExecutionStartTimeElapsed = 0L;
mVerb = VERB_FINISHED; mVerb = VERB_FINISHED;
@@ -263,28 +296,29 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
return false; return false;
} }
@Override void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
public void jobFinished(int jobId, boolean reschedule) { doCallback(cb, reschedule, "app called jobFinished");
doCallback(reschedule, "app called jobFinished");
} }
@Override void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
public void acknowledgeStopMessage(int jobId, boolean reschedule) { doCallback(cb, reschedule, null);
doCallback(reschedule, null);
} }
@Override void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
public void acknowledgeStartMessage(int jobId, boolean ongoing) { doCallback(cb, ongoing, "finished start");
doCallback(ongoing, "finished start");
} }
@Override JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
public JobWorkItem dequeueWork(int jobId) {
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
synchronized (mLock) { synchronized (mLock) {
assertCallingUidLocked(callingUid); assertCallerLocked(cb);
if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
// This job is either all done, or on its way out. Either way, it
// should not dispatch any more work. We will pick up any remaining
// work the next time we start the job again.
return null;
}
final JobWorkItem work = mRunningJob.dequeueWorkLocked(); final JobWorkItem work = mRunningJob.dequeueWorkLocked();
if (work == null && !mRunningJob.hasExecutingWorkLocked()) { if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
// This will finish the job. // This will finish the job.
@@ -297,13 +331,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
} }
} }
@Override boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
public boolean completeWork(int jobId, int workId) {
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
synchronized (mLock) { synchronized (mLock) {
assertCallingUidLocked(callingUid); assertCallerLocked(cb);
return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId); return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
} }
} finally { } finally {
@@ -369,8 +401,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* whether the client exercising the callback is the client we expect. * whether the client exercising the callback is the client we expect.
* @return True if the binder calling is coming from the client we expect. * @return True if the binder calling is coming from the client we expect.
*/ */
private boolean verifyCallingUidLocked(final int callingUid) { private boolean verifyCallerLocked(JobCallback cb) {
if (mRunningJob == null || callingUid != mRunningJob.getUid()) { if (mRunningCallback != cb) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Stale callback received, ignoring."); Slog.d(TAG, "Stale callback received, ignoring.");
} }
@@ -379,16 +411,15 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
return true; return true;
} }
private void assertCallingUidLocked(final int callingUid) { private void assertCallerLocked(JobCallback cb) {
if (!verifyCallingUidLocked(callingUid)) { if (!verifyCallerLocked(cb)) {
StringBuilder sb = new StringBuilder(128); StringBuilder sb = new StringBuilder(128);
sb.append("Bad calling uid "); sb.append("Caller no longer running");
sb.append(callingUid); if (cb.mStoppedReason != null) {
if (mStoppedReason != null) {
sb.append(", last stopped "); sb.append(", last stopped ");
TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStoppedTime, sb); TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
sb.append(" because: "); sb.append(" because: ");
sb.append(mStoppedReason); sb.append(cb.mStoppedReason);
} }
throw new SecurityException(sb.toString()); throw new SecurityException(sb.toString());
} }
@@ -421,12 +452,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
handleServiceBoundLocked(); handleServiceBoundLocked();
} }
void doCallback(boolean reschedule, String reason) { void doCallback(JobCallback cb, boolean reschedule, String reason) {
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
synchronized (mLock) { synchronized (mLock) {
if (!verifyCallingUidLocked(callingUid)) { if (!verifyCallerLocked(cb)) {
return; return;
} }
doCallbackLocked(reschedule, reason); doCallbackLocked(reschedule, reason);
@@ -559,7 +589,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
* {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)} * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
* _STARTING -> Mark as cancelled and wait for * _STARTING -> Mark as cancelled and wait for
* {@link JobServiceContext#acknowledgeStartMessage(int, boolean)} * {@link JobServiceContext#doAcknowledgeStartMessage}
* _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks * _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
* in the message queue. * in the message queue.
* _ENDING -> No point in doing anything here, so we ignore. * _ENDING -> No point in doing anything here, so we ignore.
@@ -671,6 +701,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
mContext.unbindService(JobServiceContext.this); mContext.unbindService(JobServiceContext.this);
mWakeLock = null; mWakeLock = null;
mRunningJob = null; mRunningJob = null;
mRunningCallback = null;
mParams = null; mParams = null;
mVerb = VERB_FINISHED; mVerb = VERB_FINISHED;
mCancelled = false; mCancelled = false;
@@ -684,6 +715,10 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
if (reason != null && mStoppedReason == null) { if (reason != null && mStoppedReason == null) {
mStoppedReason = reason; mStoppedReason = reason;
mStoppedTime = SystemClock.elapsedRealtime(); mStoppedTime = SystemClock.elapsedRealtime();
if (mRunningCallback != null) {
mRunningCallback.mStoppedReason = mStoppedReason;
mRunningCallback.mStoppedTime = mStoppedTime;
}
} }
} }

View File

@@ -66,7 +66,7 @@ import org.xmlpull.v1.XmlSerializer;
* and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that * and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
* object. * object.
*/ */
public class JobStore { public final class JobStore {
private static final String TAG = "JobStore"; private static final String TAG = "JobStore";
private static final boolean DEBUG = JobSchedulerService.DEBUG; private static final boolean DEBUG = JobSchedulerService.DEBUG;
@@ -263,7 +263,7 @@ public class JobStore {
* Runnable that writes {@link #mJobSet} out to xml. * Runnable that writes {@link #mJobSet} out to xml.
* NOTE: This Runnable locks on mLock * NOTE: This Runnable locks on mLock
*/ */
private class WriteJobsMapToDiskRunnable implements Runnable { private final class WriteJobsMapToDiskRunnable implements Runnable {
@Override @Override
public void run() { public void run() {
final long startElapsed = SystemClock.elapsedRealtime(); final long startElapsed = SystemClock.elapsedRealtime();
@@ -444,7 +444,7 @@ public class JobStore {
* Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
* need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}. * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
*/ */
private class ReadJobMapFromDiskRunnable implements Runnable { private final class ReadJobMapFromDiskRunnable implements Runnable {
private final JobSet jobSet; private final JobSet jobSet;
/** /**
@@ -796,7 +796,7 @@ public class JobStore {
} }
} }
static class JobSet { static final class JobSet {
// Key is the getUid() originator of the jobs in each sheaf // Key is the getUid() originator of the jobs in each sheaf
private SparseArray<ArraySet<JobStatus>> mJobs; private SparseArray<ArraySet<JobStatus>> mJobs;

View File

@@ -33,7 +33,7 @@ import java.io.PrintWriter;
* for a certain amount of time (maybe hours or days) are considered idle. When the app comes * for a certain amount of time (maybe hours or days) are considered idle. When the app comes
* out of idle state, it will be allowed to run scheduled jobs. * out of idle state, it will be allowed to run scheduled jobs.
*/ */
public class AppIdleController extends StateController { public final class AppIdleController extends StateController {
private static final String LOG_TAG = "AppIdleController"; private static final String LOG_TAG = "AppIdleController";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@@ -171,7 +171,7 @@ public class AppIdleController extends StateController {
} }
} }
private class AppIdleStateChangeListener private final class AppIdleStateChangeListener
extends UsageStatsManagerInternal.AppIdleStateChangeListener { extends UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override @Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {

View File

@@ -39,7 +39,7 @@ import java.io.PrintWriter;
* be charging when it's been plugged in for more than two minutes, and the system has broadcast * be charging when it's been plugged in for more than two minutes, and the system has broadcast
* ACTION_BATTERY_OK. * ACTION_BATTERY_OK.
*/ */
public class BatteryController extends StateController { public final class BatteryController extends StateController {
private static final String TAG = "JobScheduler.Batt"; private static final String TAG = "JobScheduler.Batt";
private static final Object sCreationLock = new Object(); private static final Object sCreationLock = new Object();
@@ -121,7 +121,7 @@ public class BatteryController extends StateController {
} }
} }
public class ChargingTracker extends BroadcastReceiver { public final class ChargingTracker extends BroadcastReceiver {
/** /**
* Track whether we're "charging", where charging means that we're ready to commit to * Track whether we're "charging", where charging means that we're ready to commit to
* doing work. * doing work.

View File

@@ -43,7 +43,7 @@ import java.io.PrintWriter;
* status due to user-requested network policies, so we need to check * status due to user-requested network policies, so we need to check
* constraints on a per-UID basis. * constraints on a per-UID basis.
*/ */
public class ConnectivityController extends StateController implements public final class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener { ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Conn"; private static final String TAG = "JobScheduler.Conn";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;

View File

@@ -39,7 +39,7 @@ import java.util.ArrayList;
/** /**
* Controller for monitoring changes to content URIs through a ContentObserver. * Controller for monitoring changes to content URIs through a ContentObserver.
*/ */
public class ContentObserverController extends StateController { public final class ContentObserverController extends StateController {
private static final String TAG = "JobScheduler.Content"; private static final String TAG = "JobScheduler.Content";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;

View File

@@ -37,7 +37,7 @@ import java.util.Arrays;
* When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied. * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
* When device is not dozing, set constraint for all jobs as satisfied. * When device is not dozing, set constraint for all jobs as satisfied.
*/ */
public class DeviceIdleJobsController extends StateController { public final class DeviceIdleJobsController extends StateController {
private static final String LOG_TAG = "DeviceIdleJobsController"; private static final String LOG_TAG = "DeviceIdleJobsController";
private static final boolean LOG_DEBUG = false; private static final boolean LOG_DEBUG = false;

View File

@@ -33,7 +33,7 @@ import com.android.server.am.ActivityManagerService;
import com.android.server.job.JobSchedulerService; import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener; import com.android.server.job.StateChangedListener;
public class IdleController extends StateController { public final class IdleController extends StateController {
private static final String TAG = "IdleController"; private static final String TAG = "IdleController";
// Policy: we decide that we're "idle" if the device has been unused / // Policy: we decide that we're "idle" if the device has been unused /
@@ -107,7 +107,7 @@ public class IdleController extends StateController {
mIdleTracker.startTracking(); mIdleTracker.startTracking();
} }
class IdlenessTracker extends BroadcastReceiver { final class IdlenessTracker extends BroadcastReceiver {
private AlarmManager mAlarm; private AlarmManager mAlarm;
private PendingIntent mIdleTriggerIntent; private PendingIntent mIdleTriggerIntent;
boolean mIdle; boolean mIdle;

View File

@@ -35,7 +35,7 @@ import java.io.PrintWriter;
/** /**
* Simple controller that tracks the status of the device's storage. * Simple controller that tracks the status of the device's storage.
*/ */
public class StorageController extends StateController { public final class StorageController extends StateController {
private static final String TAG = "JobScheduler.Stor"; private static final String TAG = "JobScheduler.Stor";
private static final Object sCreationLock = new Object(); private static final Object sCreationLock = new Object();
@@ -112,7 +112,7 @@ public class StorageController extends StateController {
} }
} }
public class StorageTracker extends BroadcastReceiver { public final class StorageTracker extends BroadcastReceiver {
/** /**
* Track whether storage is low. * Track whether storage is low.
*/ */

View File

@@ -38,7 +38,7 @@ import java.util.ListIterator;
* This class sets an alarm for the next expiring job, and determines whether a job's minimum * This class sets an alarm for the next expiring job, and determines whether a job's minimum
* delay has been satisfied. * delay has been satisfied.
*/ */
public class TimeController extends StateController { public final class TimeController extends StateController {
private static final String TAG = "JobScheduler.Time"; private static final String TAG = "JobScheduler.Time";
/** Deadline alarm tag for logging purposes */ /** Deadline alarm tag for logging purposes */