Chrome Exploit Mitigations

June 2nd, 2026 – Alisa Esage

Introduction

Earlier this year I invested several weeks full-time in zero day engineering for Google Chrome and validating my mental models of frontier browser defenses. I found and reported to Chromium VRP a number of security bugs across renderer, network service, and browser process. This article will focus on three modern Chrome-specific exploit mitigations that I had to confront while writing the exploits: MiraclePtr, v8 sandbox, and PartitionAlloc.

Before getting to those three, I met the old guard: ASLR, CFI, CORS and the sandbox security model itself. These are generally well understood and I will only briefly mention my experience with them here.

As my bugs spanned the full stack of Chrome technology – from a renderer RCE to network service partial-EoP and browser process EoP for sandbox escape, all memory corruption issues – I consider this subset of mitigations somewhat representative of the actual resistance surface that a modern Chrome attacker will face; and a priority for offensive R&D.

Chrome security architecture with bug map
Chrome architecture (simplified), with my findings marked on it.

Some of my bugs bypassed the relevant Chrome mitigations, while others were blocked by them despite my best effort. I cover both types in this article, as much as the present stage of responsible disclosure permits. Full technical details of the exploits will be disclosed in due course.

For a compressed systematic exposure to prerequisite knowledge, I recommend my Browser Exploit Design course.

The Landscape

Chrome has many exploit mitigations that are doing more work than public knowledge suggests.

The tables below are actual as of current Stable (M148).

