Catch heap corruption failures on Windows

Windows claims that heap corruption crashes are passed
to Windows Error Reporting but they are not, they are
swallowed and the process is simply terminated. WerFault.exe
does not run.

We can however intercept these crashes using a vectored
exception handler which forwards STATUS_HEAP_CORRUPTION
to the normal crash handler.

Adds an end-to-end test.

Bug: 2515
Change-Id: I2e1361dacef6fd03ea0f00327fee0b05a0c4899e
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4637533
Commit-Queue: Alex Gough <ajgo@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Alex Gough 2023-06-23 14:53:06 -07:00 committed by Crashpad LUCI CQ
parent bc1e904f09
commit a5e179663a
4 changed files with 148 additions and 3 deletions

View File

@ -187,6 +187,15 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
LONG WINAPI HandleHeapCorruption(EXCEPTION_POINTERS* exception_pointers) {
if (exception_pointers->ExceptionRecord->ExceptionCode ==
STATUS_HEAP_CORRUPTION) {
return UnhandledExceptionHandler(exception_pointers);
}
return EXCEPTION_CONTINUE_SEARCH;
}
void HandleAbortSignal(int signum) { void HandleAbortSignal(int signum) {
DCHECK_EQ(signum, SIGABRT); DCHECK_EQ(signum, SIGABRT);
@ -580,6 +589,16 @@ void CommonInProcessInitialization() {
void RegisterHandlers() { void RegisterHandlers() {
SetUnhandledExceptionFilter(&UnhandledExceptionHandler); SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
// Windows swallows heap corruption failures but we can intercept them with
// a vectored exception handler.
#if defined(ADDRESS_SANITIZER)
// Let ASAN have first go.
bool go_first = false;
#else
bool go_first = true;
#endif
AddVectoredExceptionHandler(go_first, HandleHeapCorruption);
// The Windows CRT's signal.h lists: // The Windows CRT's signal.h lists:
// - SIGINT // - SIGINT
// - SIGILL // - SIGILL

View File

@ -49,10 +49,9 @@ static_library("handler") {
"linux/cros_crash_report_exception_handler.cc", "linux/cros_crash_report_exception_handler.cc",
"linux/cros_crash_report_exception_handler.h", "linux/cros_crash_report_exception_handler.h",
] ]
# TODO(https://crbug.com/1420445): Remove this config when M115 branches. # TODO(https://crbug.com/1420445): Remove this config when M115 branches.
configs += [ configs += [ "../build:crashpad_is_in_chromium" ]
"../build:crashpad_is_in_chromium",
]
} }
if (crashpad_is_win) { if (crashpad_is_win) {
@ -346,6 +345,19 @@ if (crashpad_is_win) {
] ]
} }
crashpad_executable("heap_corrupting_program") {
testonly = true
sources = [ "win/heap_corrupting_program.cc" ]
deps = [
"../client",
"../compat",
"../snapshot",
"../third_party/mini_chromium:base",
]
}
if (current_cpu == "x86") { if (current_cpu == "x86") {
# Cannot create an x64 DLL with embedded debug info. # Cannot create an x64 DLL with embedded debug info.
crashpad_executable("crashy_z7_loader") { crashpad_executable("crashy_z7_loader") {

View File

@ -0,0 +1,95 @@
// Copyright 2023 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 <string.h>
#include "base/files/file_path.h"
#include "base/logging.h"
#include "client/crashpad_client.h"
#include "util/misc/paths.h"
#include <Windows.h>
// We set up a program that crashes with a heap corruption exception.
// STATUS_HEAP_CORRUPTION (0xC0000374 3221226356).
namespace crashpad {
namespace {
void HeapCorruptionCrash() {
__try {
HANDLE heap = ::HeapCreate(0, 0, 0);
CHECK(heap);
CHECK(HeapSetInformation(
heap, HeapEnableTerminationOnCorruption, nullptr, 0));
void* addr = ::HeapAlloc(heap, 0, 0x1000);
CHECK(addr);
// Corrupt heap header.
char* addr_mutable = reinterpret_cast<char*>(addr);
memset(addr_mutable - sizeof(addr), 0xCC, sizeof(addr));
HeapFree(heap, 0, addr);
HeapDestroy(heap);
} __except (EXCEPTION_EXECUTE_HANDLER) {
// Heap corruption exception should never be caught.
CHECK(false);
}
// Should never reach here.
abort();
}
int CrashyMain(int argc, wchar_t* argv[]) {
static CrashpadClient* client = new crashpad::CrashpadClient();
if (argc == 2) {
// We call this from end_to_end_test.py.
if (!client->SetHandlerIPCPipe(argv[1])) {
LOG(ERROR) << "SetHandler";
return EXIT_FAILURE;
}
} else if (argc == 3) {
// This is helpful for debugging.
if (!client->StartHandler(base::FilePath(argv[1]),
base::FilePath(argv[2]),
base::FilePath(),
std::string(),
std::map<std::string, std::string>(),
std::vector<std::string>(),
false,
true)) {
LOG(ERROR) << "StartHandler";
return EXIT_FAILURE;
}
// Got to have a handler & registration.
if (!client->WaitForHandlerStart(10000)) {
LOG(ERROR) << "Handler failed to start";
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "Usage: %ls <server_pipe_name>\n", argv[0]);
fprintf(stderr, " %ls <handler_path> <database_path>\n", argv[0]);
return EXIT_FAILURE;
}
HeapCorruptionCrash();
LOG(ERROR) << "Invalid type or exception failed.";
return EXIT_FAILURE;
}
} // namespace
} // namespace crashpad
int wmain(int argc, wchar_t* argv[]) {
return crashpad::CrashyMain(argc, argv);
}

View File

@ -212,6 +212,12 @@ def GetDumpFromZ7Program(out_dir, pipe_name):
win32con.EXCEPTION_ACCESS_VIOLATION) win32con.EXCEPTION_ACCESS_VIOLATION)
def GetDumpFromHeapCorruptingProgram(out_dir, pipe_name):
STATUS_HEAP_CORRUPTION = 0xC0000374
return GetDumpFromProgram(out_dir, pipe_name, 'heap_corrupting_program.exe',
STATUS_HEAP_CORRUPTION)
def GetDumpFromFastFailProgram(out_dir, pipe_name, *args): def GetDumpFromFastFailProgram(out_dir, pipe_name, *args):
STATUS_STACK_BUFFER_OVERRUN = 0xc0000409 STATUS_STACK_BUFFER_OVERRUN = 0xc0000409
return GetDumpFromProgram(out_dir, pipe_name, 'fastfail_program.exe', return GetDumpFromProgram(out_dir, pipe_name, 'fastfail_program.exe',
@ -444,6 +450,14 @@ def RunSigAbrtTest(cdb_path, sigabrt_main_path, sigabrt_background_path):
out.Check('code 40000015', 'got sigabrt signal from background thread') out.Check('code 40000015', 'got sigabrt signal from background thread')
def RunHeapCorruptionTest(cdb_path, heap_path):
"""Runs tests on heap corruption caught using the vectored handler."""
out = CdbRun(cdb_path, heap_path, '.ecxr;k')
out.Check('code c0000374', 'captured exception from heap corruption crash')
out.Check('::HeapCorruptionCrash', 'See expected throwing function')
out = CdbRun(cdb_path, heap_path, '.ecxr;k')
def RunFastFailDumpTest(cdb_path, fastfail_path): def RunFastFailDumpTest(cdb_path, fastfail_path):
"""Runs tests on __fastfail() caught using the runtime exception helper.""" """Runs tests on __fastfail() caught using the runtime exception helper."""
out = CdbRun(cdb_path, fastfail_path, '.ecxr;k') out = CdbRun(cdb_path, fastfail_path, '.ecxr;k')
@ -541,6 +555,11 @@ def main(args):
return 1 return 1
Run7zDumpTest(cdb_path, z7_dump_path) Run7zDumpTest(cdb_path, z7_dump_path)
heap_path = GetDumpFromHeapCorruptingProgram(args[0], pipe_name)
if not heap_path:
return 1
RunHeapCorruptionTest(cdb_path, heap_path)
# __fastfail() & CFG crash caught by WerRuntimeExceptionHelperModule. # __fastfail() & CFG crash caught by WerRuntimeExceptionHelperModule.
# TODO(crashpad:458) These are not working when launched from python. # TODO(crashpad:458) These are not working when launched from python.
if (False and Win32_20H1()): if (False and Win32_20H1()):