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