81341707ff7198f42529a62bdc5fbb034795934a IDB: reject invalid last_modified value from renderer
Triage note: Browser-process validation of renderer-supplied IPC data crossing the privilege boundary (content/browser/indexed_db), fixing a re-triggered ClusterFuzz case. Diff shows bad-state/DoS, not confirmed corruption. watch.
The bug at a glance
A browser-process trust-boundary bug: the IndexedDB backend in content/browser accepted a renderer-supplied File last_modified timestamp with no lower-bound validation, admitting timestamps before the Windows epoch (a negative TimeDelta since 1601-01-01). ClusterFuzz (474202571) found a testcase that broke a downstream invariant. Reachability is favorable to an attacker: File.lastModified is directly settable from unprivileged web JavaScript via the File constructor, so a plain page can push a negative timestamp into an IDB store; a compromised renderer can send it over Mojo just as easily. The realistic impact is bounded, however: the offending value flows into IndexedDBExternalObject metadata and downstream serialization/CHECK logic, yielding a browser-process crash or invalid-state abort rather than a demonstrated corruption/RCE primitive. That is a browser-process DoS across a security boundary from web content, hence medium. CVSS 6.5 reflects network/web reachability, low complexity, no privileges, browser-process availability impact, with no confirmed C/I impact.
last_modified is attacker-controlled data that the browser previously treated as a trusted, well-formed timestamp. base::Time is stored as microseconds since the Windows epoch (1601-01-01); a File whose lastModified is far enough negative (e.g. a JS millisecond value below the 1601 boundary) produces a base::Time whose ToDeltaSinceWindowsEpoch() is negative. Somewhere downstream (backing-store metadata write, serialization, or a DCHECK/CHECK assuming non-negative time) that invariant was relied upon, and the fuzzer walked straight into it because the renderer, not the browser, chose the value.
Root cause
In Transaction’s blob/file bookkeeping loop (transaction.cc ~line 797), for each info entry that carries a file the code builds an IndexedDBExternalObject from renderer-supplied fields including info->file->last_modified. The patch inserts a guard immediately before that construction: if info->file->last_modified.ToDeltaSinceWindowsEpoch() is less than base::TimeDelta() (i.e. negative, meaning a time before the Windows epoch), the function returns false, aborting the operation before the bad timestamp is admitted into the external-object list. The fix is purely additive rejection at the entry point where untrusted file metadata is first materialized into a browser-side object; it neither sanitizes/clamps the value nor reports a bad-message kill, it just refuses the invalid input.
Key code
Reject pre-Windows-epoch last_modified before constructing IndexedDBExternalObject
total_blob_size += info->size;
if (info->file) {
+ if (info->file->last_modified.ToDeltaSinceWindowsEpoch() <
+ base::TimeDelta()) {
+ return false;
+ }
(*external_objects)[i] = IndexedDBExternalObject(
std::move(info->blob), info->file->name, info->mime_type,
info->file->last_modified, info->size);
Patch walkthrough
content/browser/indexed_db/instance/transaction.cc— Inside the loop that converts renderer-provided blob/file info into browser-side IndexedDBExternalObject entries, a bounds check is added on the file’s last_modified. If the timestamp predates the Windows epoch (negative delta), the enclosing function returns false and the entry is never constructed. This validates untrusted IPC-origin data at the point it crosses into browser-owned data structures, closing the ClusterFuzz-found path where a negative timestamp propagated further into IDB storage.
Background
IndexedDB browser backend — content/browser/indexed_db implements the privileged half of IDB. It receives put/get and blob/file operations from renderers over Mojo and must treat all incoming field values as untrusted, because a renderer may be compromised or scripted.
IndexedDBExternalObject — The browser-side representation of a Blob or File stored in IndexedDB, carrying the blob handle, name, mime type, size, and last_modified time. It is later serialized into the backing store, so an invalid field can corrupt or crash metadata handling.
base::Time / ToDeltaSinceWindowsEpoch — Chromium represents wall-clock time relative to the Windows epoch (1601-01-01). ToDeltaSinceWindowsEpoch() returns the offset; a value less than base::TimeDelta() means a time before 1601, which is nonsensical for a real file and violates assumptions in code that expects non-negative deltas.
File.lastModified (web reachability) — In web JS, new File([…], name, {lastModified: n}) accepts an arbitrary numeric lastModified, including large negative values that map to pre-1601 times. Storing such a File into IndexedDB is the direct, unprivileged route to deliver the malformed value to the browser process.
ClusterFuzz & the revert dance — Testcase 474202571 was auto-marked fixed when the CL that introduced the exposure was temporarily reverted; when that CL re-landed the testcase regressed, prompting this dedicated validation fix. This is why the tracker state and the real fix diverged.
Vulnerability window
- Introduction/Exposure — A CL in the IDB path made the negative-timestamp value reachable in IndexedDBExternalObject construction; ClusterFuzz filed 474202571 on the resulting failure.
- False fix — The offending CL was temporarily reverted, which auto-closed the testcase without addressing the input-validation gap.
- Regression & real fix — After the CL re-landed, the testcase failed again. On 2026-01-14 Evan Stade landed CL 7464662 (r1569170) adding the explicit last_modified lower-bound rejection.
Proof of concept
No test or reproducer shipped in this diff (only the .cc guard). No exploit is demonstrated; the ClusterFuzz artifact is not included here. Honest recipe (web-reachable): create a File with a pre-1601 timestamp and store it in IndexedDB, e.g. const f = new File([‘x’], ‘f’, {lastModified: -20000000000000}); then in an IDB transaction objectStore.put(f, ‘k’). Pre-patch, the browser accepted the negative last_modified into IndexedDBExternalObject and the invalid delta propagated into downstream storage/serialization, matching the fuzzer’s failure. Post-patch the transaction operation is refused (return false) and the object is never built. This describes the trigger only; it is a reliability/DoS class issue, not a demonstrated corruption exploit.
Exploitation
- Deliver — Unprivileged JavaScript constructs a File with a negative lastModified (or a compromised renderer forges the Mojo message) and issues an IndexedDB write carrying that file.
- Cross boundary — The browser-process transaction code reads info->file->last_modified without lower-bound checks and (pre-patch) constructs IndexedDBExternalObject with the negative timestamp.
- Impact — The invalid delta violates a downstream time invariant (serialization/assertion), producing a browser-process crash or aborted operation. No attacker-controlled write primitive is evidenced; impact is availability/robustness of the browser process.
Detection & hunting
For defenders and detection engineers:
- Negative-time invariant failures — Look for browser-process crashes/DCHECKs in IndexedDB serialization or metadata handling keyed on base::Time from external objects; correlate with ClusterFuzz 474202571.
- Anomalous file metadata in IDB — Monitor for stored File records with last_modified before the Windows epoch, which indicates either the pre-patch path or a client deliberately probing it.
Audit directions
- Renderer-supplied base::Time across Mojo — Audit other content/browser sites that accept a base::Time / TimeDelta from renderers (blob/file metadata, cache, storage, downloads) without validating range/sign; the same missing-lower-bound pattern likely recurs.
- Other IndexedDBExternalObject fields — Check name, mime_type, and size for equivalent trust assumptions; size feeds total_blob_size accumulation nearby and warrants overflow/limit review.
- File metadata ingestion generally — Review File/Blob metadata handling in FileSystemAccess, uploads, and structured clone for propagation of adversarial lastModified values to browser-side time consumers.