Skip to content

Commit 7675500

Browse files
committed
uufuzz: add examples and them in the CI
1 parent fb6afec commit 7675500

File tree

6 files changed

+667
-0
lines changed

6 files changed

+667
-0
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/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+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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 rand::Rng;
7+
use std::ffi::OsString;
8+
use uufuzz::{generate_and_run_uumain, generate_random_string, run_gnu_cmd};
9+
10+
// Mock echo implementation with some bugs for demonstration
11+
fn mock_buggy_echo_main(args: std::vec::IntoIter<OsString>) -> i32 {
12+
let args: Vec<OsString> = args.collect();
13+
14+
let mut should_add_newline = true;
15+
let mut enable_escapes = false;
16+
let mut start_index = 1;
17+
18+
// Parse arguments (simplified)
19+
for arg in args.iter().skip(1) {
20+
let arg_str = arg.to_string_lossy();
21+
if arg_str == "-n" {
22+
should_add_newline = false;
23+
start_index += 1;
24+
} else if arg_str == "-e" {
25+
enable_escapes = true;
26+
start_index += 1;
27+
} else {
28+
break;
29+
}
30+
}
31+
32+
// Print arguments
33+
for (i, arg) in args.iter().skip(start_index).enumerate() {
34+
if i > 0 {
35+
print!(" ");
36+
}
37+
let arg_str = arg.to_string_lossy();
38+
39+
if enable_escapes {
40+
// Simulate a bug: incomplete escape sequence handling
41+
let processed = arg_str.replace("\\n", "\n").replace("\\t", "\t");
42+
print!("{}", processed);
43+
} else {
44+
print!("{}", arg_str);
45+
}
46+
}
47+
48+
if should_add_newline {
49+
println!();
50+
}
51+
52+
0
53+
}
54+
55+
// Generate test arguments for echo command
56+
fn generate_echo_args() -> Vec<OsString> {
57+
let mut rng = rand::rng();
58+
let mut args = vec![OsString::from("echo")];
59+
60+
// Randomly add flags
61+
if rng.random_bool(0.3) {
62+
// 30% chance
63+
args.push(OsString::from("-n"));
64+
}
65+
if rng.random_bool(0.2) {
66+
// 20% chance
67+
args.push(OsString::from("-e"));
68+
}
69+
70+
// Add 1-3 random string arguments
71+
let num_args = rng.random_range(1..=3);
72+
for _ in 0..num_args {
73+
let arg = generate_random_string(rng.random_range(1..=15));
74+
args.push(OsString::from(arg));
75+
}
76+
77+
args
78+
}
79+
80+
fn main() {
81+
println!("=== Fuzzing Simulation uufuzz Example ===");
82+
println!("This simulates how libFuzzer would test our echo implementation");
83+
println!("against GNU echo with random inputs.\n");
84+
85+
let num_tests = 10;
86+
let mut passed = 0;
87+
let mut failed = 0;
88+
89+
for i in 1..=num_tests {
90+
println!("--- Fuzz Test {} ---", i);
91+
92+
let args = generate_echo_args();
93+
println!(
94+
"Testing with args: {:?}",
95+
args.iter().map(|s| s.to_string_lossy()).collect::<Vec<_>>()
96+
);
97+
98+
// Run our implementation
99+
let rust_result = generate_and_run_uumain(&args, mock_buggy_echo_main, None);
100+
101+
// Run GNU implementation
102+
match run_gnu_cmd("echo", &args[1..], false, None) {
103+
Ok(gnu_result) => {
104+
// Check if results match
105+
let stdout_match = rust_result.stdout.trim() == gnu_result.stdout.trim();
106+
let exit_code_match = rust_result.exit_code == gnu_result.exit_code;
107+
108+
if stdout_match && exit_code_match {
109+
println!("✓ PASS: Implementations match");
110+
passed += 1;
111+
} else {
112+
println!("✗ FAIL: Implementations differ");
113+
failed += 1;
114+
115+
// Show the difference in a controlled way (not panicking like compare_result)
116+
if !stdout_match {
117+
println!(" Stdout difference:");
118+
println!(
119+
" Ours: '{}'",
120+
rust_result.stdout.trim().replace('\n', "\\n")
121+
);
122+
println!(
123+
" GNU: '{}'",
124+
gnu_result.stdout.trim().replace('\n', "\\n")
125+
);
126+
}
127+
if !exit_code_match {
128+
println!(
129+
" Exit code difference: {} vs {}",
130+
rust_result.exit_code, gnu_result.exit_code
131+
);
132+
}
133+
}
134+
}
135+
Err(error_result) => {
136+
println!("⚠ GNU echo not available: {}", error_result.stderr);
137+
println!(" Our result: '{}'", rust_result.stdout.trim());
138+
// Don't count this as pass or fail
139+
continue;
140+
}
141+
}
142+
143+
println!();
144+
}
145+
146+
println!("=== Fuzzing Results ===");
147+
println!("Total tests: {}", num_tests);
148+
println!("Passed: {}", passed);
149+
println!("Failed: {}", failed);
150+
151+
if failed > 0 {
152+
println!(
153+
"\n⚠ Found {} discrepancies! In real fuzzing, these would be investigated.",
154+
failed
155+
);
156+
println!("This demonstrates how differential fuzzing can find bugs in implementations.");
157+
} else {
158+
println!("\n✓ All tests passed! The implementations appear compatible.");
159+
}
160+
161+
println!("\nIn a real libfuzzer setup, this would run thousands of iterations");
162+
println!("automatically with more sophisticated input generation.");
163+
}

0 commit comments

Comments
 (0)