• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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