CVE-2026-19138
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
BufferMemorySnapshotthird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc |
modified | |
ifthird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc |
modified | |
CapturingDelegatethird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc |
modified | |
forthird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc |
modified | |
TESTthird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc |
modified |
Files Changed
third_party/crashpad/README.chromiumthird_party/crashpad/crashpad/snapshot/BUILD.gnthird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.ccthird_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.ccthird_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad
Patch
From c1b901cc861d0d780e911e24d3ec30e42d0d3cee Mon Sep 17 00:00:00 2001
From: Mark Mentovai <mark@chromium.org>
Date: Tue, 28 Jul 2026 13:05:04 -0700
Subject: [PATCH] Update Crashpad to ad1827ddbc03f1c214030b31b380243ded660b95
526fd4a0352b Clamp aligned_offset in MemorySnapshotSanitized
ece2fa50a313 Fix MemorySnapshotSanitized for 32-bit builds, where size_t
≠ VMAddress
ad1827ddbc03 Update cpp-httplib to d66d9a95997d (0.51.0)
Bug: 500097298, 539815273
Change-Id: Ibda9f2ea42026623032310b5eb35e90cf453d8d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8164782
Commit-Queue: Mark Mentovai <mark@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1669684}
---
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index ae571d6..8d2e2fda 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
Short Name: crashpad
URL: https://chromium.googlesource.com/crashpad/crashpad
Version: N/A
-Revision: efdc820b087c20eec9e32cb5e5b1a63dcf73a724
+Revision: ad1827ddbc03f1c214030b31b380243ded660b95
Update Mechanism: Manual
License: Apache-2.0
License File: crashpad/LICENSE
diff --git a/third_party/crashpad/crashpad/snapshot/BUILD.gn b/third_party/crashpad/crashpad/snapshot/BUILD.gn
index 4abda6b..196a68ef 100644
--- a/third_party/crashpad/crashpad/snapshot/BUILD.gn
+++ b/third_party/crashpad/crashpad/snapshot/BUILD.gn
@@ -378,6 +378,7 @@
"linux/system_snapshot_linux_test.cc",
"linux/test_modules.cc",
"linux/test_modules.h",
+ "sanitized/memory_snapshot_sanitized_test.cc",
"sanitized/process_snapshot_sanitized_test.cc",
"sanitized/sanitization_information_test.cc",
]
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.cc b/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.cc
index 58bcdde42..632d010 100644
--- a/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.cc
+++ b/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.cc
@@ -16,6 +16,8 @@
#include <string.h>
+#include <algorithm>
+
#include "util/linux/pac_helper.h"
namespace crashpad {
@@ -55,8 +57,9 @@
static_cast<Pointer>(MemorySnapshotSanitized::kDefaced);
// Sanitize up to a word-aligned address.
- const size_t aligned_offset =
- ((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_;
+ const size_t aligned_offset = std::min<VMAddress>(
+ size,
+ ((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_);
memcpy(data, &defaced, aligned_offset);
// Sanitize words that aren't small and don't look like pointers.
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc b/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc
new file mode 100644
index 0000000..2a41397b
--- /dev/null
+++ b/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc
@@ -0,0 +1,115 @@
+// Copyright 2026 The Crashpad Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "snapshot/sanitized/memory_snapshot_sanitized.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <vector>
+
+#include "base/containers/heap_array.h"
+#include "gtest/gtest.h"
+#include "util/misc/range_set.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr uint8_t kFillByte = 0x55;
+
+class BufferMemorySnapshot final : public MemorySnapshot {
+ public:
+ BufferMemorySnapshot(uint64_t address, size_t size)
+ : address_(address), size_(size) {}
+
+ BufferMemorySnapshot(const BufferMemorySnapshot&) = delete;
+ BufferMemorySnapshot& operator=(const BufferMemorySnapshot&) = delete;
+
+ uint64_t Address() const override { return address_; }
+ size_t Size() const override { return size_; }
+
+ bool Read(Delegate* delegate) const override {
+ if (size_ == 0) {
+ return delegate->MemorySnapshotDelegateRead(nullptr, 0);
+ }
+ auto buffer = base::HeapArray<uint8_t>::Uninit(size_);
+ memset(buffer.data(), kFillByte, buffer.size());
+ return delegate->MemorySnapshotDelegateRead(buffer.data(), buffer.size());
+ }
+
+ const MemorySnapshot* MergeWithOtherSnapshot(
+ const MemorySnapshot*) const override {
+ return nullptr;
+ }
+
+ private:
+ uint64_t address_;
+ size_t size_;
+};
+
+class CapturingDelegate : public MemorySnapshot::Delegate {
+ public:
+ bool MemorySnapshotDelegateRead(void* data, size_t size) override {
+ captured_.assign(static_cast<uint8_t*>(data),
+ static_cast<uint8_t*>(data) + size);
+ return true;
+ }
+
+ const std::vector<uint8_t>& captured() const { return captured_; }
+
+ private:
+ std::vector<uint8_t> captured_;
+};
+
+void ExpectSanitizedShortUnalignedRegion(uint64_t address,
+ size_t size,
+ bool is_64_bit) {
+ BufferMemorySnapshot wrapped(address, size);
+ RangeSet ranges;
+ internal::MemorySnapshotSanitized sanitized(&wrapped, &ranges, is_64_bit);
+
+ CapturingDelegate delegate;
+ ASSERT_TRUE(sanitized.Read(&delegate));
+ ASSERT_EQ(delegate.captured().size(), size);
+
+ // The region is shorter than a word and contains no pointer-aligned word, so
+ // every byte should have been defaced.
+ for (size_t index = 0; index < size; ++index) {
+ EXPECT_NE(delegate.captured()[index], kFillByte)
+ << "address=" << address << " size=" << size << " index=" << index;
+ }
+}
+
+TEST(MemorySnapshotSanitized, ShortUnalignedRegion64) {
+ for (uint64_t offset = 1; offset < sizeof(uint64_t); ++offset) {
+ for (size_t size = 1; size < sizeof(uint64_t); ++size) {
+ ExpectSanitizedShortUnalignedRegion(
+ 0x1000 + offset, size, /*is_64_bit=*/true);
+ }
+ }
+}
+
+TEST(MemorySnapshotSanitized, ShortUnalignedRegion32) {
+ for (uint64_t offset = 1; offset < sizeof(uint32_t); ++offset) {
+ for (size_t size = 1; size < sizeof(uint32_t); ++size) {
+ ExpectSanitizedShortUnalignedRegion(
+ 0x1000 + offset, size, /*is_64_bit=*/false);
+ }
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad b/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad
index c1d03f9..24bb49e 100644
--- a/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad
+++ b/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad
@@ -1,8 +1,8 @@
Name: cpp-httplib
Short Name: cpp-httplib
URL: https://github.com/yhirose/cpp-httplib
-Version: 0.27.0
-Revision: eacc1ca98e5fef25184c7d417e8417225e05e65d
+Version: 0.51.0
Regression Test / PoC
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc b/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc
new file mode 100644
index 0000000..2a41397b
--- /dev/null
+++ b/third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized_test.cc
@@ -0,0 +1,115 @@
+// Copyright 2026 The Crashpad Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "snapshot/sanitized/memory_snapshot_sanitized.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <vector>
+
+#include "base/containers/heap_array.h"
+#include "gtest/gtest.h"
+#include "util/misc/range_set.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+constexpr uint8_t kFillByte = 0x55;
+
+class BufferMemorySnapshot final : public MemorySnapshot {
+ public:
+ BufferMemorySnapshot(uint64_t address, size_t size)
+ : address_(address), size_(size) {}
+
+ BufferMemorySnapshot(const BufferMemorySnapshot&) = delete;
+ BufferMemorySnapshot& operator=(const BufferMemorySnapshot&) = delete;
+
+ uint64_t Address() const override { return address_; }
+ size_t Size() const override { return size_; }
+
+ bool Read(Delegate* delegate) const override {
+ if (size_ == 0) {
+ return delegate->MemorySnapshotDelegateRead(nullptr, 0);
+ }
+ auto buffer = base::HeapArray<uint8_t>::Uninit(size_);
+ memset(buffer.data(), kFillByte, buffer.size());
+ return delegate->MemorySnapshotDelegateRead(buffer.data(), buffer.size());
+ }
+
+ const MemorySnapshot* MergeWithOtherSnapshot(
+ const MemorySnapshot*) const override {
+ return nullptr;
+ }
+
+ private:
+ uint64_t address_;
+ size_t size_;
+};
+
+class CapturingDelegate : public MemorySnapshot::Delegate {
+ public:
+ bool MemorySnapshotDelegateRead(void* data, size_t size) override {
+ captured_.assign(static_cast<uint8_t*>(data),
+ static_cast<uint8_t*>(data) + size);
+ return true;
+ }
+
+ const std::vector<uint8_t>& captured() const { return captured_; }
+
+ private:
+ std::vector<uint8_t> captured_;
+};
+
+void ExpectSanitizedShortUnalignedRegion(uint64_t address,
+ size_t size,
+ bool is_64_bit) {
+ BufferMemorySnapshot wrapped(address, size);
+ RangeSet ranges;
+ internal::MemorySnapshotSanitized sanitized(&wrapped, &ranges, is_64_bit);
+
+ CapturingDelegate delegate;
+ ASSERT_TRUE(sanitized.Read(&delegate));
+ ASSERT_EQ(delegate.captured().size(), size);
+
+ // The region is shorter than a word and contains no pointer-aligned word, so
+ // every byte should have been defaced.
+ for (size_t index = 0; index < size; ++index) {
+ EXPECT_NE(delegate.captured()[index], kFillByte)
+ << "address=" << address << " size=" << size << " index=" << index;
+ }
+}
+
+TEST(MemorySnapshotSanitized, ShortUnalignedRegion64) {
+ for (uint64_t offset = 1; offset < sizeof(uint64_t); ++offset) {
+ for (size_t size = 1; size < sizeof(uint64_t); ++size) {
+ ExpectSanitizedShortUnalignedRegion(
+ 0x1000 + offset, size, /*is_64_bit=*/true);
+ }
+ }
+}
+
+TEST(MemorySnapshotSanitized, ShortUnalignedRegion32) {
+ for (uint64_t offset = 1; offset < sizeof(uint32_t); ++offset) {
+ for (size_t size = 1; size < sizeof(uint32_t); ++size) {
+ ExpectSanitizedShortUnalignedRegion(
+ 0x1000 + offset, size, /*is_64_bit=*/false);
+ }
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad
Original Bug Report
Heap OOB Write and Integer Underflow in Crashpad MemorySanitizer
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: A compromised renderer can trigger a heap out-of-bounds write and massive memory corruption in the unsandboxed chrome_crashpad_handler by requesting a stack-sanitized crash dump. By manipulating a thread’s Stack Pointer and TLS registers, the attacker forces an integer underflow during buffer sanitization. While this heavily corrupts memory and guarantees an eventual crash, exploiting a race condition could theoretically lead to a sandbox escape.
Affected files:
third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.ccthird_party/crashpad/crashpad/snapshot/memory_snapshot_generic.h
Estimated timestamp from git blame: 2023-04-10
Summary
A potential heap out-of-bounds (OOB) write and integer underflow exists in the stack sanitization logic of Crashpad. The vulnerability occurs when the MemorySanitizer processes a highly specific, attacker-controlled stack memory region that is very small (e.g., 1 byte) and unaligned. This can be triggered by a compromised renderer process, potentially leading to a sandbox escape via memory corruption in the privileged chrome_crashpad_handler process.
Root Cause Analysis
The vulnerability is located in third_party/crashpad/crashpad/snapshot/sanitized/memory_snapshot_sanitized.cc within the MemorySanitizer::Sanitize template function:
// Sanitize up to a word-aligned address.
const size_t aligned_offset =
((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_;
memcpy(data, &defaced, aligned_offset);
// Sanitize words that aren't small and don't look like pointers.
size_t word_count = (size - aligned_offset) / sizeof(Pointer);
The aligned_offset is calculated as the distance from the region’s base address_ to the next word-aligned boundary. For a 64-bit architecture, this can be up to 7 bytes.
The function crucially assumes that size >= aligned_offset. If an attacker can force a situation where size < aligned_offset, two critical memory corruptions occur:
- Heap OOB Write: The
memcpywritesaligned_offsetbytes (e.g., 7) of thedefacedconstant intodata. If the underlying heap buffer is onlysizebytes (e.g., 1 byte), this overflows the buffer. - Integer Underflow: The calculation
(size - aligned_offset)uses unsigned arithmetic (size_t). Whensize < aligned_offset, this underflows to a massive value (e.g.,~0x1FFFFFFFFFFFFFFE). The subsequent loop iterates over this massiveword_count, blindly overwriting vast swathes of the handler’s heap memory with0x0defaced0defaceduntil the process hits an unmapped page and crashes.
Potential Attack Steps
Note: These are suggested steps; we do not currently have a working proof of concept to run this exploit end-to-end.
- A compromised renderer process creates a new thread.
- The attacker manipulates the thread’s registers to craft an invalid stack layout:
- Sets the Stack Pointer (SP) to an unaligned memory address
X, whereX % 8 == 1. - Sets the Thread Local Storage (TLS) register to
X + 1.
- Sets the Stack Pointer (SP) to an unaligned memory address
- The attacker creates a
SanitizationInformationstruct in its memory and setssanitize_stacks = true. - The attacker sends a
kTypeCrashDumpRequestIPC to the unsandboxedchrome_crashpad_handler, pointing to this struct. - The handler’s
ProcessReaderLinux::Thread::InitializeStackFromSPlogic calculates the thread’s stack size astls_address - stack_region_address. Due to the attacker’s setup (X + 1minusX), the stack size is evaluated as1byte. - The handler allocates a 1-byte heap buffer for the stack snapshot and passes it to
MemorySanitizer::Sanitize. Sanitizeevaluatesaligned_offsetas7, triggering the out-of-boundsmemcpyand the integer underflow.- The massive memory corruption loop races against other handler threads (like the upload thread). If the attacker can win a race condition using the corrupted heap state before the inevitable segmentation fault, this could result in arbitrary code execution (sandbox escape).
Suggested Fix
Add bounds checking in MemorySanitizer::Sanitize to ensure that aligned_offset does not exceed the buffer size.
// Sanitize up to a word-aligned address, bounded by the region size.
const size_t aligned_offset = std::min(
size,
((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_);
memcpy(data, &defaced, aligned_offset);
// Sanitize words that aren't small and don't look like pointers.
size_t word_count = (size - aligned_offset) / sizeof(Pointer);
Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.