Description
A very fast program. Find the vulnerability and exploit it to read the flag.
Setup
Download the V8 build and the challenge patch/diff.
wget <url>/d8wget <url>/turboflan.diffchmod +x d8Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Read the V8 patch and identify the missing deopt guard
ObservationThe challenge ships turboflan.diff alongside the d8 binary, so a code change introduced the vulnerability. Read the patch and find the removed DeoptimizeIfNot calls in LowerCheckMaps() to see exactly which protection was stripped.Open the diff and find the change inside src/compiler/effect-control-linearizer.cc, specifically the LowerCheckMaps() function. The patch removes calls to DeoptimizeIfNot(DeoptimizeReason::kWrongMap, ...). Normally, when TurboFan's JIT encounters an object whose map (hidden class) differs from what it observed during profiling, it bails out and re-interprets the code in the interpreter (deoptimizes). With that guard removed, the JIT keeps running its specialized, optimized path even when the actual object type has changed, leading to type confusion.bashless turboflan.diffbash# Find the LowerCheckMaps function in effect-control-linearizer.ccbash# The removed lines look like:bash# DeoptimizeIfNot(DeoptimizeReason::kWrongMap, ...);What didn't work first
Tried: Search the diff for 'CheckMaps' instead of 'DeoptimizeIfNot' to find the removed guard.
The diff does reference CheckMaps nodes, but those are high-level IR nodes still present in the graph. The runtime bailout itself is the DeoptimizeIfNot calls inside LowerCheckMaps(). Searching for the node name finds the right function but not the removed lines, so look specifically for what DeoptimizeIfNot lost.
Tried: Look for the bug in the TurboFan typer (typer.cc or type-narrowing passes) rather than the effect-control linearizer.
Typer bugs, integer range confusion and incorrect widening, are a different class entirely, where the compiler miscomputes a value's range and drops bounds checks. This diff removes a map-check guard in the linearizer, which is hidden-class type confusion: the typer still sees correct types, but the runtime guard enforcing them is gone. Auditing typer.cc turns up nothing relevant.
Learn more
V8 Turbofan, not C heap."Turboflan" is a pun on Turbofan, V8's optimizing JIT compiler for JavaScript. The vulnerability lives in JS-level reasoning, not in glibc heap structures.
Why LowerCheckMaps matters: During JIT compilation, Turbofan inserts CheckMaps nodes to verify that an object's map (hidden class) matches the type the compiler assumed during optimization. The Effect Control Linearizer lowers these high-level nodes into concrete machine-level guards. When the DeoptimizeIfNot calls are removed from LowerCheckMaps(), those guards disappear and the compiled code blindly assumes the original map is still valid, even after the caller switches to a different object type.
The type confusion this enables: By calling an optimized function first with a float64 array (so the JIT specializes for floats) and then with a pointer-compressed object array (which has different element size and layout), the missing map check lets the JIT treat object pointers as raw 64-bit floats. Reading an element from the object array via the float-specialized path returns raw pointer bits as a double, leaking heap addresses. Writing via the same confused path corrupts an adjacent field, giving an arbitrary-read/write primitive.
See real-world bug patterns for adjacent JS sandbox-escape ideas.
Step 2Trigger JIT compilation and force the bug
ObservationThe missing guard exists only on the Turbofan-compiled path, not in the interpreter or the baseline tier, so the exploit fires only once the target function is optimized. A warmup loop followed by %OptimizeFunctionOnNextCall forces that tier-up before the type-confusing input arrives.Standard recipe: write a small attack function, force optimization via repeated calls (or %OptimizeFunctionOnNextCall in d8 with --allow-natives-syntax), then call once with an input the typer mispredicts.bash# Run d8 with native syntax to force optimization: ./d8 --allow-natives-syntax exploit.jsbash# Sketch of a typer-confusion exploit (specifics depend on the diff): # function leak(x) { return arr[x]; } # for (let i = 0; i < 0x10000; i++) leak(0); # %OptimizeFunctionOnNextCall(leak); # leak(badValue); // typer thinks in-range, runtime is out-of-range -> OOB readExpected output
picoCTF{...}What didn't work first
Tried: Call the attack function with the bad value immediately without warming it up first, skipping the loop of 0x10000 calls.
V8 only JIT-compiles a function once it has been called enough times to gather profiling feedback. Without the warmup loop, the function stays in the interpreter or the Maglev baseline tier, both of which still carry valid map checks. The confusion only appears in Turbofan-compiled code where the guard was removed, so skipping the warmup means never triggering the bug.
Tried: Use %OptimizeFunctionOnNextCall without passing --allow-natives-syntax to d8.
Percent-prefixed intrinsics like %OptimizeFunctionOnNextCall sit behind the --allow-natives-syntax flag at engine level. Without it, V8 reads the percent identifier as a syntax error and aborts before running anything. The script does not fail quietly, it does not run at all, so pass the flag on the d8 command line.
Learn more
From OOB read to flag. A typical chain: corrupt a JSArray's elements pointer (or its length) to fabricate a fake array that addresses arbitrary memory; build addrOf/read/write primitives; locate
%FunctionPrototype%code object; flip a writable but executable page; drop shellcode; call.readFile("flag.txt")in d8 is often available too, sidestepping shellcode entirely.
Flag
Reveal flag
picoCTF{Good_job!_Now_go_find_a_real_v8_cve!_...}
Turboflan is a V8 Turbofan JIT type-confusion challenge triggered by a missing deoptimization guard in LowerCheckMaps(), not a typer-range bug or a glibc heap bug. Exploit details depend on the exact diff shipped, so treat this page as the framing rather than a turn-key exploit.