linux, mac: disable cfi-icall for cross-dso calls
CFI attempts to verify that the dynamic type of a function object
matches the static type of the function pointer used to call it.
https://clang.llvm.org/docs/ControlFlowIntegrity.html#indirect-function-call-checking
However, the analyzer does not have enough information to check
cross-dso calls. In these instances, CFI crashes upon calling the
function with an error like:
pthread_create_linux.cc:60:16: runtime error:
control flow integrity check for type
'int (unsigned long *, const pthread_attr_t *, void *(*)(void *), void *)'
failed during indirect function call
(/lib/x86_64-linux-gnu/libpthread.so.0+0x9200):
note: (unknown) defined here pthread_create_linux.cc:60:16:
note: check failed in crashpad_handler,
destination function located in /lib/x86_64-linux-gnu/libpthread.so.0
Change-Id: Ib29dabfe714f2ee9cc06a5d17e6899ff81a06df4
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2339332
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
diff --git a/client/pthread_create_linux.cc b/client/pthread_create_linux.cc
index d5a60fe..df7aac4 100644
--- a/client/pthread_create_linux.cc
+++ b/client/pthread_create_linux.cc
@@ -17,6 +17,7 @@
#include "base/logging.h"
#include "client/crashpad_client.h"
+#include "util/misc/no_cfi_icall.h"
namespace {
@@ -45,13 +46,12 @@
const pthread_attr_t* attr,
StartRoutineType start_routine,
void* arg) {
- static const auto next_pthread_create = []() {
- const auto next_pthread_create =
- reinterpret_cast<decltype(pthread_create)*>(
- dlsym(RTLD_NEXT, "pthread_create"));
- CHECK(next_pthread_create) << "dlsym: " << dlerror();
- return next_pthread_create;
- }();
+ static const crashpad::NoCfiIcall<decltype(pthread_create)*>
+ next_pthread_create([]() {
+ const auto next_pthread_create = dlsym(RTLD_NEXT, "pthread_create");
+ CHECK(next_pthread_create) << "dlsym: " << dlerror();
+ return next_pthread_create;
+ }());
StartParams* params = new StartParams;
params->start_routine = start_routine;
diff --git a/compat/BUILD.gn b/compat/BUILD.gn
index d98a6a6..6154f8f 100644
--- a/compat/BUILD.gn
+++ b/compat/BUILD.gn
@@ -62,7 +62,7 @@
} else {
static_library(target_name) {
forward_variables_from(invoker, "*", [ "configs" ])
- if (!(defined(configs))) {
+ if (!defined(configs)) {
configs = []
}
if (defined(invoker.configs)) {
@@ -116,8 +116,6 @@
if (crashpad_is_android) {
sources += [
- "android/android/api-level.cc",
- "android/android/api-level.h",
"android/dlfcn_internal.cc",
"android/dlfcn_internal.h",
"android/elf.h",
@@ -166,7 +164,7 @@
"..:crashpad_config",
]
- deps = []
+ deps = [ "../util:no_cfi_icall" ]
if (!crashpad_is_mac) {
deps += [ "../third_party/xnu" ]
diff --git a/compat/android/android/api-level.cc b/compat/android/android/api-level.cc
deleted file mode 100644
index c29ec1a..0000000
--- a/compat/android/android/api-level.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 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 <android/api-level.h>
-
-#include <dlfcn.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <sys/system_properties.h>
-
-#include "dlfcn_internal.h"
-
-#if __NDK_MAJOR__ < 20
-
-extern "C" {
-
-int android_get_device_api_level() {
- using FuncType = int (*)();
- static const FuncType bionic_get_device_api_level =
- reinterpret_cast<FuncType>(
- crashpad::internal::Dlsym(RTLD_NEXT, "android_get_device_api_level"));
-
- if (bionic_get_device_api_level) {
- return bionic_get_device_api_level();
- }
-
- char api_string[PROP_VALUE_MAX];
- int length = __system_property_get("ro.build.version.sdk", api_string);
- if (length <= 0) {
- return -1;
- }
-
- int api_level = atoi(api_string);
- return api_level > 0 ? api_level : -1;
-}
-
-} // extern "C"
-
-#endif // __NDK_MAJOR__ < 20
diff --git a/compat/android/android/api-level.h b/compat/android/android/api-level.h
deleted file mode 100644
index bfff9af..0000000
--- a/compat/android/android/api-level.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 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.
-
-#ifndef CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
-#define CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
-
-#include_next <android/api-level.h>
-#include <android/ndk-version.h>
-
-#include <sys/cdefs.h>
-
-#if __NDK_MAJOR__ < 20
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Returns the API level of the device or -1 if it can't be determined. This
-// function is provided by NDK r20.
-int android_get_device_api_level();
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // __NDK_MAJOR__ < 20
-
-#endif // CRASHPAD_COMPAT_ANDROID_ANDROID_API_LEVEL_H_
diff --git a/compat/android/sys/epoll.cc b/compat/android/sys/epoll.cc
index 7fd3bb3..6c1a476 100644
--- a/compat/android/sys/epoll.cc
+++ b/compat/android/sys/epoll.cc
@@ -19,13 +19,14 @@
#include <unistd.h>
#include "dlfcn_internal.h"
+#include "util/misc/no_cfi_icall.h"
#if __ANDROID_API__ < 21
extern "C" {
int epoll_create1(int flags) {
- static const auto epoll_create1_p = reinterpret_cast<int (*)(int)>(
+ static const crashpad::NoCfiIcall<decltype(epoll_create1)*> epoll_create1_p(
crashpad::internal::Dlsym(RTLD_DEFAULT, "epoll_create1"));
return epoll_create1_p ? epoll_create1_p(flags)
: syscall(SYS_epoll_create1, flags);
diff --git a/compat/android/sys/mman_mmap.cc b/compat/android/sys/mman_mmap.cc
index d890d84..c929cfe 100644
--- a/compat/android/sys/mman_mmap.cc
+++ b/compat/android/sys/mman_mmap.cc
@@ -20,6 +20,7 @@
#include <unistd.h>
#include "dlfcn_internal.h"
+#include "util/misc/no_cfi_icall.h"
#if defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21
@@ -88,8 +89,7 @@
void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) {
// Use the system’s mmap64() wrapper if available. It will be available on
// Android 5.0 (“Lollipop”) and later.
- using Mmap64Type = void* (*)(void*, size_t, int, int, int, off64_t);
- static const Mmap64Type mmap64 = reinterpret_cast<Mmap64Type>(
+ static const crashpad::NoCfiIcall<decltype(LocalMmap64)*> mmap64(
crashpad::internal::Dlsym(RTLD_DEFAULT, "mmap64"));
if (mmap64) {
return mmap64(addr, size, prot, flags, fd, offset);
diff --git a/compat/compat.gyp b/compat/compat.gyp
index 1c2eeaf..693f2c6 100644
--- a/compat/compat.gyp
+++ b/compat/compat.gyp
@@ -19,9 +19,13 @@
'targets': [
{
'target_name': 'crashpad_compat',
+ 'dependencies': [
+ '../util/no_cfi_icall.gyp:no_cfi_icall',
+ ],
+ 'include_dirs': [
+ '..',
+ ],
'sources': [
- 'android/android/api-level.cc',
- 'android/android/api-level.h',
'android/dlfcn_internal.cc',
'android/dlfcn_internal.h',
'android/elf.h',
diff --git a/compat/linux/sys/mman_memfd_create.cc b/compat/linux/sys/mman_memfd_create.cc
index 12aaa2c..e0ee5a4 100644
--- a/compat/linux/sys/mman_memfd_create.cc
+++ b/compat/linux/sys/mman_memfd_create.cc
@@ -18,14 +18,15 @@
#include <sys/syscall.h>
#include <unistd.h>
+#include "util/misc/no_cfi_icall.h"
+
#if defined(__GLIBC__)
extern "C" {
int memfd_create(const char* name, unsigned int flags) {
- using MemfdCreateType = int (*)(const char*, int);
- static const MemfdCreateType next_memfd_create =
- reinterpret_cast<MemfdCreateType>(dlsym(RTLD_NEXT, "memfd_create"));
+ static const crashpad::NoCfiIcall<decltype(memfd_create)*> next_memfd_create(
+ dlsym(RTLD_NEXT, "memfd_create"));
return next_memfd_create ? next_memfd_create(name, flags)
: syscall(SYS_memfd_create, name, flags);
}
diff --git a/handler/BUILD.gn b/handler/BUILD.gn
index dc48178..332117d 100644
--- a/handler/BUILD.gn
+++ b/handler/BUILD.gn
@@ -181,6 +181,8 @@
sources = [ "linux/handler_trampoline.cc" ]
+ deps = [ "../util:no_cfi_icall" ]
+
ldflags = [ "-llog" ]
if (crashpad_is_in_chromium) {
diff --git a/handler/linux/handler_trampoline.cc b/handler/linux/handler_trampoline.cc
index 3453563..1596d77 100644
--- a/handler/linux/handler_trampoline.cc
+++ b/handler/linux/handler_trampoline.cc
@@ -16,6 +16,8 @@
#include <dlfcn.h>
#include <stdlib.h>
+#include "util/misc/no_cfi_icall.h"
+
// The first argument passed to the trampoline is the name of the native library
// exporting the symbol `CrashpadHandlerMain`. The remaining arguments are the
// same as for `HandlerMain()`.
@@ -34,8 +36,8 @@
}
using MainType = int (*)(int, char*[]);
- MainType crashpad_main =
- reinterpret_cast<MainType>(dlsym(handle, "CrashpadHandlerMain"));
+ const crashpad::NoCfiIcall<MainType> crashpad_main(
+ dlsym(handle, "CrashpadHandlerMain"));
if (!crashpad_main) {
__android_log_print(ANDROID_LOG_FATAL, kTag, "dlsym: %s", dlerror());
return EXIT_FAILURE;
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 2585caa..81e6205 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -575,6 +575,7 @@
}
public_deps = [
+ ":no_cfi_icall",
"../compat",
"../third_party/zlib",
]
@@ -668,6 +669,14 @@
}
}
+# This exists as a separate target from util so that compat may depend on it
+# without cycles.
+source_set("no_cfi_icall") {
+ sources = [ "misc/no_cfi_icall.h" ]
+ public_deps = [ "../third_party/mini_chromium:base" ]
+ public_configs = [ "..:crashpad_config" ]
+}
+
source_set("util_test") {
testonly = true
@@ -685,6 +694,7 @@
"misc/from_pointer_cast_test.cc",
"misc/initialization_state_dcheck_test.cc",
"misc/initialization_state_test.cc",
+ "misc/no_cfi_icall_test.cc",
"misc/paths_test.cc",
"misc/random_string_test.cc",
"misc/range_set_test.cc",
diff --git a/util/mach/exception_types.cc b/util/mach/exception_types.cc
index 7fc33f0..9c9bbd4 100644
--- a/util/mach/exception_types.cc
+++ b/util/mach/exception_types.cc
@@ -26,6 +26,7 @@
#include "base/mac/mach_logging.h"
#include "util/mac/mac_util.h"
#include "util/mach/mach_extensions.h"
+#include "util/misc/no_cfi_icall.h"
#include "util/numeric/in_range_cast.h"
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_9
@@ -84,8 +85,8 @@
int ProcGetWakemonParams(pid_t pid, int* rate_hz, int* flags) {
#if __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9
// proc_get_wakemon_params() isn’t in the SDK. Look it up dynamically.
- static ProcGetWakemonParamsType proc_get_wakemon_params =
- GetProcGetWakemonParams();
+ static crashpad::NoCfiIcall<ProcGetWakemonParamsType> proc_get_wakemon_params(
+ GetProcGetWakemonParams());
#endif
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9
diff --git a/util/misc/no_cfi_icall.h b/util/misc/no_cfi_icall.h
new file mode 100644
index 0000000..ebc632c
--- /dev/null
+++ b/util/misc/no_cfi_icall.h
@@ -0,0 +1,125 @@
+// Copyright 2020 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.
+
+#ifndef CRASHPAD_UTIL_MISC_NO_CFI_ICALL_H_
+#define CRASHPAD_UTIL_MISC_NO_CFI_ICALL_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif // OS_WIN
+
+namespace crashpad {
+
+namespace {
+
+template <typename Functor>
+struct FunctorTraits;
+
+template <typename R, typename... Args>
+struct FunctorTraits<R (*)(Args...)> {
+ template <typename Function, typename... RunArgs>
+ DISABLE_CFI_ICALL static R Invoke(Function&& function, RunArgs&&... args) {
+ return std::forward<Function>(function)(std::forward<RunArgs>(args)...);
+ }
+};
+
+template <typename R, typename... Args>
+struct FunctorTraits<R (*)(Args..., ...)> {
+ template <typename Function, typename... RunArgs>
+ DISABLE_CFI_ICALL static R Invoke(Function&& function, RunArgs&&... args) {
+ return std::forward<Function>(function)(std::forward<RunArgs>(args)...);
+ }
+};
+
+#if defined(OS_WIN) && defined(ARCH_CPU_X86)
+template <typename R, typename... Args>
+struct FunctorTraits<R(__stdcall*)(Args...)> {
+ template <typename... RunArgs>
+ DISABLE_CFI_ICALL static R Invoke(R(__stdcall* function)(Args...),
+ RunArgs&&... args) {
+ return function(std::forward<RunArgs>(args)...);
+ }
+};
+#endif // OS_WIN && ARCH_CPU_X86
+
+} // namespace
+
+//! \brief Disables cfi-icall for calls made through a function pointer.
+//!
+//! Clang provides several Control-Flow-Integrity (CFI) sanitizers, among them,
+//! cfi-icall, which attempts to verify that the dynamic type of a function
+//! matches the static type of the function pointer used to call it.
+//!
+//! https://clang.llvm.org/docs/ControlFlowIntegrity.html#indirect-function-call-checking
+//!
+//! However, cfi-icall does not have enough information to check indirect calls
+//! to functions in other modules, such as through the pointers returned by
+//! `dlsym()`. In these cases, CFI aborts the program upon executing the
+//! indirect call.
+//!
+//! This class encapsulates cross-DSO function pointers to disable cfi-icall
+//! precisely when calling these pointers.
+template <typename Functor>
+class NoCfiIcall {
+ public:
+ //! \brief Constructs this object.
+ //!
+ //! \param function A pointer to the function to be called.
+ explicit NoCfiIcall(Functor function) : function_(function) {}
+
+ //! \see NoCfiIcall
+ template <typename PointerType,
+ typename = std::enable_if_t<
+ std::is_same<typename std::remove_cv<PointerType>::type,
+ void*>::value>>
+ explicit NoCfiIcall(PointerType function)
+ : function_(reinterpret_cast<Functor>(function)) {}
+
+#if defined(OS_WIN)
+ //! \see NoCfiIcall
+ template <typename = std::enable_if_t<
+ !std::is_same<typename std::remove_cv<Functor>::type,
+ FARPROC>::value>>
+ explicit NoCfiIcall(FARPROC function)
+ : function_(reinterpret_cast<Functor>(function)) {}
+#endif // OS_WIN
+
+ ~NoCfiIcall() = default;
+
+ //! \brief Calls the function without sanitization by cfi-icall.
+ template <typename... RunArgs>
+ decltype(auto) operator()(RunArgs&&... args) const {
+ return FunctorTraits<Functor>::Invoke(function_,
+ std::forward<RunArgs>(args)...);
+ }
+
+ //! \brief Returns `true` if not `nullptr`.
+ operator bool() const { return function_ != nullptr; }
+
+ private:
+ Functor function_;
+
+ DISALLOW_COPY_AND_ASSIGN(NoCfiIcall);
+};
+
+} // namespace crashpad
+
+#endif // CRASHPAD_UTIL_MISC_NO_CFI_ICALL_H_
diff --git a/util/misc/no_cfi_icall_test.cc b/util/misc/no_cfi_icall_test.cc
new file mode 100644
index 0000000..680edbb
--- /dev/null
+++ b/util/misc/no_cfi_icall_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2020 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/misc/no_cfi_icall.h"
+
+#include <stdio.h>
+
+#include "build/build_config.h"
+#include "gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "util/win/get_function.h"
+#else
+#include <dlfcn.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+namespace crashpad {
+namespace test {
+namespace {
+
+TEST(NoCfiIcall, NullptrIsFalse) {
+ NoCfiIcall<void (*)(void)> call(nullptr);
+ ASSERT_FALSE(call);
+}
+
+TEST(NoCfiIcall, SameDSOICall) {
+ static int (*func)() = []() { return 42; };
+ NoCfiIcall<decltype(func)> call(func);
+ ASSERT_TRUE(call);
+ ASSERT_EQ(call(), 42);
+}
+
+TEST(NoCfiIcall, CrossDSOICall) {
+#if defined(OS_WIN)
+ static const NoCfiIcall<decltype(GetCurrentProcessId)*> call(
+ GET_FUNCTION_REQUIRED(L"kernel32.dll", GetCurrentProcessId));
+ ASSERT_TRUE(call);
+ EXPECT_EQ(call(), GetCurrentProcessId());
+#else
+ static const NoCfiIcall<decltype(getpid)*> call(dlsym(RTLD_NEXT, "getpid"));
+ ASSERT_TRUE(call);
+ EXPECT_EQ(call(), getpid());
+#endif
+}
+
+TEST(NoCfiIcall, Args) {
+#if !defined(OS_WIN)
+ static const NoCfiIcall<decltype(snprintf)*> call(
+ dlsym(RTLD_NEXT, "snprintf"));
+ ASSERT_TRUE(call);
+
+ char buf[1024];
+
+ // Regular args.
+ memset(buf, 0, sizeof(buf));
+ ASSERT_GT(call(buf, sizeof(buf), "Hello!"), 0);
+ EXPECT_STREQ(buf, "Hello!");
+
+ // Variadic args.
+ memset(buf, 0, sizeof(buf));
+ ASSERT_GT(call(buf, sizeof(buf), "%s, %s!", "Hello", "World"), 0);
+ EXPECT_STREQ(buf, "Hello, World!");
+#endif
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad
diff --git a/util/no_cfi_icall.gyp b/util/no_cfi_icall.gyp
new file mode 100644
index 0000000..cfb53a7
--- /dev/null
+++ b/util/no_cfi_icall.gyp
@@ -0,0 +1,38 @@
+# Copyright 2020 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.
+
+{
+ 'includes': [
+ '../build/crashpad.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'no_cfi_icall',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../third_party/mini_chromium/mini_chromium.gyp:base',
+ ],
+ 'include_dirs': [
+ '..',
+ '<(INTERMEDIATE_DIR)',
+ ],
+ 'sources': [
+ 'misc/no_cfi_icall.h',
+ ],
+ 'export_dependent_settings': [
+ '../third_party/mini_chromium/mini_chromium.gyp:base',
+ ],
+ },
+ ],
+}
diff --git a/util/util_test.gyp b/util/util_test.gyp
index 749c532..339bb4a 100644
--- a/util/util_test.gyp
+++ b/util/util_test.gyp
@@ -21,6 +21,7 @@
'target_name': 'crashpad_util_test',
'type': 'executable',
'dependencies': [
+ 'no_cfi_icall.gyp:no_cfi_icall',
'util.gyp:crashpad_util',
'../client/client.gyp:crashpad_client',
'../compat/compat.gyp:crashpad_compat',
@@ -79,6 +80,7 @@
'misc/from_pointer_cast_test.cc',
'misc/initialization_state_dcheck_test.cc',
'misc/initialization_state_test.cc',
+ 'misc/no_cfi_icall_test.cc',
'misc/paths_test.cc',
'misc/scoped_forbid_return_test.cc',
'misc/random_string_test.cc',