summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dianne Hackborn <hackbod@google.com> 2017-06-09 16:06:01 -0700
committer Dianne Hackborn <hackbod@google.com> 2017-06-09 16:50:00 -0700
commit729a328aca436d71b80f3d72f5d54e38d4d2c12e (patch)
treed2f4e775f9a7cdef62c105dd81576de57d240718
parentf574869c1190d3e836d765ac74630761b1195f68 (diff)
Work on issue #62390590: SecurityException in JobIntentService$...
...JobServiceEngineImpl$WrapperWorkItem.complete Add more useful information in the security exception message that is shown -- the reason the last job that was running on the context was stopped. This should tell you why when you are calling at that point your job is no longer running. Test: bit CtsJobSchedulerTestCases:* Change-Id: Ia7155248b4b4f032cbf8e8754c5437f658ed192c
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java48
-rw-r--r--services/core/java/com/android/server/job/JobServiceContext.java109
2 files changed, 98 insertions, 59 deletions
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index b0d76e854af2..b8fe88439c2d 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -524,7 +524,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for package " + pkgName
+ " in user " + userId);
}
- cancelJobsForUid(pkgUid);
+ cancelJobsForUid(pkgUid, "app package state changed");
}
} catch (RemoteException|IllegalArgumentException e) {
/*
@@ -553,7 +553,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (DEBUG) {
Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
}
- cancelJobsForUid(uidRemoved);
+ cancelJobsForUid(uidRemoved, "app uninstalled");
}
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -611,7 +611,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
@Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
if (disabled) {
- cancelJobsForUid(uid);
+ cancelJobsForUid(uid, "uid gone");
}
}
@@ -620,7 +620,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
@Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
if (disabled) {
- cancelJobsForUid(uid);
+ cancelJobsForUid(uid, "app uid idle");
}
}
};
@@ -689,7 +689,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
jobStatus.prepareLocked(ActivityManager.getService());
if (toCancel != null) {
- cancelJobImplLocked(toCancel, jobStatus);
+ cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
}
if (work != null) {
// If work has been supplied, enqueue it into the new job.
@@ -747,7 +747,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
- cancelJobImplLocked(toRemove, null);
+ cancelJobImplLocked(toRemove, null, "user removed");
}
}
}
@@ -765,7 +765,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
- cancelJobImplLocked(job, null);
+ cancelJobImplLocked(job, null, "app force stopped");
}
}
}
@@ -778,12 +778,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
* @param uid Uid to check against for removal of a job.
*
*/
- public void cancelJobsForUid(int uid) {
+ public void cancelJobsForUid(int uid, String reason) {
synchronized (mLock) {
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
- cancelJobImplLocked(toRemove, null);
+ cancelJobImplLocked(toRemove, null, reason);
}
}
}
@@ -800,12 +800,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
- cancelJobImplLocked(toCancel, null);
+ cancelJobImplLocked(toCancel, null, "cancel() called by app");
}
}
}
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob) {
+ private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked(ActivityManager.getService());
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
@@ -814,7 +814,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
mJobPackageTracker.noteNonpending(cancelled);
}
// Cancel if running.
- stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
+ stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
reportActiveLocked();
}
@@ -844,7 +844,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null
&& (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
- jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE);
+ jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
+ "cancelled due to doze");
}
}
} else {
@@ -1023,12 +1024,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
return removed;
}
- private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
+ private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
final JobStatus executing = jsc.getRunningJobLocked();
if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
- jsc.cancelExecutingJobLocked(reason);
+ jsc.cancelExecutingJobLocked(reason, debugReason);
return true;
}
}
@@ -1270,7 +1271,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
queueReadyJobsForExecutionLocked();
break;
case MSG_STOP_JOB:
- cancelJobImplLocked((JobStatus) message.obj, null);
+ cancelJobImplLocked((JobStatus) message.obj, null,
+ "app no longer allowed to run");
break;
}
maybeRunPendingJobsLocked();
@@ -1286,7 +1288,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
final JobStatus running = serviceContext.getRunningJobLocked();
if (running != null && !running.isReady()) {
serviceContext.cancelExecutingJobLocked(
- JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
+ JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+ "cancelled due to unsatisfied constraints");
}
}
}
@@ -1960,7 +1963,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
long ident = Binder.clearCallingIdentity();
try {
- JobSchedulerService.this.cancelJobsForUid(uid);
+ JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2357,7 +2360,14 @@ public final class JobSchedulerService extends com.android.server.SystemService
pw.print(" Slot #"); pw.print(i); pw.print(": ");
final JobStatus job = jsc.getRunningJobLocked();
if (job == null) {
- pw.println("inactive");
+ if (jsc.mStoppedReason != null) {
+ pw.print("inactive since ");
+ TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw);
+ pw.print(", stopped because: ");
+ pw.println(jsc.mStoppedReason);
+ } else {
+ pw.println("inactive");
+ }
continue;
} else {
pw.println(job.toShortString());
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index ff39baf36c2e..637db11af6d8 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -38,6 +38,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -126,6 +127,12 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
/** Track when job will timeout. */
private long mTimeoutElapsed;
+ // Debugging: reason this job was last stopped.
+ public String mStoppedReason;
+
+ // Debugging: time this job was last stopped.
+ public long mStoppedTime;
+
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
JobPackageTracker tracker, Looper looper) {
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
@@ -204,6 +211,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
}
mJobPackageTracker.noteActive(job);
mAvailable = false;
+ mStoppedReason = null;
+ mStoppedTime = 0;
return true;
}
}
@@ -216,12 +225,12 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
}
/** Called externally when a job that was scheduled for execution should be cancelled. */
- void cancelExecutingJobLocked(int reason) {
- doCancelLocked(reason);
+ void cancelExecutingJobLocked(int reason, String debugReason) {
+ doCancelLocked(reason, debugReason);
}
void preemptExecutingJobLocked() {
- doCancelLocked(JobParameters.REASON_PREEMPT);
+ doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
}
int getPreferredUid() {
@@ -247,7 +256,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
&& (!matchJobId || jobId == executing.getJobId())) {
if (mVerb == VERB_EXECUTING) {
mParams.setStopReason(JobParameters.REASON_TIMEOUT);
- sendStopMessageLocked();
+ sendStopMessageLocked("force timeout from shell");
return true;
}
}
@@ -256,17 +265,17 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
@Override
public void jobFinished(int jobId, boolean reschedule) {
- doCallback(reschedule);
+ doCallback(reschedule, "app called jobFinished");
}
@Override
public void acknowledgeStopMessage(int jobId, boolean reschedule) {
- doCallback(reschedule);
+ doCallback(reschedule, null);
}
@Override
public void acknowledgeStartMessage(int jobId, boolean ongoing) {
- doCallback(ongoing);
+ doCallback(ongoing, "finished start");
}
@Override
@@ -275,14 +284,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (!verifyCallingUidLocked(callingUid)) {
- throw new SecurityException("Bad calling uid: " + callingUid);
- }
-
+ assertCallingUidLocked(callingUid);
final JobWorkItem work = mRunningJob.dequeueWorkLocked();
if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
// This will finish the job.
- doCallbackLocked(false);
+ doCallbackLocked(false, "last work dequeued");
}
return work;
}
@@ -297,9 +303,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- if (!verifyCallingUidLocked(callingUid)) {
- throw new SecurityException("Bad calling uid: " + callingUid);
- }
+ assertCallingUidLocked(callingUid);
return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
}
} finally {
@@ -324,7 +328,8 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
runningJob = mRunningJob;
if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
- closeAndCleanupJobLocked(true /* needsReschedule */);
+ closeAndCleanupJobLocked(true /* needsReschedule */,
+ "connected for different component");
return;
}
this.service = IJobService.Stub.asInterface(service);
@@ -355,7 +360,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
- closeAndCleanupJobLocked(true /* needsReschedule */);
+ closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
}
}
@@ -374,6 +379,21 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
return true;
}
+ private void assertCallingUidLocked(final int callingUid) {
+ if (!verifyCallingUidLocked(callingUid)) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Bad calling uid ");
+ sb.append(callingUid);
+ if (mStoppedReason != null) {
+ sb.append(", last stopped ");
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mStoppedTime, sb);
+ sb.append(" because: ");
+ sb.append(mStoppedReason);
+ }
+ throw new SecurityException(sb.toString());
+ }
+ }
+
/**
* Scheduling of async messages (basically timeouts at this point).
*/
@@ -401,7 +421,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
handleServiceBoundLocked();
}
- void doCallback(boolean reschedule) {
+ void doCallback(boolean reschedule, String reason) {
final int callingUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
@@ -409,14 +429,14 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
if (!verifyCallingUidLocked(callingUid)) {
return;
}
- doCallbackLocked(reschedule);
+ doCallbackLocked(reschedule, reason);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- void doCallbackLocked(boolean reschedule) {
+ void doCallbackLocked(boolean reschedule, String reason) {
if (DEBUG) {
Slog.d(TAG, "doCallback of : " + mRunningJob
+ " v:" + VERB_STRINGS[mVerb]);
@@ -427,7 +447,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
handleStartedLocked(reschedule);
} else if (mVerb == VERB_EXECUTING ||
mVerb == VERB_STOPPING) {
- handleFinishedLocked(reschedule);
+ handleFinishedLocked(reschedule, reason);
} else {
if (DEBUG) {
Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
@@ -435,7 +455,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
}
}
- void doCancelLocked(int arg1) {
+ void doCancelLocked(int arg1, String debugReason) {
if (mVerb == VERB_FINISHED) {
if (DEBUG) {
Slog.d(TAG,
@@ -448,7 +468,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
NO_PREFERRED_UID;
}
- handleCancelLocked();
+ handleCancelLocked(debugReason);
}
/** Start the job on the service. */
@@ -459,7 +479,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
if (mVerb != VERB_BINDING) {
Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
+ VERB_STRINGS[mVerb]);
- closeAndCleanupJobLocked(false /* reschedule */);
+ closeAndCleanupJobLocked(false /* reschedule */, "started job not pending");
return;
}
if (mCancelled) {
@@ -467,7 +487,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
+ mRunningJob);
}
- closeAndCleanupJobLocked(true /* reschedule */);
+ closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind");
return;
}
try {
@@ -496,7 +516,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
mVerb = VERB_EXECUTING;
if (!workOngoing) {
// Job is finished already so fast-forward to handleFinished.
- handleFinishedLocked(false);
+ handleFinishedLocked(false, "onStartJob returned false");
return;
}
if (mCancelled) {
@@ -504,7 +524,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
}
// Cancelled *while* waiting for acknowledgeStartMessage from client.
- handleCancelLocked();
+ handleCancelLocked(null);
return;
}
scheduleOpTimeOutLocked();
@@ -522,11 +542,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* _STARTING -> Error
* _PENDING -> Error
*/
- private void handleFinishedLocked(boolean reschedule) {
+ private void handleFinishedLocked(boolean reschedule, String reason) {
switch (mVerb) {
case VERB_EXECUTING:
case VERB_STOPPING:
- closeAndCleanupJobLocked(reschedule);
+ closeAndCleanupJobLocked(reschedule, reason);
break;
default:
Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
@@ -544,7 +564,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* in the message queue.
* _ENDING -> No point in doing anything here, so we ignore.
*/
- private void handleCancelLocked() {
+ private void handleCancelLocked(String reason) {
if (JobSchedulerService.DEBUG) {
Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
+ VERB_STRINGS[mVerb]);
@@ -553,9 +573,10 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
case VERB_BINDING:
case VERB_STARTING:
mCancelled = true;
+ applyStoppedReasonLocked(reason);
break;
case VERB_EXECUTING:
- sendStopMessageLocked();
+ sendStopMessageLocked(reason);
break;
case VERB_STOPPING:
// Nada.
@@ -572,7 +593,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
case VERB_BINDING:
Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
", dropping.");
- closeAndCleanupJobLocked(false /* needsReschedule */);
+ closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
break;
case VERB_STARTING:
// Client unresponsive - wedged or failed to respond in time. We don't really
@@ -580,25 +601,25 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
// FINISHED/NO-RETRY.
Slog.e(TAG, "No response from client for onStartJob '" +
mRunningJob.toShortString());
- closeAndCleanupJobLocked(false /* needsReschedule */);
+ closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
break;
case VERB_STOPPING:
// At least we got somewhere, so fail but ask the JobScheduler to reschedule.
Slog.e(TAG, "No response from client for onStopJob, '" +
mRunningJob.toShortString());
- closeAndCleanupJobLocked(true /* needsReschedule */);
+ closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
break;
case VERB_EXECUTING:
// Not an error - client ran out of time.
Slog.i(TAG, "Client timed out while executing (no jobFinished received)." +
" sending onStop. " + mRunningJob.toShortString());
mParams.setStopReason(JobParameters.REASON_TIMEOUT);
- sendStopMessageLocked();
+ sendStopMessageLocked("timeout while executing");
break;
default:
Slog.e(TAG, "Handling timeout for an invalid job state: " +
mRunningJob.toShortString() + ", dropping.");
- closeAndCleanupJobLocked(false /* needsReschedule */);
+ closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
}
}
@@ -606,11 +627,11 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
* VERB_STOPPING.
*/
- private void sendStopMessageLocked() {
+ private void sendStopMessageLocked(String reason) {
removeOpTimeOutLocked();
if (mVerb != VERB_EXECUTING) {
Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
- closeAndCleanupJobLocked(false /* reschedule */);
+ closeAndCleanupJobLocked(false /* reschedule */, reason);
return;
}
try {
@@ -620,7 +641,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
} catch (RemoteException e) {
Slog.e(TAG, "Error sending onStopJob to client.", e);
// The job's host app apparently crashed during the job, so we should reschedule.
- closeAndCleanupJobLocked(true /* reschedule */);
+ closeAndCleanupJobLocked(true /* reschedule */, "host crashed when trying to stop");
}
}
@@ -630,11 +651,12 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
* or from acknowledging the stop message we sent. Either way, we're done tracking it and
* we want to clean up internally.
*/
- private void closeAndCleanupJobLocked(boolean reschedule) {
+ private void closeAndCleanupJobLocked(boolean reschedule, String reason) {
final JobStatus completedJob;
if (mVerb == VERB_FINISHED) {
return;
}
+ applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
mJobPackageTracker.noteInactive(completedJob);
try {
@@ -658,6 +680,13 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
}
+ private void applyStoppedReasonLocked(String reason) {
+ if (reason != null && mStoppedReason == null) {
+ mStoppedReason = reason;
+ mStoppedTime = SystemClock.elapsedRealtime();
+ }
+ }
+
/**
* Called when sending a message to the client, over whose execution we have no control. If
* we haven't received a response in a certain amount of time, we want to give up and carry