PartitionAlloc
MitigationDetailsEnabled?OSBypass
PartitionAlloc-EverywherePA-E routes every renderer and browser allocation through PartitionRoot via the allocator shim, controlled by use_partition_alloc_as_malloc_default and implemented under base/allocator/partition_allocator/.Default onAll
Bucketed slot spansEach partition splits allocations into size-class buckets (PartitionBucket in partition_bucket.cc); requests round up to the bucket's slot_size and slots come from num_system_pages_per_slot_span system pages, isolating size classes from one another.Default onAllPartial
Address-pool isolationOn 64-bit, PartitionAddressSpace reserves separate virtual-address pools by purpose — regular (kRegularPoolHandle), BRP (kBRPPoolHandle), configurable, and thread-isolated — collectively known as the giga-cage / gigacage.Default on64-bitPartial
Out-of-cage metadataWith move_metadata_outside_gigacage, per-slot metadata (refcounts and tags held in InSlotMetadata / InSlotMetadataTable) lives in a second mapping outside the giga-cage, so in-cage write primitives can't reach it.Default on64-bit non-iOSPartial
Freelist pointer encodingThe freelist next pointer is obfuscated by EncodedNextFreelistEntry::Transform()ReverseBytes() on little-endian, negation on big-endian — and cross-checked against a shadow copy at a different offset, raising the bar for tcache-style freelist poisoning.Default onAllYes
Thread CacheThreadCache keeps a per-thread, TLS-backed freelist of recently-freed slots (up to kThreadCacheDefaultSizeThreshold) in front of the central per-bucket freelist; instances coordinate via ThreadCacheRegistry, are sized by SetThreadCacheMultiplier, and drained by RunPeriodicPurge. The same hot path hosts cacheline poisoning, tombstone validation, and SchedulerLoopQuarantine integration (kThreadCacheQuarantineIndex) — added because real-world exploits target tcache/fastbin-style freelist corruption. See base/allocator/partition_allocator/src/partition_alloc/thread_cache.h.Default onAllSubsystem — sub-features bypassed individually
Cacheline poisoning on freeOn free() into the Thread Cache, the cacheline holding the new freelist next is overwritten with poison_16_bytes (0xbadbad00 repeating) so a subsequent UAF read on that line crashes early instead of returning attacker-shaped data; gated by PA_CONFIG(HAS_FREELIST_SHADOW_ENTRY). See thread_cache.h:626-674.Default on (with shadow entry, LE)AllProbabilistic — only the touched cacheline
ThreadCache tombstoneAfter a thread exits, its per-thread ThreadCache* is marked with kTombstoneMask low bits; IsValidPtr / IsValid check for the tombstone and reject the access before dereference, preventing use-after-thread-exit on the cache pointer. See thread_cache.h:279-298.Default onAllLimited
Freelist randomizationPer-bucket randomization of free-entry order would make grooming the freelist harder, but no such shuffle is implemented in M148.Not found in M148
Guard pagesUnmapped guard pages flank slot spans and direct maps (DirectMapGuardPages in partition_page.h) so a linear overflow off the end of a slot faults rather than corrupting adjacent metadata.Default onAllPartial
Partition cookieA 16-byte canary 0xDE 0xAD 0xBE 0xEF 0xCA 0xFE 0xD0 0x0D 0x13 0x37 0xF0 0x05 0xBA 0x11 0xAB 0x1E (kCookieValue in partition_cookie.h) is stamped at the end of every allocation via PartitionCookieWriteValue; on free, PartitionCookieCheckValue verifies the pattern and calls CookieCorruptionDetected on mismatch. Gated by USE_PARTITION_COOKIE (use_partition_cookie), enabled only in debug / DCHECK builds.Debug / DCHECK onlyAllLimited
MiraclePtrMiraclePtr — also called BRP, BackupRefPtr, or raw_ptr<T> (with raw_ref<T> for references) — wraps non-owning pointer fields; BackupRefPtrImpl increments a refcount in InSlotMetadata and, on free() with a non-zero count, quarantines the slot via request_quarantine and poisons the memory with internal::kQuarantinedByte = 0xEF (so reads return the famous 0xEFEFEFEFEFEFEFEF pattern), turning a UAF into a crash. Gated by kPartitionAllocBackupRefPtr; fields opt out with RAW_PTR_EXCLUSION.Default on (Android renderer excluded)AllNo bypass of invariant
Dangling-pointer detectorWhen a slot is freed while a raw_ptr<T> still points at it, DanglingPointerDetector raises a DanglingRawPtrChecks failure in DCHECK/debug builds; enable_dangling_raw_ptr_checks_default gates the build, and DanglingUntriaged tags known-but-unaddressed cases. Debug builds (EXPENSIVE_DCHECKS_ARE_ON) also DebugMemset freed slots with kFreedByte = 0xCD and stamp kUninitializedByte = 0xAB on freshly recommitted memory.Debug / DCHECK onlyLinux/CrOS
PCScanThe briefly-shipped conservative-scanning quarantine — also called *Scan / StarScan — has been removed from M148; no references remain.Removed
GWP-ASanenable_gwp_asan_support puts a guard page around a tiny sampled fraction of allocations so heap bugs occasionally crash deterministically and bucket together in crash reports; detection only, not prevention.Default on (low rate)All
Scheduler-loop quarantineSchedulerLoopQuarantine defers the actual free() until a later scheduler-loop tick so reuse can't happen on the freeing thread's hot path; quarantined memory is poisoned with 0xcdcdcdcd; per-process configuration is JSON-driven and the feature is off by default.Opt-inAllNew
Advanced checkskPartitionAllocAdvancedChecks (AdvancedChecks) turns on extra runtime integrity assertions, enabled per process (browser / renderer / GPU / non-renderer / all); off by default.Opt-inAllNew
Memory reclaimerPartitionAllocMemoryReclaimer periodically decommits idle slot spans to reclaim address-space fragmentation.Default onAll
Straighten/sort freelistStraightenLargerSlotSpanFreeLists and SortSmallerSlotSpanFreeLists reorder per-span freelists on purge to improve allocation locality.Default onAll
Shadow metadataEnableShadowMetadata maps PartitionAlloc metadata twice — read-only to the allocator's normal code, and writable behind a PKU key — so corruption from a write primitive in regular code cannot reach metadata. HW-gated.Default on (HW-gated)x64 Linux/CrOSNew
Thread-isolation poolA dedicated partition pool (kThreadIsolatedPoolHandle, PA_THREAD_ISOLATED_ALIGN) for sensitive allocations whose pages are guarded by Intel MPK / PKU keys (enable_pkeys); only threads holding the key can write.Default on (HW-gated)x64 Linux/CrOSYes (PKU)
PartitionAlloc MTEOn ARM hardware with MTE, PartitionAllocMTE / kEnableMemoryTaggingChecks tags each slot and matches the tag on access in sync or async mode; mismatched tags fault.Default on (HW-gated)Android arm64Yes
Strict free-size checkkPartitionAllocFreeWithSize enables PartitionRoot::FreeWithSize to verify that the caller-passed size on free matches the slot's actual size, catching size-class confusion and type-confused frees.Opt-inAllNew
ASan-BRP integrationASan-only checks (kAsanBrpDereferenceCheck, kAsanBrpExtractionCheck, kAsanBrpInstantiationCheck) instrument raw_ptr<T> dereference, extraction, and instantiation against BRP-protected memory to surface misuse during sanitizer runs.ASan builds onlyAllN/A (sanitizer)
V8 Sandbox
MitigationDetailsEnabled?OSBypass
V8 SandboxThe V8 heap sandbox (a.k.a. heap cage) is a 4 GB virtual address range set up by Sandbox::Initialize() in v8/src/sandbox/sandbox.h and gated by v8_enable_sandbox / V8_ENABLE_SANDBOX; all V8 on-heap pointers must point inside it, and stray out-of-cage references are caught by SBXCHECKs.Default on64-bit non-FuchsiaYes
Pointer compressionInside the cage, V8 stores pointers as 32-bit cage-relative tagged values (V8_COMPRESS_POINTERS); v8_enable_pointer_compression / _shared_cage and kPtrComprCageBaseAlignment define the shared base alignment.Default onx64 / arm64 / loong64Foundational
External Pointer Table (EPT)Raw host pointers held by V8 objects ("sandboxed pointers") live in ExternalPointerTable outside the cage and are referenced from inside via tagged handles (kExternalPointerTagShift). See v8/src/sandbox/external-pointer-table.h.Default on (with sandbox)AllYes
Code Pointer Table (CPT)JSFunction::code is indirected through CodePointerTable via a kCodePointerHandle, so a corrupted handle can only resolve to an existing compiled code object rather than an arbitrary address. See v8/src/sandbox/code-pointer-table.h.Default on (with sandbox)AllYes
Wasm Code Pointer Table (WCPT)The Wasm analog of CPT: WasmCodePointerTable stores a signature_hash per entry that's cross-checked at indirect Wasm calls, and the table itself is pkey-write-protected on supported HW. See v8/src/wasm/wasm-code-pointer-table.h.Default on (with sandbox)AllYes
Trusted Pointer Table (TPT)TrustedPointerTable holds pointers to V8-internal trusted objects, dispatched through 15-bit IndirectPointerTags so a corrupted handle can only name a same-type trusted entry. See v8/src/sandbox/trusted-pointer-table.h and indirect-pointer-tag.h.Default on (with sandbox)AllLimited
Trusted SpaceTrustedSpace is a V8 heap region placed outside the sandbox cage; sensitive objects (select Maps, bytecode handlers) live there so in-cage corruption cannot reach them directly.Default on (with sandbox)AllBoundary, not bypass target
CppHeap Pointer TableCppHeapPointerTable indirects pointers from V8-heap objects into the Oilpan / cppgc C++ heap, applying the same handle-based protection across the heap boundary. See v8/src/sandbox/cppheap-pointer-table.h.Default on (with sandbox)AllSame class
JS Dispatch TableJSDispatchTable indirects every JS function dispatch through a write-protected table indexed by JSDispatchHandle, supporting leaptiering (seamless tier switching) without exposing raw code pointers to sandboxed code. See v8/src/sandbox/js-dispatch-table.h.Default onAllSame class
Trap-based Wasm boundsWasm memory accesses skip explicit bounds-cmp instructions; OOB accesses fault and are caught by kTrapHandler (v8/src/trap-handler/), which converts the signal into a Wasm trap.Default on64-bitHolds
Hardware sandbox (PKU)V8_ENABLE_SANDBOX_HARDWARE_SUPPORT uses Intel MPK / PKU keys (V8_HAS_PKU_JIT_WRITE_PROTECT) to enforce the sandbox/trusted-region boundary in hardware; M148's CodeSandboxingMode enum (kSandboxed / kUnsandboxed) annotates which code regions run under that enforcement. See v8/src/sandbox/hardware-support.h and code-sandboxing-mode.h.Experimentalx64 Linux/CrOS
V8 (non-sandbox)
MitigationDetailsEnabled?OSBypass
MAP_JIT W^XOn Apple Silicon, V8's JIT region is mapped MAP_JIT and toggled per-thread between writable and executable via pthread_jit_write_protect_np; RwxMemoryWriteScope brackets writes, providing per-thread W^X.Default onmacOS arm64Yes
Dual-mapping W^XOn Linux and Android, V8's JIT pages are dual-mapped via mmap aliasing — one view writable, one executable — so executable code is never simultaneously writable. See code-space-access.h.Default onLinux/AndroidYes
Windows JIT W^XWindows JIT is not W^X-enforced: the renderer uses MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT so V8 can opt out of strict ACG and keep writable+executable JIT pages.OffWindows
CFI-icall in TurboFan/WasmWhere Clang CFI is on (Linux x64 official), TurboFan- and Wasm-emitted indirect calls get LLVM cfi-icall checks via use_cfi_icall; call sites that can't carry type metadata opt out with V8_CLANG_NO_SANITIZE.Default on (where CFI on)Linux x64 officialYes
Short builtin callsWith v8_enable_short_builtin_calls, embedded builtins are laid out close enough to the heap (within kShortBuiltinCallsOldSpaceSizeThreshold) to be reached by direct rather than indirect calls, shrinking the icall surface.Default onAll
--jitless--jitless (FLAG_jitless) runs V8 in interpreter-only mode, disabling TurboFan, Maglev, Sparkplug, and the Wasm baseline JIT — eliminating writable-executable pages and unlocking strict ACG on Windows.Opt-inAll
Spectre v1 masking (V8)Early JIT-level Spectre v1 mitigation (branch load poisoning, --branch-load-poisoning) was retired in favor of Site Isolation; no active masking code remains.Retired
Process / Sandbox
MitigationDetailsEnabled?OSBypass
Multi-process architectureChrome splits responsibilities across renderer, browser, GPU, and utility processes under content/browser and content/renderer; RenderProcessHost and ProcessLauncher manage spawning.Default onAll
Site IsolationEach web site is placed in its own renderer process (--site-per-process, IsolateOrigins); SiteIsolationPolicy (content/browser/site_isolation_policy.cc) enforces strict site-per-process on desktop, partial on low-RAM Android.Default on (desktop); partial (Android low-RAM)AllYes
OOPIFsOut-of-process iframes (OOPIFs) place cross-site iframes in their own renderer process; isolated sandboxed iframes (AreIsolatedSandboxedIframesEnabled) extend this to same-origin sandboxed frames.Default onAllYes
Origin-Agent-Cluster (OAC)A site can opt into per-origin isolation finer than site isolation via the Origin-Agent-Cluster header (kOriginIsolationHeader), reflected in JS as document.originAgentCluster.Opt-in (by site)All
ZygoteOn Linux/CrOS/Android, renderers are spawned from a pre-forked, pre-hardened zygote process managed by ZygoteHost; an intermediate kZygoteIntermediateSandbox primes sandbox state before the renderer-specific policy is applied, and the zygote socket carries spawn requests.Default onLinux/CrOS/AndroidYes (Zygote socket)
seccomp-bpfOn Linux/CrOS/Android, the renderer is constrained by a seccomp-bpf syscall allowlist defined in BaselinePolicy / BaselinePolicyAndroid (sandbox/linux/seccomp-bpf-helpers/); disallowed syscalls return errno or kill the process.Default onLinux/CrOS/AndroidYes (via kernel)
User namespacesOn Linux/CrOS, the sandbox is built on CLONE_NEWUSER (namespace_sandbox), replacing the older setuid_sandbox helper so no setuid bit is required.Default onLinux/CrOSYes (via kernel)
LandlockOn Linux, the renderer installs a Landlock LSM ruleset (landlock_create_ruleset, linux_landlock.h) restricting filesystem access; enabled since M97.Default on (M97+)LinuxLimited
AppContainer + restricted tokenOn Windows, the renderer runs inside an AppContainer (AppContainerBase / AppContainerProfile, sandbox/win/src/app_container_base.cc) at low integrity level with a restricted token, sharply limiting object access.Default onWindowsYes
Seatbelt profileOn macOS, the renderer is confined by a Seatbelt .sb profile applied via sandbox_init_with_parameters (sandbox/mac/seatbelt.cc) — the same App Sandbox mechanism Apple uses for sandboxed apps.Default onmacOSYes
isolatedProcess / SELinuxOn Android, the renderer runs as an isolatedProcess in the isolated_app SELinux domain (seapp_contexts), with Binder and file access denied beyond what's explicitly granted.Default onAndroidYes
Win32k lockdownThe renderer sets MITIGATION_WIN32K_DISABLE (PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON) so calls into win32k.sys are blocked, removing a large kernel attack surface.Default onWindowsYes
Code Integrity Guard (CIG)MITIGATION_FORCE_MS_SIGNED_BINS (PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON, MicrosoftSignedOnly) makes the renderer reject load of any non-Microsoft-signed DLL.Default on (renderer)WindowsLimited
Arbitrary Code Guard (ACG)Strict ACG (MITIGATION_DYNAMIC_CODE_DISABLE, PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON, ProhibitDynamicCode) blocks all dynamic code generation; the renderer instead uses the opt-out variant MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT so V8's JIT can keep running. Strict mode is only viable under --jitless.Off in renderer; opt-in with --jitlessWindows
Intel CET shadow stack (process)Intel CET's user shadow stack (PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_ON) is requested per process; MITIGATION_CET_STRICT_MODE opts a process into hard-fail (opt-in), and MITIGATION_CET_DISABLED opts a process out entirely.Conditional; strict mode opt-inWindows ≥20H1 x64Yes
FSCTL syscall disableMITIGATION_FSCTL_SYSTEM_CALL_DISABLE blocks FSCTL control codes to NtFsControlFile, narrowing a historically rich kernel attack surface; supported on Windows 10 22H2+.Default on (where supported)Windows 10 22H2+New
Restrict core sharingMITIGATION_RESTRICT_CORE_SHARING (Windows 11 24H2+) prevents the renderer from sharing an SMT/hyperthread core with another security domain, mitigating same-core side-channel leaks.Default on (where supported)Windows 11 24H2+New
Module tampering protectionMITIGATION_MODULE_TAMPERING_PROTECTION detects in-process IAT (Import Address Table) patching and remaps the affected module from a clean on-disk image, defending against import-table hijacking that would otherwise sidestep CIG. Lands in M149.Emergent (M149+) — not in M148 stableWindowsNew
No-child-process policyThe renderer cannot spawn child processes; all process launches go through ChildProcessLauncher in the browser, enforced at the content layer rather than via a single OS mitigation flag.Default onAllYes (browser-side bugs)
Misc Windows process mitigationsThe renderer also applies MITIGATION_EXTENSION_POINT_DISABLE (no AppInit DLLs), MITIGATION_NONSYSTEM_FONT_DISABLE (no third-party fonts), and MITIGATION_IMAGE_LOAD_NO_REMOTE / _NO_LOW_LABEL (ProcessImageLoadPolicy) to block remote and low-integrity DLL loads.Default on (renderer)WindowsLimited
Sandbox brokerA privileged broker process (Windows: BrokerServices / TargetProcess in sandbox/win/src/broker_services.cc; Linux: namespace broker) mediates the small set of OS operations the renderer is allowed to perform — broker bugs are sandbox escapes by definition.Default onWindows, LinuxYes
Network Service sandboxThe Network Service (services/network/) runs in its own sandboxed process (kNetwork sandbox type, NetworkProcessSandbox) — fully sandboxed on desktop, partial on mobile.Default on (desktop); partial (mobile)AllYes
LPACOn Windows, the network and audio services run inside a Less-Privileged AppContainer (LowPrivilegeAppContainer, IsLpacEnabled) — a tighter variant than the renderer's AppContainer, with only explicitly granted capabilities (lpacCom, lpacPnpNotifications, etc.).PartialWindowsLimited
GPU process sandboxThe GPU process runs in the kGpu sandbox (GpuSandbox); sandbox strength is constrained by what the platform driver requires the process to access, leaving a substantial attack surface in the driver itself.Default onAllYes
Specialized service sandboxesNewer per-service sandbox types in sandbox/policy/mojom/sandbox.mojomkOnDeviceModelExecution, kHardwareVideoDecoding / kHardwareVideoEncoding, kPrintBackend, kScreenAI, kIme, kTts, kMirroring, kOnDeviceTranslation, kShapeDetection — give each subsystem its own narrow policy.Default on per-featurePlatform-specific
Utility / Audio isolationAudio (kAudio) and generic utility (kUtility) services run in their own sandboxed processes rather than in the renderer or browser.Default onAllLimited
IPC / Mojo
MitigationDetailsEnabled?OSBypass
Interface attribute enforcementMojom-level capability gating via [RuntimeFeature], [RequireContext], [AllowedContext], and [MinVersion] attributes — enforced at codegen time by mojom_restrictions_check.py and mojom_interface_feature_check.py — restricts which contexts can bind or send which interfaces.Default onAllYes (mis-scoped bindings)
Bind-time caller validationOn BindReceiver, Mojo tracks the source node (set_source_node) and lets endpoints reject unexpected callers; mis-routed messages report via NotifyBadMessageFrom / BadMessageCallback, which can kill the offending process.Default on (partial)AllYes
Message ordinal scramblingWhen enable_mojom_message_id_scrambling is on (official non-CrOS desktop builds), ScrambleMethodOrdinals in mojo/public/tools/bindings/mojom_bindings_generator.py derives each wire method ID as the first 31 bits of SHA-256(salt + interface + index), with the per-build salt sourced from //chrome/VERSION — raising the bar for cross-pipe confusion and blind probing.Default onmac/win/linux (official, non-CrOS)Limited
Fuzzing infrastructureIn-tree Mojo fuzzing: MojoLPM (libFuzzer + LPM-driven protocol fuzzing), proto-based fuzzers, mojo_parse_message_fuzzer, and channel_mac_fuzzer, all gated by enable_mojom_fuzzer.Default onAll
MojoJS gatingJavaScript-to-Mojo bindings (MojoBindingsController, MojoJsFeatures) are restricted to the main frame's isolated world for WebUI (kMojoWebUi) and only opt in via EnableMojoJsBindings / AllowMojoJSForProcess — they are not exposed to web content in stable.Default on (off for web)All
Capability-based validationMojo validates every incoming message at the receiver — typed enums, struct header / version, pointer overflow, ValidateNonNullableUnion (validation_util.h); a malformed message triggers ReportBadMessage / BadMessageCallback, which terminates the sender.Default onAllYes (validation-logic bugs)
IsolatedConnectionmojo::IsolatedConnection opens a point-to-point Mojo channel that is not joined to the global Mojo node graph, used where strict cross-process isolation is wanted.Default onAll
Control-Flow Integrity (native)
MitigationDetailsEnabled?OSBypass
LLVM CFIClang's LLVM CFI (is_cfi; -fsanitize=cfi-vcall / -cfi-icall / -cfi-cast via build/config/sanitizers/sanitizers.gni) provides forward-edge type-based call protection — vtable calls and indirect calls are checked against the legal type set — using ThinLTO for cross-DSO coverage. On by default only in official Linux x64 and CrOS device builds; use_cfi_icall and use_cfi_cast are narrower still.Default on (official builds)Linux x64, CrOS deviceYes
ShadowCallStack (SCS)ShadowCallStack (-fsanitize=shadow-call-stack, enable_shadow_call_stack) keeps a separate read-only return-address stack on arm64, anchored at the reserved x18 register; opt-in per Android arm64 build.Default on (opt-in per build)Android arm64Limited
Intel CET shadow stack (compile)Windows x64 official binaries are linked CET-compatible (/CETCOMPAT, enable_cet_shadow_stack), letting the OS enable the hardware shadow stack on supported CPUs.Default on (official)Windows x64Yes
Arm PACArm Pointer Authentication / PAUTH (ARMv8.3-A) signs return addresses (PACIASP) and verifies them on return (AUTIASP); enabled via -mbranch-protection=pac-ret under arm_control_flow_integrity=standard on Linux/Android arm64.Default onLinux/Android arm64Yes
Arm BTIArm Branch Target Identification (ARMv8.5-A) requires every indirect-branch landing pad to be a BTI instruction; enabled via -mbranch-protection=standard and link-time -Wl,-z,force-bti / lld_branch_target_hardening on Linux/Android arm64.Default onLinux/Android arm64Yes
Stack canariesCompiler-inserted stack cookies (SSP, also called GS cookies) via -fstack-protector or -fstack-protector-strong depending on platform/config, validated on function return through __stack_chk_fail.Default onAllYes
SafeStackSafeStack (-fsanitize=safe-stack) split the stack into a safe and an unsafe stack; removed from Chrome's build configuration.Removed
Compiler / OS Baseline
MitigationDetailsEnabled?OSBypass
ASLRAddress Space Layout Randomization for image, heap, and stack — Windows /DYNAMICBASE, POSIX position-independent code/executable (-fPIC / -fPIE, PIE binaries).Default onAllYes
High-entropy ASLROn 64-bit Windows, /HIGHENTROPYVA opts into the wider 64-bit ASLR entropy range; corresponding POSIX defaults apply on Linux/Android.Default on64-bitYes
DEP / NXData Execution Prevention / NX: Windows /NXCOMPAT and POSIX -Wl,-z,noexecstack mark data pages non-executable so injected data can't be jumped to directly.Default onAllYes
/SAFESEHOn Windows x86, /SAFESEH (SafeSEH) requires Structured Exception Handlers to live in a known table, validating the SEH chain at dispatch time.Default onWindows x86Yes
RELRO + BIND_NOWFull RELRO: -Wl,-z,relro makes the GOT read-only after relocation and -Wl,-z,now forces eager symbol resolution, so the PLT no longer needs to write to the GOT at runtime. On for non-component Linux/Android/Fuchsia builds.Default on (non-component)Linux/Android/Fuchsia
_FORTIFY_SOURCEglibc's fortified-libc wrappers around string and memory functions (__strcpy_chk and friends) — _FORTIFY_SOURCE=2 by default on Linux/Android, =3 on ChromeOS and sysroot+Clang builds, providing compile- and runtime-time bounds checks where the size is statically known.=2 default; =3 on CrOS and sysroot+ClangLinux/Android/CrOSYes
Auto-init stack variables-ftrivial-auto-var-init=zero (controlled by init_stack_vars) auto-initializes stack-local variables to zero on entry (-ftrivial-auto-var-init=pattern is the fallback), closing the uninitialized-stack-read bug class; on by default everywhere except non-official Android.Default onAll except non-official AndroidLimited
ThinLTO + whole-program-vtables-flto=thin (ThinLTO) plus -fwhole-program-vtables enables link-time optimization and whole-program devirtualization, prerequisites for cross-DSO CFI; on in CFI builds.Default on (CFI builds)Linux x64 official, CrOS
Relative VTables ABI-fexperimental-relative-c++-abi-vtables switches to a compact vtable layout using relative offsets instead of absolute pointers, hardening vtable layout on Android arm64 component builds.Default onAndroid arm64 component
libstdc++ / libc++ assertionsStandard-library assertions — _GLIBCXX_DEBUG=1 / _GLIBCXX_ASSERTIONS=1 for libstdc++, _LIBCPP_HARDENING_MODE for libc++ — catch out-of-range access and iterator misuse; enabled in debug and ASan builds.Debug + ASan onlyAll
MTE (toolchain)Toolchain-level MTE codegen (-march=armv8.5-a+memtag, __arm_mte intrinsics) is not enabled in M148 at the build level; the runtime MTE support in PartitionAlloc covers what's actually deployed.Not enabled
-fstack-clash-protection-fstack-clash-protection would insert stack probes to detect stack/heap clash, but no such flag is set in M148's build configuration.Not found in M148
HWASan / ASanSanitizer builds only — -fsanitize=address (AddressSanitizer) and -fsanitize=hwaddress (HWASan, a hardware-tagged ASan based on Top-Byte-Ignore on arm64); used in dev and fuzz builds, not shipped.Debug + fuzz onlyAll (HWASan: Android arm64)
Web Platform
MitigationDetailsEnabled?OSBypass
CORBCross-Origin Read Blocking (orb_api.h) prevents cross-origin responses with protected MIME types (HTML, XML, JSON) from reaching renderers that should not see them; respects X-Content-Type-Options: nosniff.Default on (coexists with ORB)AllPartial
ORBOpaque Response Blocking (services/network/orb/, orb_impl.h) is CORB's successor, blocking cross-origin opaque responses from being delivered to renderers based on a stricter classification.Default on (rolling)AllLimited
COOP / COEP / CORPCross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, and Cross-Origin-Resource-Policy headers (with values such as require-corp and same-origin-allow-popups) gate window.crossOriginIsolated, which in turn gates SharedArrayBuffer and high-resolution timers.Opt-in (by site)AllYes
Document Isolation Policy (DIP)Document Isolation Policy (content/browser/security/dip/, feature kDocumentIsolationPolicyWithoutSiteIsolation) grants a document crossOriginIsolated without requiring full Site Isolation for the embedding context.Opt-in (feature)AllNew
Fetch MetadataChrome attaches Sec-Fetch-Site / -Mode / -Dest / -User / -Storage-Access headers (sec_header_helpers.cc) so servers can reject unexpected requests — CSRF, XSSI, Spectre cross-origin loads — at the edge.Default on (Chrome sends)All
Private Network Access (PNA)Private Network Access (a.k.a. CORS-RFC1918 / Local Network Access; local_network_access_checker) blocks requests from public origins to private/local IP ranges unless preflighted, mitigating router and intranet attacks.Default on (rolling)AllLimited
HTTPS-Upgrades + HTTPS-FirstChrome automatically attempts HTTPS first for http:// navigations; HTTPS-First Mode (HTTPS-Only Mode) requires explicit user opt-in to fall back to plaintext (force_no_https_upgrade).Default onAllLimited
Encrypted Client Hello (ECH)Encrypted Client Hello encrypts the TLS SNI using a public key fetched from the DNS HTTPS resource record (SVCB family), preventing on-path observers from learning the hostname being connected to.Default onAll
HSTS preloadChrome ships an HTTP Strict Transport Security preload list (transport_security_state, AddHSTS) so listed hosts are HTTPS-only from the first request — no opportunity for SSL stripping on initial connect.Default onAllLimited
Schemeful Same-SiteSchemeful Same-Site (schemeful_site.h) treats http:// and https:// versions of the same eTLD+1 as cross-site for SameSite cookie purposes, closing a downgrade-attack path.Default onAllLimited
SameSite=Lax defaultCookies without an explicit SameSite attribute default to Lax (CookieSameSite::LAX_MODE, kSameSiteByDefaultCookies), mitigating cross-site request forgery.Default onAllYes
First-Party SetsFirst-Party Sets, also called Related Website Sets (FPS / RWS — GlobalFirstPartySets, FirstPartySetsHandlerImpl), let a controlling site declare a set of related domains that share certain storage and identity boundaries.Default onAll
DIPSBounce Tracking Mitigations (DIPS — btm_bounce_detector, Privacy.DIPS UMA) identify domains used purely as redirect-bounce trackers and clear their state.Default onAll
BFCache securityBack-Forward Cache (BackForwardCacheImpl) keeps the prior page's renderer process alive but isolated; pages only enter BFCache when no cross-process leaks are possible.Default onAllLimited
Trusted TypesTrusted Types (TrustedHTML, TrustedScript, TrustedScriptURL, trusted_types_names.h) require typed wrappers before passing strings to DOM-XSS sinks; opt-in for web content, mandatory for chrome:// WebUI.Opt-in (mandatory for WebUI)All
CSP / XFO / nosniffContent-Security-Policy (with directives such as script-src and frame-ancestors), X-Frame-Options for legacy framing, and X-Content-Type-Options: nosniff to disable MIME sniffing — all enforced where sites set them.Default on (where set by site)AllYes
Permissions PolicyPermissions Policy (formerly Feature Policy, with sibling Document Policy; permissions_policy/) lets a document gate which browser features can be used by itself and by embedded frames.Default onAll
Spectre / Side-Channel
MitigationDetailsEnabled?OSBypass
Site Isolation (Spectre)Site Isolation places different sites in different OS processes (ChildProcessSecurityPolicy enforces the boundary), so a Spectre v1/v2 transient-execution read inside a renderer cannot reach another site's data — that data lives in another address space entirely.Default on (desktop); partial (Android)AllYes
SAB COOP+COEP gatingSharedArrayBuffer is only available to documents that have set COOP+COEP headers granting crossOriginIsolated (SharedArrayBufferIssueType tracks violations), since SAB enables the high-resolution timers Spectre PoCs depend on.Default onAllYes
performance.now() clampClampTimeResolution (time_clamper.h) quantizes performance.now() to kCoarseResolutionMicroseconds = 100µs without crossOriginIsolated and kFineResolutionMicroseconds = 5µs with it, denying easy high-resolution timing for side-channel attacks.Default onAllYes
OS microarch mitigationsMicroarchitectural mitigations against branch-target-injection and related transient-execution attacks — retpoline, IBRS / IBPB / eIBRS, STIBP — are configured at the OS/kernel level; Chrome inherits whatever the OS provides.OS-level (Chrome inherits)AllYes
Cross-domain SMT restrictionMITIGATION_RESTRICT_CORE_SHARING (Windows 11 24H2+) prevents the renderer's threads from sharing an SMT/hyperthread core with another security domain, mitigating cross-domain hyperthread side-channel leaks.Default on (where supported)Windows 11 24H2+New

