Skip to content

Commit

Permalink
[sandbox] Add regression test for crbug.com/379774687
Browse files Browse the repository at this point in the history
This is a follow-up for https://crrev.com/c/6035110 that adds a
regression test that is able to trigger issues due to concurrent
modifications of JS source code while it is being parsed. The testcase
is effectively a mini-fuzzer for these issues and requires --random-seed
to trigger the issue reliably. This way the test case is fairly robust
and doesn't rely on a specific heap layout, and it is easily picked up
by "real" fuzzers for further mutations.

Bug: 379774687
Change-Id: I95d1991c997354a8afc5c299475862aa68fa577d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6048825
Commit-Queue: Samuel Groß <[email protected]>
Reviewed-by: Toon Verwaest <[email protected]>
Cr-Commit-Position: refs/heads/main@{#97449}
  • Loading branch information
Samuel Groß authored and V8 LUCI CQ committed Nov 27, 2024
1 parent 2a37e31 commit 954dbf0
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/objects/name.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ V8_OBJECT class Name : public PrimitiveHeapObject {
friend class V8HeapExplorer;
friend class CodeStubAssembler;
friend class StringBuiltinsAssembler;
friend class SandboxTesting;
friend class maglev::MaglevAssembler;
friend class compiler::AccessBuilder;
friend class compiler::WasmGraphBuilder;
Expand Down
5 changes: 5 additions & 0 deletions src/sandbox/testing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ SandboxTesting::InstanceTypeMap& SandboxTesting::GetInstanceTypeMap() {
types["JS_FUNCTION_TYPE"] = JS_FUNCTION_TYPE;
types["JS_ARRAY_TYPE"] = JS_ARRAY_TYPE;
types["SEQ_ONE_BYTE_STRING_TYPE"] = SEQ_ONE_BYTE_STRING_TYPE;
types["SEQ_TWO_BYTE_STRING_TYPE"] = SEQ_TWO_BYTE_STRING_TYPE;
types["INTERNALIZED_ONE_BYTE_STRING_TYPE"] =
INTERNALIZED_ONE_BYTE_STRING_TYPE;
types["SLICED_ONE_BYTE_STRING_TYPE"] = SLICED_ONE_BYTE_STRING_TYPE;
Expand Down Expand Up @@ -837,6 +838,10 @@ SandboxTesting::FieldOffsetMap& SandboxTesting::GetFieldOffsetMap() {
fields[JS_ARRAY_TYPE]["length"] = JSArray::kLengthOffset;
fields[SEQ_ONE_BYTE_STRING_TYPE]["length"] =
offsetof(SeqOneByteString, length_);
fields[SEQ_TWO_BYTE_STRING_TYPE]["hash"] =
offsetof(SeqTwoByteString, raw_hash_field_);
fields[SEQ_TWO_BYTE_STRING_TYPE]["length"] =
offsetof(SeqTwoByteString, length_);
fields[INTERNALIZED_ONE_BYTE_STRING_TYPE]["length"] =
offsetof(InternalizedString, length_);
fields[SLICED_ONE_BYTE_STRING_TYPE]["parent"] =
Expand Down
93 changes: 93 additions & 0 deletions test/mjsunit/sandbox/regress/regress-379774687.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --sandbox-testing --expose-gc

const kSeqStringType = Sandbox.getInstanceTypeIdFor("SEQ_TWO_BYTE_STRING_TYPE");
const kStringHashOffset = Sandbox.getFieldOffset(kSeqStringType, "hash");

let memory = new DataView(new Sandbox.MemoryView(0, 0x100000000));

// Will return random (but deterministic, if a --random-seed is used) numbers.
function randomIntUpTo(n) {
return Math.floor(Math.random() * n);
}

// Helper function that spawns a worker thread that corrupts memory in the
// background, constantly flipping the given address between two values.
function corruptInBackground(address, bitToFlip) {
function workerTemplate(address, bitToFlip) {
let memory = new DataView(new Sandbox.MemoryView(0, 0x100000000));
let oldValue = memory.getUint32(address, true);
let newValue = oldValue ^ bitToFlip;
while (true) {
memory.setUint32(address, newValue, true);
memory.setUint32(address, oldValue, true);
}
}
const workerCode = new Function(
`(${workerTemplate})(${address}, ${bitToFlip})`);
return new Worker(workerCode, {type: 'function'});
}

// Some random code for the parser...
// Need some unicode in here to get a Utf16 string (otherwise, we'd get a
// buffered stream during parsing, which probably complicates things a bit).
function test() {
function log(msg) {}

function launchRockets(n, destination) {
class RocketLauncher {
constructor(type) {
this.rocketType = type ?? '🚀';
this.launchCount = 0;
}

launch(destination) {
log(`Launching ${this.rocketType} to ${destination}!`);
this.launchCount++;
}
}

if (typeof destination === 'undefined') {
destination = "the moon";
}

let launcher = new RocketLauncher;
for (let i = 0; i < n; i++) {
launcher.launch(destination);
}
}

launchRockets(3);
launchRockets(2, "mars");
launchRockets(1, "pluto");
}

// Create a SeqTwoByteString (otherwise we have a slice string)
let source = (test.toString() + "\ntest();").split('').join('');
assertEquals(Sandbox.getInstanceTypeIdOf(source), kSeqStringType);

// Trigger some GCs to move the object to a stable position in memory.
gc();
gc();

// Start the worker thread to corrupt the string in the background.
let size = Sandbox.getSizeOf(source);
assertTrue(size > source.length * 2);
let offset = randomIntUpTo(size);
let string_address = Sandbox.getAddressOf(source);
let bitToFlip = 1 << randomIntUpTo(32);
corruptInBackground(string_address + offset, bitToFlip);

for (let i = 0; i < 1000; i++) {
// Modify the hash in between every attempt to avoid code caching.
// Use + 0x4 here to not change the type bits (see HashFieldTypeBits).
// Alternatively, use --no-compilation-cache.
let currentHash = memory.getUint32(string_address + kStringHashOffset, true);
let newHash = currentHash + 0x4;
memory.setUint32(string_address + kStringHashOffset, newHash, true);

try { eval(source); } catch {}
}

0 comments on commit 954dbf0

Please sign in to comment.