// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "testing/libfuzzer/fuzzer_support_ios/fuzzer_support.h"
#import <UIKit/UIKit.h>
// Springboard/Frontboard will kill any iOS/MacCatalyst app that fails to check
// in after launch within a given time. Starting a UIApplication before invoking
// fuzzer prevents this from happening.
// Since the executable isn't likely to be a real iOS UI, the delegate puts up a
// window displaying the app name. If a bunch of apps using MainHook are being
// run in a row, this provides an indication of which one is currently running.
static int g_argc;
static char** g_argv;
namespace {
extern "C" int LLVMFuzzerRunDriver(int* argc,
char*** argv,
int (*UserCb)(const uint8_t* Data,
size_t Size));
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
void PopulateUIWindow(UIWindow* window) {
[window setBackgroundColor:[UIColor whiteColor]];
[window makeKeyAndVisible];
CGRect bounds = [[UIScreen mainScreen] bounds];
// Add a label with the app name.
UILabel* label = [[UILabel alloc] initWithFrame:bounds];
label.text = [[NSProcessInfo processInfo] processName];
label.textAlignment = NSTextAlignmentCenter;
[window addSubview:label];
// An NSInternalInconsistencyException is thrown if the app doesn't have a
// root view controller. Set an empty one here.
[window setRootViewController:[[UIViewController alloc] init]];
}
} // namespace
@interface UIApplication (Testing)
- (void)_terminateWithStatus:(int)status;
@end
// No-op scene delegate for libFuzzer. Note that this is created along with
// the application delegate, so they need to be separate objects (the same
// object can't be both the app and scene delegate, since new scene delegates
// are created for each scene).
@interface ChromeLibFuzzerSceneDelegate : NSObject <UIWindowSceneDelegate> {
UIWindow* _window;
}
- (void)runFuzzer;
@end
@interface ChromeLibFuzzerDelegate : NSObject {
}
@end
@implementation ChromeLibFuzzerSceneDelegate
- (void)scene:(UIScene*)scene
willConnectToSession:(UISceneSession*)session
options:(UISceneConnectionOptions*)connectionOptions
API_AVAILABLE(ios(13), macCatalyst(13.0)) {
_window =
[[UIWindow alloc] initWithWindowScene:static_cast<UIWindowScene*>(scene)];
PopulateUIWindow(_window);
static dispatch_once_t once;
// Delay 0.3 seconds to allow NSMenuBarScene to be created and thus app won't
// be killed by the watchdog tracking that.
dispatch_once(&once, ^{
[self performSelector:@selector(runFuzzer) withObject:nil afterDelay:0.3];
});
}
- (void)sceneDidDisconnect:(UIScene*)scene
API_AVAILABLE(ios(13), macCatalyst(13.0)) {
_window = nil;
}
- (void)runFuzzer {
int exitStatus =
LLVMFuzzerRunDriver(&g_argc, &g_argv, &LLVMFuzzerTestOneInput);
// If a test app is too fast, it will exit before Instruments has has a
// a chance to initialize and no test results will be seen.
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
// Use the hidden selector to try and cleanly take down the app (otherwise
// things can think the app crashed even on a zero exit status).
UIApplication* application = [UIApplication sharedApplication];
[application _terminateWithStatus:exitStatus];
exit(exitStatus);
}
@end
@implementation ChromeLibFuzzerDelegate
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return YES;
}
@end
namespace ios_fuzzer {
void RunFuzzerFromIOSApp(int argc, char* argv[]) {
g_argc = argc;
g_argv = argv;
@autoreleasepool {
int exit_status =
UIApplicationMain(g_argc, g_argv, nil, @"ChromeLibFuzzerDelegate");
exit(exit_status);
}
}
} // namespace ios_fuzzer