1// Copyright 2021 The Chromium Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "testing/libfuzzer/fuzzer_support_ios/fuzzer_support.h" 6 7#import <UIKit/UIKit.h> 8 9#if !defined(__has_feature) || !__has_feature(objc_arc) 10#error "This file requires ARC support." 11#endif 12 13// Springboard/Frontboard will kill any iOS/MacCatalyst app that fails to check 14// in after launch within a given time. Starting a UIApplication before invoking 15// fuzzer prevents this from happening. 16 17// Since the executable isn't likely to be a real iOS UI, the delegate puts up a 18// window displaying the app name. If a bunch of apps using MainHook are being 19// run in a row, this provides an indication of which one is currently running. 20 21static int g_argc; 22static char** g_argv; 23 24namespace fuzzer { 25typedef int (*UserCallback)(const uint8_t* Data, size_t Size); 26int FuzzerDriver(int* argc, char*** argv, UserCallback Callback); 27} // namespace fuzzer 28 29namespace { 30// TODO(crbug.com/1261537): Remove this when the function is provided by 31// libFuzzer. 32extern "C" __attribute__((visibility("default"))) int LLVMFuzzerRunDriver( 33 int* argc, 34 char*** argv, 35 int (*UserCb)(const uint8_t* Data, size_t Size)) { 36 return fuzzer::FuzzerDriver(argc, argv, UserCb); 37} 38 39extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); 40 41void PopulateUIWindow(UIWindow* window) { 42 [window setBackgroundColor:[UIColor whiteColor]]; 43 [window makeKeyAndVisible]; 44 CGRect bounds = [[UIScreen mainScreen] bounds]; 45 // Add a label with the app name. 46 UILabel* label = [[UILabel alloc] initWithFrame:bounds]; 47 label.text = [[NSProcessInfo processInfo] processName]; 48 label.textAlignment = NSTextAlignmentCenter; 49 [window addSubview:label]; 50 51 // An NSInternalInconsistencyException is thrown if the app doesn't have a 52 // root view controller. Set an empty one here. 53 [window setRootViewController:[[UIViewController alloc] init]]; 54} 55} 56 57@interface UIApplication (Testing) 58- (void)_terminateWithStatus:(int)status; 59@end 60 61// No-op scene delegate for libFuzzer. Note that this is created along with 62// the application delegate, so they need to be separate objects (the same 63// object can't be both the app and scene delegate, since new scene delegates 64// are created for each scene). 65@interface ChromeLibFuzzerSceneDelegate : NSObject <UIWindowSceneDelegate> { 66 UIWindow* _window; 67} 68- (void)runFuzzer; 69@end 70 71@interface ChromeLibFuzzerDelegate : NSObject { 72} 73@end 74 75@implementation ChromeLibFuzzerSceneDelegate 76 77- (void)scene:(UIScene*)scene 78 willConnectToSession:(UISceneSession*)session 79 options:(UISceneConnectionOptions*)connectionOptions 80 API_AVAILABLE(ios(13), macCatalyst(13.0)) { 81 _window = 82 [[UIWindow alloc] initWithWindowScene:static_cast<UIWindowScene*>(scene)]; 83 PopulateUIWindow(_window); 84 static dispatch_once_t once; 85 // Delay 0.3 seconds to allow NSMenuBarScene to be created and thus app won't 86 // be killed by the watchdog tracking that. 87 dispatch_once(&once, ^{ 88 [self performSelector:@selector(runFuzzer) withObject:nil afterDelay:0.3]; 89 }); 90} 91 92- (void)sceneDidDisconnect:(UIScene*)scene 93 API_AVAILABLE(ios(13), macCatalyst(13.0)) { 94 _window = nil; 95} 96 97- (void)runFuzzer { 98 int exitStatus = 99 LLVMFuzzerRunDriver(&g_argc, &g_argv, &LLVMFuzzerTestOneInput); 100 101 // If a test app is too fast, it will exit before Instruments has has a 102 // a chance to initialize and no test results will be seen. 103 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; 104 105 // Use the hidden selector to try and cleanly take down the app (otherwise 106 // things can think the app crashed even on a zero exit status). 107 UIApplication* application = [UIApplication sharedApplication]; 108 [application _terminateWithStatus:exitStatus]; 109 110 exit(exitStatus); 111} 112 113@end 114 115@implementation ChromeLibFuzzerDelegate 116 117- (BOOL)application:(UIApplication*)application 118 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 119 return YES; 120} 121 122@end 123 124namespace ios_fuzzer { 125void RunFuzzerFromIOSApp(int argc, char* argv[]) { 126 g_argc = argc; 127 g_argv = argv; 128 @autoreleasepool { 129 int exit_status = 130 UIApplicationMain(g_argc, g_argv, nil, @"ChromeLibFuzzerDelegate"); 131 exit(exit_status); 132 } 133} 134 135} // namespace ios_fuzzer 136