// Copyright 2015 The Crashpad Authors. All rights reserved. // // 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 "util/mach/exception_types.h" #include #include #include #include #include #include "base/cxx17_backports.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "util/mac/mac_util.h" #include "util/mach/mach_extensions.h" namespace crashpad { namespace test { namespace { TEST(ExceptionTypes, ExcCrashRecoverOriginalException) { static constexpr struct { mach_exception_code_t code_0; exception_type_t exception; mach_exception_code_t original_code_0; int signal; } kTestData[] = { {0xb100001, EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, SIGSEGV}, {0xb100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGSEGV}, {0xa100002, EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE, SIGBUS}, {0xa100005, EXC_BAD_ACCESS, VM_PROT_READ | VM_PROT_EXECUTE, SIGBUS}, {0x9100032, EXC_BAD_ACCESS, KERN_CODESIGN_ERROR, SIGKILL}, {0x0700080, EXC_SYSCALL, 128, 0}, {0x0706000, EXC_SYSCALL, 0x6000, 0}, {0x3000000, 0, 0, SIGQUIT}, {0x4000000, 0, 0, SIGILL}, {0x5000000, 0, 0, SIGTRAP}, {0x6000000, 0, 0, SIGABRT}, {0x7000000, 0, 0, SIGEMT}, {0x8000000, 0, 0, SIGFPE}, {0xa000000, 0, 0, SIGBUS}, {0xb000000, 0, 0, SIGSEGV}, {0xc000000, 0, 0, SIGSYS}, {0, 0, 0, 0}, #if defined(ARCH_CPU_X86_FAMILY) {0xa10000d, EXC_BAD_ACCESS, EXC_I386_GPFLT, SIGBUS}, {0x4200001, EXC_BAD_INSTRUCTION, EXC_I386_INVOP, SIGILL}, {0x420000b, EXC_BAD_INSTRUCTION, EXC_I386_SEGNPFLT, SIGILL}, {0x420000c, EXC_BAD_INSTRUCTION, EXC_I386_STKFLT, SIGILL}, {0x8300001, EXC_ARITHMETIC, EXC_I386_DIV, SIGFPE}, {0x8300002, EXC_ARITHMETIC, EXC_I386_INTO, SIGFPE}, {0x8300005, EXC_ARITHMETIC, EXC_I386_EXTERR, SIGFPE}, {0x8300008, EXC_ARITHMETIC, EXC_I386_SSEEXTERR, SIGFPE}, {0x5500007, EXC_SOFTWARE, EXC_I386_BOUND, SIGTRAP}, {0x5600001, EXC_BREAKPOINT, EXC_I386_SGL, SIGTRAP}, {0x5600002, EXC_BREAKPOINT, EXC_I386_BPT, SIGTRAP}, #endif // TODO(macos_arm64): Add arm64 test data. }; for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& test_data = kTestData[index]; SCOPED_TRACE(base::StringPrintf( "index %zu, code_0 0x%llx", index, test_data.code_0)); mach_exception_code_t original_code_0; int signal; exception_type_t exception = ExcCrashRecoverOriginalException( test_data.code_0, &original_code_0, &signal); EXPECT_EQ(exception, test_data.exception); EXPECT_EQ(original_code_0, test_data.original_code_0); EXPECT_EQ(signal, test_data.signal); } // Now make sure that ExcCrashRecoverOriginalException() properly ignores // optional arguments. static_assert(base::size(kTestData) >= 1, "must have something to test"); const auto& test_data = kTestData[0]; EXPECT_EQ( ExcCrashRecoverOriginalException(test_data.code_0, nullptr, nullptr), test_data.exception); mach_exception_code_t original_code_0; EXPECT_EQ(ExcCrashRecoverOriginalException( test_data.code_0, &original_code_0, nullptr), test_data.exception); EXPECT_EQ(original_code_0, test_data.original_code_0); int signal; EXPECT_EQ( ExcCrashRecoverOriginalException(test_data.code_0, nullptr, &signal), test_data.exception); EXPECT_EQ(signal, test_data.signal); } TEST(ExceptionTypes, ExcCrashCouldContainException) { // This seems wrong, but it happens when EXC_CRASH carries an exception not // originally caused by a hardware fault, such as SIGABRT. EXPECT_TRUE(ExcCrashCouldContainException(0)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_BAD_ACCESS)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_BAD_INSTRUCTION)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_ARITHMETIC)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_EMULATION)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_SOFTWARE)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_BREAKPOINT)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_SYSCALL)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_MACH_SYSCALL)); EXPECT_TRUE(ExcCrashCouldContainException(EXC_RPC_ALERT)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_CRASH)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_RESOURCE)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_GUARD)); EXPECT_FALSE(ExcCrashCouldContainException(EXC_CORPSE_NOTIFY)); EXPECT_FALSE(ExcCrashCouldContainException(kMachExceptionSimulated)); } // This macro is adapted from those in the #ifdef KERNEL section of // : 10.12.2 xnu-3789.31.2/osfmk/kern/exc_resource.h. #define EXC_RESOURCE_ENCODE_TYPE_FLAVOR(type, flavor) \ (static_cast( \ (((static_cast(type) & 0x7ull) << 61) | \ (static_cast(flavor) & 0x7ull) << 58))) TEST(ExceptionTypes, ExceptionCodeForMetrics) { static constexpr struct { exception_type_t exception; mach_exception_code_t code_0; int32_t metrics_code; } kTestData[] = { #define ENCODE_EXC(type, code_0) \ { (type), (code_0), ((type) << 16) | (code_0) } ENCODE_EXC(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS), ENCODE_EXC(EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE), ENCODE_EXC(EXC_BAD_ACCESS, VM_PROT_READ | VM_PROT_EXECUTE), ENCODE_EXC(EXC_BAD_ACCESS, KERN_CODESIGN_ERROR), ENCODE_EXC(EXC_SYSCALL, 128), ENCODE_EXC(EXC_SYSCALL, 0x6000), #if defined(ARCH_CPU_X86_FAMILY) ENCODE_EXC(EXC_BAD_ACCESS, EXC_I386_GPFLT), ENCODE_EXC(EXC_BAD_INSTRUCTION, EXC_I386_INVOP), ENCODE_EXC(EXC_BAD_INSTRUCTION, EXC_I386_SEGNPFLT), ENCODE_EXC(EXC_BAD_INSTRUCTION, EXC_I386_STKFLT), ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_DIV), ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_INTO), ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_EXTERR), ENCODE_EXC(EXC_ARITHMETIC, EXC_I386_SSEEXTERR), ENCODE_EXC(EXC_SOFTWARE, EXC_I386_BOUND), ENCODE_EXC(EXC_BREAKPOINT, EXC_I386_SGL), ENCODE_EXC(EXC_BREAKPOINT, EXC_I386_BPT), #endif // TODO(macos_arm64): Add arm64 test data. #undef ENCODE_EXC #define ENCODE_EXC_CRASH(type, code_0) \ { \ EXC_CRASH, (((type) & 0xf) << 20) | ((code_0) & 0xfffff), \ ((type) << 16) | (code_0) \ } ENCODE_EXC_CRASH(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS), ENCODE_EXC_CRASH(EXC_BAD_ACCESS, KERN_PROTECTION_FAILURE), ENCODE_EXC_CRASH(EXC_BAD_ACCESS, VM_PROT_READ | VM_PROT_EXECUTE), ENCODE_EXC_CRASH(EXC_BAD_ACCESS, KERN_CODESIGN_ERROR), ENCODE_EXC_CRASH(EXC_SYSCALL, 128), ENCODE_EXC_CRASH(EXC_SYSCALL, 0x6000), #if defined(ARCH_CPU_X86_FAMILY) ENCODE_EXC_CRASH(EXC_BAD_ACCESS, EXC_I386_GPFLT), ENCODE_EXC_CRASH(EXC_BAD_INSTRUCTION, EXC_I386_INVOP), ENCODE_EXC_CRASH(EXC_BAD_INSTRUCTION, EXC_I386_SEGNPFLT), ENCODE_EXC_CRASH(EXC_BAD_INSTRUCTION, EXC_I386_STKFLT), ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_DIV), ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_INTO), ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_EXTERR), ENCODE_EXC_CRASH(EXC_ARITHMETIC, EXC_I386_SSEEXTERR), ENCODE_EXC_CRASH(EXC_SOFTWARE, EXC_I386_BOUND), ENCODE_EXC_CRASH(EXC_BREAKPOINT, EXC_I386_SGL), ENCODE_EXC_CRASH(EXC_BREAKPOINT, EXC_I386_BPT), #endif // TODO(macos_arm64): Add arm64 test data. #undef ENCODE_EXC_CRASH #define ENCODE_EXC_CRASH_SIGNAL(signal) \ { EXC_CRASH, (((signal) & 0xff) << 24), (EXC_CRASH << 16) | (signal) } ENCODE_EXC_CRASH_SIGNAL(SIGQUIT), ENCODE_EXC_CRASH_SIGNAL(SIGILL), ENCODE_EXC_CRASH_SIGNAL(SIGTRAP), ENCODE_EXC_CRASH_SIGNAL(SIGABRT), ENCODE_EXC_CRASH_SIGNAL(SIGEMT), ENCODE_EXC_CRASH_SIGNAL(SIGFPE), ENCODE_EXC_CRASH_SIGNAL(SIGBUS), ENCODE_EXC_CRASH_SIGNAL(SIGSEGV), ENCODE_EXC_CRASH_SIGNAL(SIGSYS), #undef ENCODE_EXC_CRASH_SIGNAL #define ENCODE_EXC_RESOURCE(type, flavor) \ { \ EXC_RESOURCE, EXC_RESOURCE_ENCODE_TYPE_FLAVOR((type), (flavor)), \ (EXC_RESOURCE << 16) | ((type) << 8) | (flavor) \ } ENCODE_EXC_RESOURCE(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR), ENCODE_EXC_RESOURCE(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR_FATAL), ENCODE_EXC_RESOURCE(RESOURCE_TYPE_WAKEUPS, FLAVOR_WAKEUPS_MONITOR), ENCODE_EXC_RESOURCE(RESOURCE_TYPE_MEMORY, FLAVOR_HIGH_WATERMARK), ENCODE_EXC_RESOURCE(RESOURCE_TYPE_IO, FLAVOR_IO_PHYSICAL_WRITES), ENCODE_EXC_RESOURCE(RESOURCE_TYPE_IO, FLAVOR_IO_LOGICAL_WRITES), #undef ENCODE_EXC_RESOURCE #define ENCODE_EXC_GUARD(type, flavor) \ { \ EXC_GUARD, \ static_cast(static_cast((type) & 0x7) \ << 61) | \ (static_cast((1 << (flavor)) & 0x1ffffffff) << 32), \ (EXC_GUARD << 16) | ((type) << 8) | (flavor) \ } ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 0), // kGUARD_EXC_DESTROY ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 1), // kGUARD_EXC_MOD_REFS ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 2), // kGUARD_EXC_SET_CONTEXT ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 3), // kGUARD_EXC_UNGUARDED ENCODE_EXC_GUARD(GUARD_TYPE_MACH_PORT, 4), // kGUARD_EXC_INCORRECT_GUARD // 2 is GUARD_TYPE_FD from 10.12.2 xnu-3789.31.2/bsd/sys/guarded.h. ENCODE_EXC_GUARD(2, 0), // kGUARD_EXC_CLOSE ENCODE_EXC_GUARD(2, 1), // kGUARD_EXC_DUP ENCODE_EXC_GUARD(2, 2), // kGUARD_EXC_NOCLOEXEC ENCODE_EXC_GUARD(2, 3), // kGUARD_EXC_SOCKET_IPC ENCODE_EXC_GUARD(2, 4), // kGUARD_EXC_FILEPORT ENCODE_EXC_GUARD(2, 5), // kGUARD_EXC_MISMATCH ENCODE_EXC_GUARD(2, 6), // kGUARD_EXC_WRITE #undef ENCODE_EXC_GUARD // Test that overflow saturates. {0x00010000, 0x00001000, static_cast(0xffff1000)}, {0x00001000, 0x00010000, 0x1000ffff}, {0x00010000, 0x00010000, static_cast(0xffffffff)}, }; for (size_t index = 0; index < base::size(kTestData); ++index) { const auto& test_data = kTestData[index]; SCOPED_TRACE(base::StringPrintf("index %zu, exception 0x%x, code_0 0x%llx", index, test_data.exception, test_data.code_0)); int32_t metrics_code = ExceptionCodeForMetrics(test_data.exception, test_data.code_0); EXPECT_EQ(metrics_code, test_data.metrics_code); } } TEST(ExceptionTypes, IsExceptionNonfatalResource) { const pid_t pid = getpid(); mach_exception_code_t code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR); EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid)); if (MacOSVersionNumber() >= 10'10'00) { // FLAVOR_CPU_MONITOR_FATAL was introduced in OS X 10.10. code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_CPU, FLAVOR_CPU_MONITOR_FATAL); EXPECT_FALSE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid)); } // This assumes that WAKEMON_MAKE_FATAL is not set for this process. The // default is for WAKEMON_MAKE_FATAL to not be set, there’s no public API to // enable it, and nothing in this process should have enabled it. code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_WAKEUPS, FLAVOR_WAKEUPS_MONITOR); EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid)); code = EXC_RESOURCE_ENCODE_TYPE_FLAVOR(RESOURCE_TYPE_MEMORY, FLAVOR_HIGH_WATERMARK); EXPECT_TRUE(IsExceptionNonfatalResource(EXC_RESOURCE, code, pid)); // Non-EXC_RESOURCE exceptions should never be considered nonfatal resource // exceptions, because they aren’t resource exceptions at all. EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0xb100001, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0x0b00000, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(EXC_CRASH, 0x6000000, pid)); EXPECT_FALSE( IsExceptionNonfatalResource(EXC_BAD_ACCESS, KERN_INVALID_ADDRESS, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(EXC_BAD_INSTRUCTION, 1, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(EXC_ARITHMETIC, 1, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(EXC_BREAKPOINT, 2, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(0, 0, pid)); EXPECT_FALSE(IsExceptionNonfatalResource(kMachExceptionSimulated, 0, pid)); } } // namespace } // namespace test } // namespace crashpad