Not all the mitigations at once are at work against every Chrome exploit chain. An exploit would usually hit a small subset of mitigations, which depends on the bug class, operating system, hardware architecture, field trials configuration, user configuration, Chrome version, specific bug instance shape, and other factors.

That said, certain mitigations are more prohibitive while others are mostly obstacles in the exploit engineering process.

The Old Guard

Familiar exploit mitigations – such as ASLR and CFI – are still relevant and present a substantial obstacle to exploit engineering for modern Chrome.

Consider ASLR. The common bounce-off judgement from an "exploit expert": "Just find an infoleak bug to bypass it". Technically correct, but... In my experience, reality differs.

First, pure memory disclosure vulnerabilities in Chrome are rare. This is both statistical and structural. I looked at most Chrome security bugs of the past year, plus selectively picked prior samples. I didn't get an impression that a memory disclosure bug in Chrome is easy.

Furthermore, an infoleak bug must be located in the same process as the main bug to be chainable, which constrains the search space. Every process type in Chrome is a different world with its own anti-patterns, architectural models and implementation details. A problem like "I need to find an infoleak in renderer and another one in the browser process" isn't the same problem repeated twice, it's two different specialization projects.

Historically, ASLR in Chrome renderer was weakened by JavaScript engine type confusion vulnerabilities. A typical JSE bug is powerful enough that it can work twice: first to disclose memory and break ASLR, then to execute the code. [1] Enablement of v8 sandbox in M124 broke this convenience, and made ASLR hard again for the Chrome renderer.

