| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_TEST_FUZZING_IN_PROCESS_FUZZER_H_ |
| #define CHROME_TEST_FUZZING_IN_PROCESS_FUZZER_H_ |
| |
| #include <optional> |
| |
| #include "chrome/test/base/in_process_browser_test.h" |
| |
| enum class RunLoopTimeoutBehavior { |
| // Default behavior that doesn't alter the current way run loop internal |
| // mechanism handles timeouts. |
| kDefault, |
| // Continues the normal execution after the call to RunLoop::Run. This |
| // basically ignores the timeouts. |
| kContinue, |
| // Calls InProcessFuzzer::DeclareInfiniteLoop. This will still run the fuzz |
| // case as `kContinue` would until the Fuzz method returns. |
| kDeclareInfiniteLoop, |
| }; |
| |
| struct InProcessFuzzerOptions { |
| // The behavior to be set when a run loop times out. |
| RunLoopTimeoutBehavior run_loop_timeout_behavior = |
| RunLoopTimeoutBehavior::kDefault; |
| |
| // Overrides the default 60s run loop timeout set by `BrowserTestBase`. See |
| // https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/browser_test_base.cc?q=ScopedRunLoopTimeout. |
| // Mind that it doesn't guarantee that this will be the maximum time a fuzzing |
| // loop will take since ScopedRunLoopTimeout can be nested. |
| std::optional<base::TimeDelta> run_loop_timeout = std::nullopt; |
| }; |
| |
| // In-process fuzz test. |
| // |
| // This is equivalent to a browser test, in that the entire browser |
| // environment is available for your use, and you can do rich things that |
| // require the whole browser infrastructure. |
| // |
| // The 'Fuzz' method will be called repeatedly, and you just have to |
| // implement something sensible there to explore parts of Chrome's |
| // attack surface. |
| // |
| // Register your subclass with REGISTER_IN_PROCESS_FUZZER. There can only |
| // be one per executable. |
| // |
| // Different fuzz frameworks might run this in different ways. |
| // For instance, |
| // * libfuzzer runs this in a multi-process Chrome browser_test |
| // environment. |
| // * centipede runs it in single-process browser_test mode (currently), |
| // with an external fuzz co-ordinator running multiple instances |
| // of Chrome. |
| // * in the future, snapshot fuzzers might pause a VM and resume |
| // clones of it (to ensure a cleaner state for each iteration) |
| // To the extent possible, you should write your fuzzer to be |
| // implementation-independent and semantically express what |
| // should happen during such fuzzing of the whole browser. |
| class InProcessFuzzer : virtual public InProcessBrowserTest { |
| public: |
| // Called by the main function to create this class. |
| // This is called prior to all the normal browser test setup, |
| // so don't do anything important in your constructor. |
| // Furthermore, this will be re-run even for child Chromium processes. |
| // NOLINTNEXTLINE(runtime/explicit) |
| InProcessFuzzer(InProcessFuzzerOptions options = {}); |
| ~InProcessFuzzer() override; |
| |
| // Called by the main function to run this fuzzer, after the browser_test |
| // equivalent infrastructure has been set up. |
| void Run(const std::vector<std::string>& libfuzzer_command_line); |
| |
| // If you override this, it's essential you call the superclass method. |
| void SetUpOnMainThread() override; |
| void RunTestOnMainThread() override; |
| void TestBody() override {} |
| void SetUp() override; |
| |
| friend int fuzz_callback(const uint8_t* data, size_t size); |
| |
| // Override if you want to pass particular command line arguments to |
| // Chromium for its startup. This is called before any fuzz test case |
| // is actually run, so unfortunately you can't generate these through |
| // fuzzing. In addition, the browser test framework itself does all |
| // sorts of fiddling with the arguments (e.g. a user data dir). |
| // It's generally OK to leave this at the default unless you specifically |
| // need to enable a feature or similar. |
| // Do not include the executable name in your return value - that's |
| // prepended automatically. |
| virtual base::CommandLine::StringVector GetChromiumCommandLineArguments(); |
| |
| protected: |
| // Callback to actually do your fuzzing. This is called from the UI thread, |
| // so you should take care not to block the thread too long. If you need |
| // to run your fuzz case across multiple threads, consider a nested RunLoop. |
| // Return 0 if the input is valid, -1 if it's invalid and should not be |
| // evolved further by the fuzzing engine. |
| virtual int Fuzz(const uint8_t* data, size_t size) = 0; |
| |
| // Should be called by subclasses from within Fuzz if they believe that |
| // a fuzz case is going to take infinite time to run. This will arrange |
| // to communicate this status to the fuzz engine as far as possible, |
| // then for the whole process to exit, thus throwing away that fuzz case. |
| // However, after calling this method, Fuzz should return -1 to indicate |
| // invalid input. |
| // The normal pattern for using this is, within Fuzz, to do this: |
| // 1. Create a RunLoop but don't start it yet |
| // 2. Start a OneShotTimer which calls this method then stops the RunLoop |
| // 3. Start an async task which will run the test case, cancel the timer, |
| // and then stop the run loop |
| // 4. Start the RunLoop. |
| // If the test case turns out not actually to be infinite, step 3 could |
| // cause a UaF, so this pattern can probably be improved in future. |
| void DeclareInfiniteLoop() { exit_after_fuzz_case_ = true; } |
| |
| private: |
| int DoFuzz(const uint8_t* data, size_t size); |
| |
| // Changes run loop timeout behavior to silently continue running the |
| // test/fuzzer instead of failing. Timed out run loops will stop running, |
| // but the rest of the test will continue executing. |
| void KeepRunningOnTimeout(); |
| |
| // Changes run loop timeouts behaviour to call `DeclareInfiniteLoop()`. |
| void DeclareInfiniteLoopOnTimeout(); |
| |
| private: |
| std::vector<std::string> libfuzzer_command_line_; |
| bool exit_after_fuzz_case_ = false; |
| InProcessFuzzerOptions options_; |
| }; |
| |
| class InProcessFuzzerFactoryBase { |
| public: |
| virtual std::unique_ptr<InProcessFuzzer> CreateInProcessFuzzer() = 0; |
| }; |
| |
| extern InProcessFuzzerFactoryBase* g_in_process_fuzzer_factory; |
| |
| // Class used to register a single in-process fuzzer in each executable. |
| template <typename T> |
| class InProcessFuzzerFactory : public InProcessFuzzerFactoryBase { |
| public: |
| InProcessFuzzerFactory() { g_in_process_fuzzer_factory = this; } |
| std::unique_ptr<InProcessFuzzer> CreateInProcessFuzzer() override { |
| return std::make_unique<T>(); |
| } |
| }; |
| |
| #define REGISTER_IN_PROCESS_FUZZER(fuzzer_class) \ |
| InProcessFuzzerFactory<fuzzer_class> fuzzer_instance; |
| |
| #endif // CHROME_TEST_FUZZING_IN_PROCESS_FUZZER_H_ |