Prevent apps from setting negative estimated bytes.

Bug: 253665015
Test: atest CtsJobSchedulerTestCases:JobInfoTest
Test: atest CtsJobSchedulerTestCases:JobWorkItemTest
Change-Id: I4f8185b4f6abbe872d8944448de629ed0d903634
This commit is contained in:
Kweku Adams
2022-10-24 15:58:59 +00:00
parent 9bdbc5786d
commit 3d5d56f871
5 changed files with 49 additions and 25 deletions

View File

@@ -32,6 +32,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
@@ -97,6 +98,15 @@ public class JobInfo implements Parcelable {
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long THROW_ON_INVALID_PRIORITY_VALUE = 140852299L;
/**
* Require that estimated network bytes are nonnegative.
*
* @hide
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long REJECT_NEGATIVE_NETWORK_ESTIMATES = 253665015L;
/** @hide */
@IntDef(prefix = { "NETWORK_TYPE_" }, value = {
NETWORK_TYPE_NONE,
@@ -1890,11 +1900,13 @@ public class JobInfo implements Parcelable {
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS));
return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS),
Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES));
}
/** @hide */
public JobInfo build(boolean disallowPrefetchDeadlines) {
public JobInfo build(boolean disallowPrefetchDeadlines,
boolean rejectNegativeNetworkEstimates) {
// This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
// check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -1903,7 +1915,7 @@ public class JobInfo implements Parcelable {
" setRequiresDeviceIdle is an error.");
}
JobInfo jobInfo = new JobInfo(this);
jobInfo.enforceValidity(disallowPrefetchDeadlines);
jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates);
return jobInfo;
}
@@ -1921,13 +1933,24 @@ public class JobInfo implements Parcelable {
/**
* @hide
*/
public final void enforceValidity(boolean disallowPrefetchDeadlines) {
public final void enforceValidity(boolean disallowPrefetchDeadlines,
boolean rejectNegativeNetworkEstimates) {
// Check that network estimates require network type and are reasonable values.
if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
&& networkRequest == null) {
throw new IllegalArgumentException(
"Can't provide estimated network usage without requiring a network");
}
if (networkRequest != null && rejectNegativeNetworkEstimates) {
if (networkUploadBytes != NETWORK_BYTES_UNKNOWN && networkUploadBytes < 0) {
throw new IllegalArgumentException(
"Invalid network upload bytes: " + networkUploadBytes);
}
if (networkDownloadBytes != NETWORK_BYTES_UNKNOWN && networkDownloadBytes < 0) {
throw new IllegalArgumentException(
"Invalid network download bytes: " + networkDownloadBytes);
}
}
final long estimatedTransfer;
if (networkUploadBytes == NETWORK_BYTES_UNKNOWN) {
estimatedTransfer = networkDownloadBytes;

View File

@@ -20,6 +20,7 @@ import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
import android.annotation.BytesLong;
import android.annotation.Nullable;
import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Build;
@@ -88,25 +89,11 @@ final public class JobWorkItem implements Parcelable {
*/
public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes,
@BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) {
if (minimumChunkBytes != NETWORK_BYTES_UNKNOWN && minimumChunkBytes <= 0) {
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
final long estimatedTransfer;
if (uploadBytes == NETWORK_BYTES_UNKNOWN) {
estimatedTransfer = downloadBytes;
} else {
estimatedTransfer = uploadBytes
+ (downloadBytes == NETWORK_BYTES_UNKNOWN ? 0 : downloadBytes);
}
if (minimumChunkBytes != NETWORK_BYTES_UNKNOWN && estimatedTransfer != NETWORK_BYTES_UNKNOWN
&& minimumChunkBytes > estimatedTransfer) {
throw new IllegalArgumentException(
"Minimum chunk size can't be greater than estimated network usage");
}
mIntent = intent;
mNetworkDownloadBytes = downloadBytes;
mNetworkUploadBytes = uploadBytes;
mMinimumChunkBytes = minimumChunkBytes;
enforceValidity(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
}
/**
@@ -222,7 +209,17 @@ final public class JobWorkItem implements Parcelable {
/**
* @hide
*/
public void enforceValidity() {
public void enforceValidity(boolean rejectNegativeNetworkEstimates) {
if (rejectNegativeNetworkEstimates) {
if (mNetworkUploadBytes != NETWORK_BYTES_UNKNOWN && mNetworkUploadBytes < 0) {
throw new IllegalArgumentException(
"Invalid network upload bytes: " + mNetworkUploadBytes);
}
if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN && mNetworkDownloadBytes < 0) {
throw new IllegalArgumentException(
"Invalid network download bytes: " + mNetworkDownloadBytes);
}
}
final long estimatedTransfer;
if (mNetworkUploadBytes == NETWORK_BYTES_UNKNOWN) {
estimatedTransfer = mNetworkDownloadBytes;

View File

@@ -3163,9 +3163,12 @@ public class JobSchedulerService extends com.android.server.SystemService
}
private void validateJob(JobInfo job, int callingUid, @Nullable JobWorkItem jobWorkItem) {
final boolean rejectNegativeNetworkEstimates = CompatChanges.isChangeEnabled(
JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES, callingUid);
job.enforceValidity(
CompatChanges.isChangeEnabled(
JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid));
JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid),
rejectNegativeNetworkEstimates);
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
@@ -3180,7 +3183,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
if (jobWorkItem != null) {
jobWorkItem.enforceValidity();
jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
if (jobWorkItem.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN
|| jobWorkItem.getEstimatedNetworkUploadBytes()
!= JobInfo.NETWORK_BYTES_UNKNOWN

View File

@@ -1024,7 +1024,8 @@ public final class JobStore {
// have a deadline. If a job is rescheduled (via jobFinished(true) or onStopJob()'s
// return value), the deadline is dropped. Periodic jobs require all constraints
// to be met, so there's no issue with their deadlines.
builtJob = jobBuilder.build(false);
// The same logic applies for other target SDK-based validation checks.
builtJob = jobBuilder.build(false, false);
} catch (Exception e) {
Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
return null;

View File

@@ -612,9 +612,9 @@ public final class JobStatus {
requestBuilder.setUids(
Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
builder.setRequiredNetwork(requestBuilder.build());
// Don't perform prefetch-deadline check at this point. We've already passed the
// Don't perform validation checks at this point since we've already passed the
// initial validation check.
job = builder.build(false);
job = builder.build(false, false);
}
updateMediaBackupExemptionStatus();