In contrast, the broker process, GPU process and other high-privilege browser processes never had this kind of a luxury bug class to coast through ASLR, which is why the majority of wild 0-day chains for Chrome use either logic bugs for the sandbox escape part, or an OS kernel EoP (another restricted attack surface), or settle on a renderer-only payload, or attack a peculiar configuration – all compromises driven by the cost of an honest infoleak chain in the browser process.

Another assumed solution to bypass ASLR is to "make" an infoleak out of a compatible use-after-free bug. In fact, PartitionAlloc blocks many state manipulations on use-after-free bugs, while MiraclePtr shrinks the attack surface.

A similar situation with CFI/CFG (Control Flow Integrity). In Chrome, CFI is baked into every object vtable dispatch by the compiler, excluding a handful of code-annotated exceptions, which makes the coverage broad and solid. It means that any accidental vtable call on a use-after-free object will hit a hard crash, and if that happens early enough in the bug's state machine, it becomes unexploitable.

CFG wall in Chrome on Windows
CFG wall in Chrome on Windows. %rax has the vtable pointer that was called to 0x41414141`41414141, %rip on failed CFG dispatch.

In general, most of the old-gen mitigations are not invariants, but rather cost-raisers. An encounter with them is often bypassable with the same bug and a bit of luck. Newer Chrome-specific mitigations tend to be more solid. If a bug is blocked by it, it's often game over – go find another one.

Further sections deal with the newer mitigations that I experienced.

MiraclePtr

MiraclePtr – also known as BackupRefPtr or BRP – is a fairly recent and ambitious addition to Chrome's list of exploit mitigations. It aims to make use-after-free bugs unexploitable without eliminating them, at the cost of manually wrapping each pointer in raw_ptr<T>, plus some runtime overhead. Design docs and developer's instructions are published [1 2 3], although it's best understood by reading the code, which is compact.

The security invariant offered by MiraclePtr: "as long as a dangling raw_ptr<T> exists, the slot is not reused". This is achieved by 1) counting pointer instances in a field under InSlotMetadata, and 2) quarantining the freed memory if InSlotMetadata counter isn't zero on free. Coverage is per-field and per-process, not whole-allocator.

BackupRefPtr code subsystem map
BackupRefPtr (MiraclePtr) code subsystem map

I first encountered MiraclePtr at the stage of vulnerability discovery. I found a bug by static analysis; it looked good until I tested it with Address Sanitizer...

MiraclePtr-PROTECTED crash in ASan
MiraclePtr-PROTECTED crash in ASan

That was surprising: I knew about raw_ptr<T> and didn't see it in the code? Turns out that MiraclePtr was recently (and quietly) rolled into some popular chromium idioms, so it's invisible in the code unless you look deep into familiar system internals. Sneaky!

I spent some time looking at the MiraclePtr implementation, and couldn't find a bypass in the given time. It's quite obvious that the code was heavily scrutinized and hedged against anything that could go wrong. I didn't bother reporting the bug – Google officially considers MiraclePtr-PROTECTED bugs strongly mitigated. On those Chromium builds that don't enable MiraclePtr mitigation, it's still an exploitable 0-day.

Meanwhile, my other bugs escaped MiraclePtr – they are UNPROTECTED and exploitable in release Chrome before the patch.

PartitionAlloc

PartitionAlloc manages all the memory in official Chrome, including C++ new operator, and excluding v8 (which uses Oilpan). PartitionAlloc has been ramping exploit mitigations for years, and by now is a bit of a beast. MiraclePtr implementation is based on PartitionAlloc infrastructure, too. Official documentation is in the chromium docs [4].

Out of many mitigations in PartitionAlloc, Thread Cache itself – as in, separate freelists per thread – doesn't have a reputation as an exploit blocker. Indeed, it was built as a performance optimization and never offered any strong security invariants. In reality, it quietly mitigates many bug shapes in the browser process.

PartitionAlloc ThreadCache code subsystem map
PartitionAlloc ThreadCache code subsystem map

I have dealt with Thread Cache directly and passed by encoded freelist pointers, cacheline poisoning (0xbadbad00), and scheduler loop quarantine (0xcdcdcdcd) when working on the exploit for a double-free issue in the browser process. The latter was surprising, since Scheduler Loop Quarantine isn't supposed to be enabled yet – it's actually enabled in Dev channel via field trials configuration.

Encoded freelist pointers in PartitionAlloc
Encoded freelist pointers in PartitionAlloc. Invalid memory access pattern: high-bits saturated, low-bits zero due to XOR'ing logic.
Cacheline poisoning (0xbadbad00) in PartitionAlloc Thread Cache
Cacheline poisoning (0xbadbad00) in PartitionAlloc Thread Cache

Consider the following scenario: a use-after-free bug in the browser process. As of current Chromium, the majority of objects in the browser process live in the same PA root partition, so reclaiming the freed object doesn't look hard in code. The problem is the multi-threading architecture of the browser process combined with the per-thread cache. This combination constrains object reclamation paths to those used in the vulnerable thread, which substantially shrinks the opportunity space to even begin to control the state machine from the bug. The following diagram illustrates the problem.

Browser process threads, ThreadCache, and cross-thread UAF reclaim architecture
Browser process threads, ThreadCache, and cross-thread UAF reclaim architecture

In this scenario, a bypass of Thread Cache depends on the bug's control flow graph escaping between free and re-use of the vulnerable object to allocate memory elsewhere to force a purge to the shared bucket. If the bug's code site is tight between free and re-use, it becomes unexploitable and heap grooming via another thread doesn't help. The same bug without Thread Cache would be exploitable. The specific bug that I worked with was of the former type and was effectively blocked by the mitigation in official Chrome.

v8 sandbox

v8 sandbox works by breaking common JavaScript Engine exploit idioms at a deep level of abstraction, which makes it efficient. Consider in-object pointer indirection, one of the key features of v8 sandbox. In a JavaScript engine, many object types have a pointer in them, which points to additional data (such as array contents in an ArrayBuffer). A typical JavaScript bug allows to craft a fake JavaScript object, which the unsuspecting engine then uses – dereferencing the fake inner pointer and thereby reading/writing to arbitrary memory whatever the JavaScript code requires. This allows to build an arbitrary RW primitive through the bug, a canonical JavaScript engine exploitation technique. The v8 sandbox replaces in-object pointers with indexes in out-of-sandbox tables, which breaks the exploit primitive universally. This feature alone isn't sufficient to break every exploit, and v8 sandbox doesn't end on it. Many official design docs are available [5 6].

V8 sandbox code subsystem map
V8 sandbox code subsystem map

All known public bypasses of the v8 sandbox, including 0-days, are bugs in the sandbox code. I looked at most of v8 sandbox bypasses (more than 80 issues in 2025), the bug patterns were eliminated systematically, and the implementation is substantially hardened now.

My renderer exploit bypasses the v8 sandbox by using a special kind of v8 bug that isn't fully covered by the sandbox. The exploit achieves arbitrary address read and write. This is an interesting attack surface for lateral thinking about attacking v8 in post-v8 sandbox era.

v8 exploit achieving arbitrary read/write
v8 exploit — full arbitrary write. Controlled registers with markers: %rax (0xbadd1e) and %rbx (0xc0ffee).

Conclusions

Chrome exploit mitigations continue to harden while being asymmetrically concentrated in the browser process and high-privilege processes, with the renderer and v8 now catching up.

Bypassing Chrome-specific mitigations head-on becomes nearly impossible; the open path lies through blind spots in coverage, platform-specific weaknesses, and bugs in mitigation code.

All current mitigations have limits, some of them structural (e.g., optimization). Those limits mark the edge of the practical attack surface.

Logic issues such as UXSS gain importance, as they are the least covered by current exploit mitigations.

Relevant Products

Browser Exploit Design — hands-on self-paced training in modern browser exploitation. Focus on class-wide models, canonical bug classes and exploit techniques, and full-chain perspective.

Pi — Pattern Insight — subscription intelligence service with in-depth research and analysis.

References

  1. MiraclePtr One Pager
  2. BackupRefPtr design document
  3. Developer docs — raw_ptr.md (source.chromium.org)
  4. PartitionAlloc docs (source.chromium.org)
  5. V8 Sandbox design document
  6. V8 sandbox README (source.chromium.org)
  7. Chrome Security Architecture diagram
Research Training
1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 • 0 • 1 •