
BLOG.JETBRAINS.COM
Getting Started With Fuzz Testing in CLion
Fuzz testing is an effective technique for identifying bugs that are difficult to detect using unit tests or static analysis alone. This article will show you how to perform fuzz testing in CLion, analyze the results, and debug a tested function. Well use CI Fuzz, a fuzz testing tool developed by Code Intelligence, a company that specializes in C and C++ fuzz testing solutions. The Code Intelligence team also contributed to this article.If you are already familiar with the concept of fuzz testing, feel free to skip the next two sections and jump straight to the practical part.What is fuzz testing?Fuzz testing, also called fuzzing, is an automated dynamic software testing technique that provides a program with invalid, unexpected, or random data. The goal is to monitor the programs runtime behavior and try to trigger bugs, such as crashes, hangs, or security vulnerabilities. By bombarding the software with a high volume and variety of incorrect inputs, fuzzing aims to uncover hidden bugs and weaknesses that might not be found through traditional testing methods.A useful mental model is to think of a fuzz test as similar to a unit test. But instead of feeding fixed inputs to the system, you supply it with inputs that are carefully selected and strategically mutated by the fuzzer to maximize code coverage.How fuzz testing worksLets assume we have an important function, ExploreSimpleChecks, in our codebase that we want to test:void ExploreSimpleChecks(int a, int b, std::string c) { if (a >= 20000) { if (b >= 2000000) { if (b - a < 100000) { if (c == Attacker) { trigger_global_buffer_overflow(c); } } } }}This function takes three parameters: two integers and one string. Under certain circumstances, it calls the vulnerable trigger_global_buffer_overflow function, which has a buffer overflow vulnerability. To trigger this vulnerability, we need:A test case that reaches the function call, meaning the two integers must be in a specific range, and the string must be Attacker.A check to see if a buffer overflow occurs, since these bugs dont necessarily cause crashes.Now, lets head over to the IDE and run a fuzz test on an example project to see how it works in practice.Running fuzz testing in CLionFirst, we need to install the CI Fuzz tool and download an example project. After that, well complete all the tasks in CLion.1. Install CI FuzzInstall a free trial of CI Fuzz on your machine following the Code Intelligence documentation. Note that you also need to install a build system as a prerequisite. If you prefer CMake, you can use one shipped with CLion and install only CI Fuzz. Otherwise, refer to the Code Intelligence documentation.2. Open an example project in CLionClone the c-cpp-example project and open it in CLion. You can also clone and open the project when CLion is already running by selecting File | New | Project From Version Control in the main menu.Well test the ExploreSimpleChecks function mentioned above, which is located in src/simple_examples/explore_me.cpp. For this, well use the FUZZ_TEST function from simple_checks_test.cpp that takes two parameters a byte buffer and its size:FUZZ_TEST(const uint8_t *data, size_t size) { FuzzedDataProvider fdp(data, size); int a = fdp.ConsumeIntegral<int>(); int b = fdp.ConsumeIntegral<int>(); std::string c = fdp.ConsumeRemainingBytesAsString(); ExploreSimpleChecks(a, b, c);}This fuzz test tries all possible combinations of the a, b, and c parameters in the ExploreSimpleChecks function to find hidden bugs. The FuzzedDataProvider class enables the consumption of specific types of data from the raw fuzzer-generated byte buffer.The mechanism looks very similar to how a unit test works. The only difference is that the function receives the data generated by the fuzzing engine as opposed to fixed, predefined inputs.To test your own code, use the methods provided by the FuzzedDataProvider class to consume the required types of data.3. Run a fuzz testIn the simple_checks_test.cpp file, you can see the fuzz test alongside two unit tests written for the same function.To run the test, open the IDEs terminal (F12 on macOS or Alt+F12 on Windows/Linux) and execute the command:cifuzz run simple_checks_fuzz_testThe output will include a report with the test summary. If a bug is found, the report will show its location in the source code.Heres our report:As it turns out, theres a bug on line 40 in explore_me.cpp that causes a global buffer overflow. Severity 9 out of 10 indicates a rather critical issue, so its a good thing we caught it!After the test is complete, CI Fuzz also suggests checking how much code the fuzz test has covered, asking Do you want to collect coverage information?. This can be useful, for example, if you want to identify functionality that has not yet been tested. If you are ready to check the coverage, type Y, and youll see the coverage report:In addition to the report in the terminal, youll see the HTML report that will open in your default browser:If you dont need the test coverage information at the moment, simply type N. Later, you can run the cifuzz coverage simple_checks_fuzz_test command separately. You can also get a combined coverage report that represents the merged coverage reached by all fuzz tests. To generate this report, either provide a list of fuzz tests to the coverage command or execute the command without specifying fuzz tests.4. Debug findingsCI Fuzz provides actionable results, including the exact input that triggered the finding and the complete stack trace. This allows you to easily debug the issue and understand the root cause by observing how the code handles the crashing input step by step. CI Fuzz offers a DEBUG_FINDING macro to facilitate this process:This macro registers a unit test that starts with the crashing input, triggering the finding.To start debugging:Add a DEBUG_FINDING macro with the finding name to your test code (as shown in line 16 above).Click the green Run Test button next to the macro.Select the Debug configuration:With this option, you can access a native debugging session in CLion that is similar to debugging any unit test:ConclusionThrough fuzz testing, weve discovered a critical memory corruption bug something that would have been difficult to catch with unit or integration tests. The latter typically rely on predefined inputs and expected outputs, whereas fuzz testing simulates more unpredictable, real-world scenarios. This simple example demonstrates the effectiveness of fuzz testing in finding bugs and security vulnerabilities beyond just testing the code functionality.The combination of CI Fuzz and CLion offers a powerful and efficient way to find hidden bugs in your code through fuzz testing. As youve seen, integrating CI Fuzz into your CLion workflow is simple, enabling you to concentrate on developing high-quality software.We encourage you to try out CI Fuzz for your projects and experience its capabilities firsthand. Your feedback is invaluable. Whether you discover new insights, encounter challenges, or have suggestions for improvements, we want to hear from you. Please share your thoughts in the comments!
0 Comentários
0 Compartilhamentos
20 Visualizações