Developers must test the stability and security of their programs before distributing them. Traditional unit tests often fail to reveal edge case vulnerabilities. Trdelnik addresses this problem by introducing fuzz testing for Solana programs, which generates large amounts of random input and call instructions to probe for unexpected weaknesses.
Why we created Trdelnik
Most of the programs on the Solana blockchain are written in the Rust programming language. There is a good reason to use Rust because it guarantees memory safety without the performance penalty like other programming languages that use garbage collection. Rust, on the other hand, uses a new and unique ownership concept. The downside is that Rust has become very complex and has a steep learning curve.
But wait, have you heard the saying “Only the best programmers can write in Rust, and the best make fewer mistakes”? Even if this is partially true, there are better ways to prevent bugs, and one of them is testing. Extensive, systematic testing is an essential part of all software development and is a great way to catch bugs early in development.
How is it possible that most projects only have basic tests or no tests at all? Believe it or not, writing good tests is not as easy as it looks, and in some cases it can take longer than product development. In the fast-moving world of cryptocurrency, timelines are often very short and the pressure to launch new projects is very high.
That’s why we developed it. hardenerThe Rust-based testing framework provides several convenient developer tools for testing Solana programs written in Solana. anchor. Trdelnik’s main goal is to simplify test environment setup and provide auto-generated APIs to send instructions to custom programs and accelerate the testing process.
fuzz test
A new feature recently introduced in Trdelnik is fuzz testing. An automated software testing technique that provides a program with generated random, invalid, or unexpected input data. This helps discover unknown bugs and vulnerabilities and prevents zero-day attacks in your programs.
There are several fuzzers used in Rust programs, but Googling Solana fuzz tests doesn’t return any results, which is why we decided to integrate this functionality into the framework. Internally Trdelnik uses a well-known fuzzer. Hongfuzz Developed by Google.
The following text explains step by step how to use Trdelnik for fuzz testing.
TL;DR
Step-by-step fuzzing with Trdelnik
In this tutorial we’ll walk through the complete setup, including the following steps:
- Setting up a new anchor project
- Create a program that contains the bug you want to detect
- Trdelnik test framework initialization
- Write a simple fuzzy test
- Run a purge test
- Debugging programs using fuzzy test conflict files
- Setting up a new anchor project
For the purposes of this tutorial, we will create a new Anchor project. If you don’t already have the Anchor framework install Version 0.28.0.
Open a terminal, navigate to the project folder where you want to create a new project, and verify that you can build the project.
Verify that the Anchor project was successfully initialized and built.
Create a program that contains the bug you want to detect
Next, we’ll create a simple program that uses fuzzers to intentionally introduce the bugs we want to find. Open the program’s source file. programs /my-trdelnik-fuzz-test/src/lib.rs
And replace everything after that. declare_id!()
A macro using the following code:
This is a simple program with two commands: initialize
and update
. that much initialize
The command creates the necessary data accounts and update
The command updates on-chain data. It also contains intentional content. panic!
A macro that immediately terminates the program, simulating a crash if the first input to the program is equal to a constant. MAGIC_NUMBER
.
Now you can double-check that your program built successfully using: anchor build
.
Trdelnik test framework initialization
To use Trdelnik and fuzzer you need to install:
If you already have Trdelnik installed, you will need to upgrade to version 0.5.0.
You then need to initialize the Trdelnik framework in your project using the following command:
This command automatically creates a .program_client folder using the API for your program, adds the necessary dependencies to the configuration file, and generates a trdelnik test template.
Write a simple fuzzy test
The fuzz test target template is located here: trdelnik-tests/src/bin/fuzz_target.rs
file. This file can be modified as needed.
Now you can just replace that with the following code:
For the purposes and simplicity of this tutorial, we only fuzz the command parameters. i.e. two parameters input1
and input2
~ Of update
guideline.
Looking at the Fuzz target code, main
The function contains an infinite loop like this: fuzz!
A macro that takes the closure we pass FuzzData
structure.
Let’s analyze the code a bit. The entry point for the fuzz target is: main
Here we fuzz!
The macro repeats over and over again. This macro executes the code passed in the closure and each loop. fuzz_data
different.
we are arbitrary
A crate that allows us to easily convert random, unstructured data into structured data as we define it. FuzzData
structure. Once the closure code is executed and the program does not crash, the fuzz test continues with a new loop iteration. fuzz_data
The content passed to the closure has been modified. if fuzz_data
Variables contain data that causes conflicts in your program, and the fuzzer automatically saves that data to a separate fuzz conflict file. ./trdelnik-tests/hfuzz_workspace/<TARGET_NAME>/<CRASH_FILE_NAME>.fuzz
. This is especially useful for subsequent debugging.
Code executed in the fuzz target’s closure first creates a new fuzz target. TestProgram
Add the program to your test environment. Then a test client is started that can send transactions to the program. To do this, you must first initialize the program. Generate initialization commands with the help of automatically generated Trdelnik. initialize_ix
Features for us. Finally, we create a new transaction that we sign through the client and send to our test environment. great. The program has been initialized!
Now we fuzz_update_ix
We use functions to provide random data to the update commands we want to fuzz and where we want to find bugs. Again, we construct the update command using the automatically generated update command. update_ix
The function provides randomly generated parameters. FuzzData
structure. Finally, create, sign, and send the transaction. Now your fuzz target is ready!
Run a purge test
Trdelnik provides a convenient way to run fuzz tests. You can run the following command from anywhere in your Anchor project:
So in our case <TARGET_NAME>
With real names you get:
Running this command will take some time as it requires building the entire project into an instrument for fuzzing. Once the build is complete, the fuzzer will start automatically and you will see something like this:
At the top you can see fuzzing statistics. In particular, it includes the number of times the program crashed, how many of those crashes were unique, and the number of iterations.
To stop fuzzing, simply press Ctrl+C. Since Trdelnik uses Honggfuzz internally, you can also use environment variables to pass parameters directly to the fuzzer.
for example:
Running the above command will stop the fuzzer after the first crash and -Q
The flag allows you to view Solana logs. The Solana example program uses: msg!
Macro to display values of update command parameters input1
and input2
This can be seen in the logs with values 254 and 255 respectively.
Debugging programs using fuzzy test conflict files
As you can see in the log above, the data that caused our program to crash was stored in the following fuzz crash file: ./trdelnik-tests/hfuzz_workspace/fuzz_target
folder.
You can now use this crash file and “play” the crash in the debugger to check for bugs. The corresponding command is:
So in our case <TARGET_NAME>
and <CRASH_FILE_PATH>
With actual values. The conflicting files may have different names and must be modified accordingly.
In our case the resulting command would be:
Running this command should build the entire project for debugging. The Fuzzer then runs the program using the parameters provided in the crash file and re-simulates the crash for checking.
Now you can see in the debugger that the thread panics at ‘Black Magic not Supported!’ (programs/my-trdelnik-fuzz-test/src/lib.rs:27:13).
This is an expected result because we intentionally introduced panic into the program. input1
Variables are like us MAGIC_NUMBER
It’s 254.
While in the debugger, you can run any of the following: help
To perform further actions, run the command or exit by running: q
Command.
If you want to find other bugs in your program, you can uncomment the if statement.
at buggy_math_function
Features of the Solana program programs/my-trdelnik-fuzz-test/src/lib.rs
And try running the fuzzer again. After some time, new unique conflicts should be found that can be re-analyzed using the debugger as shown previously.
conclusion
We showed how to use the Trdelnik framework to write fuzzy tests for Solana programs written in Anchor. You can find the full example project in Trdelnik’s GitHub repository.
Trdelnik’s goal is not to implement a new fuzzer, but to provide a convenient way to use the existing Honggfuzz Fuzzer without the hassle of setting up a test environment.
It’s as simple as initializing Trdelnik in your project and you’re ready for fuzzing!
The next step is fuzzing accounts and instruction flows.
Stay tuned for more tutorials in the future!