CVE-2026-11290
Overview
Files Changed
android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.javaandroid_webview/nonembedded/java/src/org/chromium/android_webview/services/AwNetLogService.java
Patch
From 2f6cc7dfa765126ffbcbeba1057713734521ed7e Mon Sep 17 00:00:00 2001 From: Nate Fischer <[email protected]> Date: Tue, 14 Apr 2026 08:31:41 -0700 Subject: [PATCH] AW: handle integer overflow for timestamps This fixes a type-casting bug in the compare() function. This uses `Long.compare()` to avoid any risk of integer overflow. I couldn't actually reproduce a case which triggers a comparison error in the sorting function (this would require writing 1 GB to disk to trigger the extra clean up logic), but I've added a test case to offer some minimal coverage around integer overflow. This also adds documentation for isCorrectPackage() and cleanUpNetLogDirectory(). Bug: 502264647 Test: run_webview_instrumentation_test_apk -f AwNetLogServiceTest.* Change-Id: I8c9e9a929aacef9c0767aa36716b56602e97ad7e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7759260 Commit-Queue: Richard Coles <[email protected]> Auto-Submit: Nate Fischer <[email protected]> Reviewed-by: Richard Coles <[email protected]> Cr-Commit-Position: refs/heads/main@{#1614482} --- diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java index e5b9156..b85510a 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java @@ -29,6 +29,8 @@ import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; /** * Instrumentation tests AwNetLogServiceTest. These tests are not batched to make sure all unbinded @@ -145,4 +147,40 @@ Assert.assertEquals(currentTime, fileTime); } + + private void createNetLogWithTimestamp(long timestamp) throws Throwable { + Intent intent = new Intent(ContextUtils.getApplicationContext(), AwNetLogService.class); + try (ServiceConnectionHelper helper = + new ServiceConnectionHelper(intent, Context.BIND_AUTO_CREATE)) { + INetLogService service = INetLogService.Stub.asInterface(helper.getBinder()); + ParcelFileDescriptor parcelFileDescriptor = service.streamLog(timestamp, PACKAGE_NAME); + Assert.assertTrue( + "Received an invalid file descriptor for net log with timestamp " + timestamp, + parcelFileDescriptor.getFileDescriptor().valid()); + + parcelFileDescriptor.close(); + } + } + + @Test + @MediumTest + @CommandLineFlags.Add(AwSwitches.NET_LOG) + public void testTimestampsIntegerOverflow() throws Throwable { + File directory = AwNetLogService.getNetLogFileDirectory(); + Assert.assertEquals(0, directory.listFiles().length); + final long timestamp1 = System.currentTimeMillis(); + final long timestamp2 = timestamp1 - TimeUnit.DAYS.toMillis(30); + final long timestamp3 = timestamp1 - TimeUnit.DAYS.toMillis(60); + + createNetLogWithTimestamp(timestamp1); + createNetLogWithTimestamp(timestamp2); + createNetLogWithTimestamp(timestamp3); + + AwNetLogService.cleanUpNetLogDirectory(); + Assert.assertEquals( + "Expected only one unexpired net log but the directory has wrong number of files: " + + Arrays.toString(directory.listFiles()), + 1, + directory.listFiles().length); + } } diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/services/AwNetLogService.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/services/AwNetLogService.java index b2c9855c..ae42af33 100644 --- a/android_webview/nonembedded/java/src/org/chromium/android_webview/services/AwNetLogService.java +++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/services/AwNetLogService.java @@ -76,6 +76,16 @@ return NET_LOG_DIR; } + /** + * This method asserts that the calling app really is {@code packageName}. We cannot directly + * determine what the app's package name is which is why we ask apps to provide their own + * package name in the calling parameters, however we can instead assert that the calling app is + * at least part of a shared UID group with {@code packageName}. + * + * <p>Shared UIDs are relatively rare: typically a UID group will only have a single package + * name. But even in the case where it has multiple package names, there's no security issue if + * one app uses another app's package name from the same UID group. + */ private boolean isCorrectPackage(String packageName) { int binderUid = Binder.getCallingUid(); try { @@ -92,6 +102,11 @@ return false; } + /** + * Cleans up stale net log files from over 30 days ago. If the remaining non-stale netlogs are + * still taking up space exceeding MAX_TOTAL_CAPACITY, then this will delete non-stale net log + * files until we are back under storage capacity. + */ public static void cleanUpNetLogDirectory() { // Date thirty days ago long expirationDate = System.currentTimeMillis() - (1000L * 60 * 60 * 24 * 30); @@ -125,8 +140,7 @@ public int compare(File fileOne, File fileTwo) { long firstFileTime = getCreationTimeFromFileName(fileOne.getName()); long secondFileTime = getCreationTimeFromFileName(fileTwo.getName()); - long diff = firstFileTime - secondFileTime; - return (int) diff; + return Long.compare(firstFileTime, secondFileTime); } }); int index = 0;
Regression Test / PoC
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java
index e5b9156..b85510a 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/AwNetLogServiceTest.java
@@ -29,6 +29,8 @@
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
/**
* Instrumentation tests AwNetLogServiceTest. These tests are not batched to make sure all unbinded
@@ -145,4 +147,40 @@
Assert.assertEquals(currentTime, fileTime);
}
+
+ private void createNetLogWithTimestamp(long timestamp) throws Throwable {
+ Intent intent = new Intent(ContextUtils.getApplicationContext(), AwNetLogService.class);
+ try (ServiceConnectionHelper helper =
+ new ServiceConnectionHelper(intent, Context.BIND_AUTO_CREATE)) {
+ INetLogService service = INetLogService.Stub.asInterface(helper.getBinder());
+ ParcelFileDescriptor parcelFileDescriptor = service.streamLog(timestamp, PACKAGE_NAME);
+ Assert.assertTrue(
+ "Received an invalid file descriptor for net log with timestamp " + timestamp,
+ parcelFileDescriptor.getFileDescriptor().valid());
+
+ parcelFileDescriptor.close();
+ }
+ }
+
+ @Test
+ @MediumTest
+ @CommandLineFlags.Add(AwSwitches.NET_LOG)
+ public void testTimestampsIntegerOverflow() throws Throwable {
+ File directory = AwNetLogService.getNetLogFileDirectory();
+ Assert.assertEquals(0, directory.listFiles().length);
+ final long timestamp1 = System.currentTimeMillis();
+ final long timestamp2 = timestamp1 - TimeUnit.DAYS.toMillis(30);
+ final long timestamp3 = timestamp1 - TimeUnit.DAYS.toMillis(60);
+
+ createNetLogWithTimestamp(timestamp1);
+ createNetLogWithTimestamp(timestamp2);
+ createNetLogWithTimestamp(timestamp3);
+
+ AwNetLogService.cleanUpNetLogDirectory();
+ Assert.assertEquals(
+ "Expected only one unexpired net log but the directory has wrong number of files: "
+ + Arrays.toString(directory.listFiles()),
+ 1,
+ directory.listFiles().length);
+ }
}
Original Bug Report
Potential Missing Authorization and Integer Truncation in AwNetLogService
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: AwNetLogService lacks proper authorization, allowing any unprivileged Android app to write files to the WebView provider’s private directory. Furthermore, an integer truncation bug in the log cleanup logic can be exploited to persistently crash the webview_service process.
Affected files:
android_webview/nonembedded/java/src/org/chromium/android_webview/services/AwNetLogService.javaandroid_webview/nonembedded/java/AndroidManifest.xml
Estimated timestamp from git blame: 2025-10-08
Overview
Two potential vulnerabilities exist in org.chromium.android_webview.services.AwNetLogService, a newly introduced service for streaming network logs in WebView. Together, they allow an unprivileged malicious application to write files into the WebView provider’s private data directory and trigger a persistent Denial of Service (DoS) by crashing the :webview_service process.
1. Missing Authorization
AwNetLogService is declared in AndroidManifest.xml as an exported service without any <permission> restrictions. Its streamLog(long creationTime, String packageName) method attempts to authorize callers via the isCorrectPackage method:
private boolean isCorrectPackage(String packageName) {
int binderUid = Binder.getCallingUid();
// ... fetches applicationUid for packageName ...
if (applicationUid == binderUid) {
return true;
}
return false;
}
This logic only verifies that the caller has provided their own package name. It completely fails to verify whether the caller is actually authorized to use the developer-only NetLog functionality. Consequently, any application on the device can bind to the service, call streamLog, and obtain a write-only ParcelFileDescriptor pointing to the WebView provider’s private aw_net_logs directory.
2. Integer Truncation in Storage Reclamation
To limit disk usage, AwNetLogService includes a cleanUpNetLogDirectory() method that deletes older logs when the directory exceeds 1 GB. It sorts files using a custom Comparator inside reclaimStorageSpace():
long diff = firstFileTime - secondFileTime;
return (int) diff;
Because the timestamps are in milliseconds, a difference greater than Integer.MAX_VALUE (approximately 24.8 days) will truncate when cast to an int, causing an integer overflow that flips the sign of the result.
If an attacker crafts three log files with specific future timestamps that exploit this truncation, they can create a state where compare(A, B) < 0 and compare(B, C) < 0, but compare(A, C) >= 0. This violates the strict transitivity contract of Java’s Arrays.sort() (Timsort), causing it to throw an unhandled IllegalArgumentException: Comparison method violates its general contract!.
Impact
This unhandled exception persistently crashes the :webview_service background process. Because the malicious files remain in the aw_net_logs directory, the crash loop repeats every time AwNetLogService or the WebView DevTools UI attempts to process the directory. This disables critical WebView background features running in the same process, such as Finch variations, metrics/crash uploading, and Safe Browsing component updates, until the user manually clears the WebView provider’s app data.
Potential Attacker Steps
(Note: These are suggested steps based on code analysis; our tooling agent does not run code to provide a working PoC.)
- An attacker creates a malicious, unprivileged Android application.
- The app binds to
org.chromium.android_webview.services.AwNetLogService. - The app invokes
streamLog(creationTime, packageName)using its own package name and heavily padded future timestamps (e.g.,T,T - (Integer.MAX_VALUE + 1), andT - 2 * (Integer.MAX_VALUE + 1)). - The app writes garbage data into the returned
ParcelFileDescriptors until the 1 GBMAX_TOTAL_CAPACITYthreshold is reached. - The next time
streamLogis invoked,cleanUpNetLogDirectory()triggersArrays.sort(), throws theIllegalArgumentException, and crashes the:webview_serviceprocess.
Suggested Fix
- Authorization: Implement strict UID/Package checks similar to
DeveloperUiService, requiring the caller to share the same UID as the WebView provider (Binder.getCallingUid() == Process.myUid()), or enforce a specific developer permission. - Truncation: Replace the unsafe integer cast in the
ComparatorwithLong.compare():return Long.compare(firstFileTime, secondFileTime);
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.