Add another iOS library path sinkhole.
Add another sinkhole for _UIGestureEnvironmentUpdate.
Bug: crashpad:31
Change-Id: I4084ee3064b1e483ac2a426d5b30e40cc27594d0
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2145017
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Reviewed-by: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
GitOrigin-RevId: ea4af71c2ac7a2daa50e4d22bc48270b1a66c83a
diff --git a/BUILD.gn b/BUILD.gn
index 17c380e..0b16a6d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -45,7 +45,6 @@
"handler:handler_test",
"minidump:minidump_test",
"snapshot:snapshot_test",
- "util:util_test",
]
}
if (crashpad_is_in_fuchsia) {
@@ -63,9 +62,7 @@
"util/net/testdata/binary_http_body.dat",
]
- outputs = [
- "$root_out_dir/crashpad_test_data/{{source}}",
- ]
+ outputs = [ "$root_out_dir/crashpad_test_data/{{source}}" ]
}
deps += [ ":crashpad_test_data" ]
@@ -222,9 +219,6 @@
"test:gmock_main",
"util:util_test",
]
- if (crashpad_is_ios) {
- deps -= [ "util:util_test" ]
- }
}
}
diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm
index 5369e3c..4529124 100644
--- a/test/ios/crash_type_xctest.mm
+++ b/test/ios/crash_type_xctest.mm
@@ -193,6 +193,21 @@
XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
}
+- (void)testCatchUIGestureEnvironmentNSException {
+ XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+
+ // Tap the button with the string UIGestureEnvironmentException.
+ [_app.buttons[@"UIGestureEnvironmentException"] tap];
+
+ // Confirm the app is not running.
+ XCTAssertTrue([_app waitForState:XCUIApplicationStateNotRunning timeout:15]);
+ XCTAssertTrue(_app.state == XCUIApplicationStateNotRunning);
+
+ // TODO: Query the app for crash data
+ [_app launch];
+ XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
+}
+
- (void)testCatchNSException {
XCTAssertTrue(_app.state == XCUIApplicationStateRunningForeground);
diff --git a/test/ios/host/cptest_crash_view_controller.mm b/test/ios/host/cptest_crash_view_controller.mm
index 90f92da..8e49b02 100644
--- a/test/ios/host/cptest_crash_view_controller.mm
+++ b/test/ios/host/cptest_crash_view_controller.mm
@@ -22,6 +22,40 @@
- (void)loadView {
self.view = [[UIView alloc] init];
+
+ UIStackView* buttonStack = [[UIStackView alloc] init];
+ buttonStack.axis = UILayoutConstraintAxisVertical;
+ buttonStack.spacing = 6;
+
+ UIButton* button = [UIButton new];
+ [button setTitle:@"UIGestureEnvironmentException"
+ forState:UIControlStateNormal];
+ UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc]
+ initWithTarget:self
+ action:@selector(throwUIGestureEnvironmentException)];
+ [button addGestureRecognizer:tapGesture];
+ [button setTranslatesAutoresizingMaskIntoConstraints:NO];
+ [button.widthAnchor constraintEqualToConstant:16.0].active = YES;
+ [button.heightAnchor constraintEqualToConstant:16.0].active = YES;
+
+ [buttonStack addArrangedSubview:button];
+
+ [self.view addSubview:buttonStack];
+
+ [buttonStack setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [buttonStack.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
+ [buttonStack.topAnchor constraintEqualToAnchor:self.view.topAnchor],
+ [buttonStack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
+ [buttonStack.trailingAnchor
+ constraintEqualToAnchor:self.view.trailingAnchor],
+ ]];
+}
+
+- (void)throwUIGestureEnvironmentException {
+ NSArray* empty_array = @[];
+ [empty_array objectAtIndex:42];
}
- (void)viewDidLoad {
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 18cc112..183a0a8 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -548,7 +548,7 @@
configs += [ "..:disable_ubsan" ]
}
-if (!crashpad_is_android) {
+if (!crashpad_is_android && !crashpad_is_ios) {
crashpad_executable("http_transport_test_server") {
testonly = true
sources = [ "net/http_transport_test_server.cc" ]
@@ -639,13 +639,13 @@
"thread/worker_thread_test.cc",
]
- if (!crashpad_is_android) {
+ if (!crashpad_is_android && !crashpad_is_ios) {
# Android requires an HTTPTransport implementation.
sources += [ "net/http_transport_test.cc" ]
}
if (crashpad_is_posix || crashpad_is_fuchsia) {
- if (!crashpad_is_fuchsia) {
+ if (!crashpad_is_fuchsia && !crashpad_is_ios) {
sources += [
"posix/process_info_test.cc",
"posix/signals_test.cc",
@@ -680,6 +680,27 @@
]
}
+ if (crashpad_is_ios) {
+ sources += [ "ios/exception_processor_test.mm" ]
+
+ sources -= [
+ "file/directory_reader_test.cc",
+ "file/file_io_test.cc",
+ "file/filesystem_test.cc",
+ "misc/capture_context_test.cc",
+ "misc/clock_test.cc",
+ "misc/paths_test.cc",
+ "net/http_body_test.cc",
+ "net/http_multipart_builder_test.cc",
+ "process/process_memory_range_test.cc",
+ "process/process_memory_test.cc",
+ "stream/file_encoder_test.cc",
+ "synchronization/semaphore_test.cc",
+ "thread/thread_test.cc",
+ "thread/worker_thread_test.cc",
+ ]
+ }
+
if (crashpad_is_linux || crashpad_is_android) {
sources += [
"linux/auxiliary_vector_test.cc",
@@ -739,7 +760,7 @@
deps += [ "../third_party/lss" ]
}
- if (!crashpad_is_android) {
+ if (!crashpad_is_android && !crashpad_is_ios) {
data_deps = [ ":http_transport_test_server" ]
if (crashpad_use_boringssl_for_http_transport_socket) {
diff --git a/util/ios/exception_processor.mm b/util/ios/exception_processor.mm
index 818d498..f767fb1 100644
--- a/util/ios/exception_processor.mm
+++ b/util/ios/exception_processor.mm
@@ -20,6 +20,7 @@
#include <dlfcn.h>
#include <libunwind.h>
#include <mach-o/loader.h>
+#include <objc/message.h>
#include <objc/objc-exception.h>
#include <objc/objc.h>
#include <objc/runtime.h>
@@ -109,6 +110,14 @@
#endif
}
+int LoggingUnwStep(unw_cursor_t* cursor) {
+ int rv = unw_step(cursor);
+ if (rv < 0) {
+ LOG(ERROR) << "unw_step: " << rv;
+ }
+ return rv;
+}
+
id ObjcExceptionPreprocessor(id exception) {
// Unwind the stack looking for any exception handlers. If an exception
// handler is encountered, test to see if it is a function known to catch-
@@ -152,7 +161,7 @@
exception_header->unwindHeader.exception_class = kOurExceptionClass;
bool handler_found = false;
- while (unw_step(&cursor) > 0) {
+ while (LoggingUnwStep(&cursor) > 0) {
unw_proc_info_t frame_info;
if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) {
continue;
@@ -226,14 +235,14 @@
// Everything in this library is a sinkhole, specifically
// _dispatch_client_callout. Both are needed here depending on whether
// the debugger is attached (introspection only appears when a simulator
- // is attached to a debugger.
- // only).
+ // is attached to a debugger).
"/usr/lib/system/introspection/libdispatch.dylib",
"/usr/lib/system/libdispatch.dylib",
// __CFRunLoopDoTimers and __CFRunLoopRun are sinkholes. Consider also
// checking that a few frames up is CFRunLoopRunSpecific().
- "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"};
+ "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
+ };
Dl_info dl_info;
if (dladdr(reinterpret_cast<const void*>(frame_info.start_ip), &dl_info) !=
@@ -245,6 +254,44 @@
}
}
+ // Some <redacted> sinkholes are harder to find. _UIGestureEnvironmentUpdate
+ // in UIKitCore is an example. UIKitCore can't be added to
+ // kExceptionLibraryPathSinkholes because it uses Objective-C exceptions
+ // internally and also has has non-sinkhole handlers. Since
+ // _UIGestureEnvironmentUpdate is always called from
+ // -[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:],
+ // inspect the caller frame info to match the sinkhole.
+ constexpr const char* kUIKitCorePath =
+ "/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore";
+ if (ModulePathMatchesSinkhole(dl_info.dli_fname, kUIKitCorePath)) {
+ unw_proc_info_t caller_frame_info;
+ if (LoggingUnwStep(&cursor) > 0 &&
+ unw_get_proc_info(&cursor, &caller_frame_info) == UNW_ESUCCESS) {
+ static IMP uigesture_deliver_event_imp = [] {
+ IMP imp = class_getMethodImplementation(
+ NSClassFromString(@"UIGestureEnvironment"),
+ NSSelectorFromString(
+ @"_deliverEvent:toGestureRecognizers:usingBlock:"));
+
+ // From 10.15.0 objc4-779.1/runtime/objc-class.mm
+ // class_getMethodImplementation returns nil or _objc_msgForward on
+ // failure.
+ if (!imp || imp == _objc_msgForward) {
+ LOG(WARNING) << "Unable to find -[UIGestureEnvironment "
+ "_deliverEvent:toGestureRecognizers:usingBlock:]";
+ return reinterpret_cast<IMP>(NULL);
+ }
+ return imp;
+ }();
+
+ if (uigesture_deliver_event_imp ==
+ reinterpret_cast<IMP>(caller_frame_info.start_ip)) {
+ TerminatingFromUncaughtNSException(exception,
+ "_UIGestureEnvironmentUpdate");
+ }
+ }
+ }
+
handler_found = true;
break;
diff --git a/util/ios/exception_processor_test.mm b/util/ios/exception_processor_test.mm
new file mode 100644
index 0000000..c9aa716
--- /dev/null
+++ b/util/ios/exception_processor_test.mm
@@ -0,0 +1,41 @@
+// 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.
+
+#import <Foundation/Foundation.h>
+#include <objc/message.h>
+#include <objc/runtime.h>
+
+#include "gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace crashpad {
+namespace test {
+namespace {
+
+using IOSExceptionProcessor = PlatformTest;
+
+TEST_F(IOSExceptionProcessor, SelectorExists) {
+ IMP uigesture_deliver_event_imp = class_getMethodImplementation(
+ NSClassFromString(@"UIGestureEnvironment"),
+ NSSelectorFromString(@"_deliverEvent:toGestureRecognizers:usingBlock:"));
+
+ // From 10.15.0 objc4-779.1/runtime/objc-class.mm
+ // class_getMethodImplementation returns nil or _objc_msgForward on failure.
+ ASSERT_TRUE(uigesture_deliver_event_imp);
+ ASSERT_NE(uigesture_deliver_event_imp, _objc_msgForward);
+}
+
+} // namespace
+} // namespace test
+} // namespace crashpad