Skip to content

Commit 57c6747

Browse files
authored
Merge pull request #8563 from sylvestre/uufuzz
prepare the publication of uufuzz + add emojis
2 parents ebec2cd + 7675500 commit 57c6747

File tree

9 files changed

+914
-5
lines changed

9 files changed

+914
-5
lines changed

.github/workflows/fuzzing.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,40 @@ concurrency:
1717
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
1818

1919
jobs:
20+
uufuzz-examples:
21+
name: Build and test uufuzz examples
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v5
25+
with:
26+
persist-credentials: false
27+
- uses: dtolnay/rust-toolchain@stable
28+
- uses: Swatinem/rust-cache@v2
29+
with:
30+
shared-key: "uufuzz-cache-key"
31+
cache-directories: "fuzz/target"
32+
- name: Build uufuzz library
33+
run: |
34+
cd fuzz/uufuzz
35+
cargo build --release
36+
- name: Run uufuzz tests
37+
run: |
38+
cd fuzz/uufuzz
39+
cargo test --lib
40+
- name: Build and run uufuzz examples
41+
run: |
42+
cd fuzz/uufuzz
43+
echo "Building all examples..."
44+
cargo build --examples --release
45+
46+
# Run all examples except integration_testing (which has FD issues in CI)
47+
for example in examples/*.rs; do
48+
example_name=$(basename "$example" .rs)
49+
if [ "$example_name" != "integration_testing" ]; then
50+
cargo run --example "$example_name" --release
51+
fi
52+
done
53+
2054
fuzz-build:
2155
name: Build the fuzzers
2256
runs-on: ubuntu-latest

fuzz/uufuzz/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ console = "0.16.0"
1212
libc = "0.2.153"
1313
rand = { version = "0.9.0", features = ["small_rng"] }
1414
similar = "2.5.0"
15-
uucore = { path = "../../src/uucore", features = ["parser"] }
15+
uucore = { version = "0.1.0", path = "../../src/uucore", features = ["parser"] }
1616
tempfile = "3.15.0"

fuzz/uufuzz/README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# uufuzz
2+
3+
A Rust library for **differential fuzzing** of command-line utilities. Originally designed for testing uutils coreutils against GNU coreutils, but can be used to compare any two implementations of command-line tools.
4+
5+
Differential fuzzing is a testing technique that compares the behavior of two implementations of the same functionality using randomly generated inputs. This helps identify bugs, inconsistencies, and security vulnerabilities by finding cases where implementations diverge unexpectedly.
6+
7+
## Features
8+
9+
- **Command Execution**: Run and capture output from both Rust and reference implementations
10+
- **Result Comparison**: Detailed comparison of stdout, stderr, and exit codes with diff output
11+
- **Input Generation**: Utilities for generating random strings, files, and test inputs
12+
- **GNU Compatibility**: Built-in support for detecting and running GNU coreutils
13+
- **Pretty Output**: Colorized and formatted test result display
14+
15+
## Usage
16+
17+
Add to your `Cargo.toml`:
18+
19+
```toml
20+
[dependencies]
21+
uufuzz = "0.1.0"
22+
```
23+
24+
### Basic Example
25+
26+
```rust
27+
use std::ffi::OsString;
28+
use uufuzz::{generate_and_run_uumain, run_gnu_cmd, compare_result};
29+
30+
// Your utility's main function
31+
fn my_echo_main(args: std::vec::IntoIter<OsString>) -> i32 {
32+
// Implementation here
33+
0
34+
}
35+
36+
// Test against GNU implementation
37+
let args = vec![OsString::from("echo"), OsString::from("hello")];
38+
39+
// Run your implementation
40+
let rust_result = generate_and_run_uumain(&args, my_echo_main, None);
41+
42+
// Run GNU implementation
43+
let gnu_result = run_gnu_cmd("echo", &args[1..], false, None).unwrap();
44+
45+
// Compare results
46+
compare_result("echo", "hello", None, &rust_result, &gnu_result, true);
47+
```
48+
49+
### With Pipe Input
50+
51+
```rust
52+
let pipe_input = "test data";
53+
let rust_result = generate_and_run_uumain(&args, my_cat_main, Some(pipe_input));
54+
let gnu_result = run_gnu_cmd("cat", &args[1..], false, Some(pipe_input)).unwrap();
55+
compare_result("cat", "", Some(pipe_input), &rust_result, &gnu_result, true);
56+
```
57+
58+
### Random Input Generation
59+
60+
```rust
61+
use uufuzz::{generate_random_string, generate_random_file};
62+
63+
// Generate random string up to 50 characters
64+
let random_input = generate_random_string(50);
65+
66+
// Generate random temporary file
67+
let file_path = generate_random_file().expect("Failed to create file");
68+
```
69+
70+
## Use Cases
71+
72+
### Fuzzing Testing
73+
Perfect for libFuzzer-based differential fuzzing:
74+
75+
```rust
76+
#![no_main]
77+
use libfuzzer_sys::fuzz_target;
78+
use uufuzz::*;
79+
80+
fuzz_target!(|_data: &[u8]| {
81+
let args = generate_test_args();
82+
let rust_result = generate_and_run_uumain(&args, my_utility_main, None);
83+
let gnu_result = run_gnu_cmd("utility", &args[1..], false, None).unwrap();
84+
compare_result("utility", &format!("{:?}", args), None, &rust_result, &gnu_result, true);
85+
});
86+
```
87+
88+
### Integration Testing
89+
Use in regular test suites to verify compatibility:
90+
91+
```rust
92+
#[test]
93+
fn test_basic_functionality() {
94+
let args = vec![OsString::from("sort"), OsString::from("-n")];
95+
let input = "3\n1\n2\n";
96+
97+
let rust_result = generate_and_run_uumain(&args, sort_main, Some(input));
98+
let gnu_result = run_gnu_cmd("sort", &args[1..], false, Some(input)).unwrap();
99+
100+
assert_eq!(rust_result.stdout, gnu_result.stdout);
101+
assert_eq!(rust_result.exit_code, gnu_result.exit_code);
102+
}
103+
```
104+
105+
## Environment Variables
106+
107+
- `LC_ALL=C` - Automatically set when running GNU commands for consistent behavior
108+
109+
## Platform Support
110+
111+
- **Linux**: Full support with GNU coreutils
112+
- **macOS**: Works with GNU coreutils via Homebrew (`brew install coreutils`)
113+
- **Windows**: Limited support (depends on available reference implementations)
114+
115+
## Examples
116+
117+
The library includes several working examples in the `examples/` directory:
118+
119+
### Running Examples
120+
121+
```bash
122+
# Basic differential comparison
123+
cargo run --example basic_echo
124+
125+
# Pipe input handling
126+
cargo run --example pipe_input
127+
128+
# Simple integration testing (recommended approach)
129+
cargo run --example simple_integration
130+
131+
# Complex integration testing (demonstrates file descriptor handling issues)
132+
cargo run --example integration_testing
133+
```
134+
135+
## License
136+
137+
Licensed under the MIT License, same as uutils coreutils.

fuzz/uufuzz/examples/basic_echo.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// This file is part of the uutils coreutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use std::ffi::OsString;
7+
use uufuzz::{compare_result, generate_and_run_uumain, run_gnu_cmd};
8+
9+
// Mock echo implementation for demonstration
10+
fn mock_echo_main(args: std::vec::IntoIter<OsString>) -> i32 {
11+
let args: Vec<OsString> = args.collect();
12+
13+
// Skip the program name (first argument)
14+
for (i, arg) in args.iter().skip(1).enumerate() {
15+
if i > 0 {
16+
print!(" ");
17+
}
18+
print!("{}", arg.to_string_lossy());
19+
}
20+
println!();
21+
0
22+
}
23+
24+
fn main() {
25+
println!("=== Basic uufuzz Example ===");
26+
27+
// Test against GNU implementation
28+
let args = vec![
29+
OsString::from("echo"),
30+
OsString::from("hello"),
31+
OsString::from("world"),
32+
];
33+
34+
println!("Running mock echo implementation...");
35+
let rust_result = generate_and_run_uumain(&args, mock_echo_main, None);
36+
37+
println!("Running GNU echo...");
38+
match run_gnu_cmd("echo", &args[1..], false, None) {
39+
Ok(gnu_result) => {
40+
println!("Comparing results...");
41+
compare_result(
42+
"echo",
43+
"hello world",
44+
None,
45+
&rust_result,
46+
&gnu_result,
47+
false,
48+
);
49+
}
50+
Err(error_result) => {
51+
println!("Failed to run GNU echo: {}", error_result.stderr);
52+
println!("This is expected if GNU coreutils is not installed");
53+
54+
// Show what our implementation produced
55+
println!("\nOur implementation result:");
56+
println!("Stdout: '{}'", rust_result.stdout);
57+
println!("Stderr: '{}'", rust_result.stderr);
58+
println!("Exit code: {}", rust_result.exit_code);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)