RPG:
Rust Library Fuzzing with Pool-based Fuzz Target Generation and Generic Support
About RPG
Rust libraries are ubiquitous in software development. Guaranteeing their correctness and reliability requires thorough analysis and testing. Fuzzing is a popular bug-finding solution, yet it requires writing fuzz targets for libraries. Recently, some automatic fuzz target generation methods have been proposed. However, two challenges remain: (1) how to generate diverse API sequences that prioritize unsafe code and interactions to reveal bugs in Rust libraries; (2) how to provide support for the generic APIs and verify both syntactic and semantic validity of the fuzz targets to achieve a high coverage rate.
In this paper, we propose RPG, an automatic fuzz target synthesis technique to support Rust library fuzzing. RPG uses a pool-based search to generate diverse and unsafe API sequences, and synthesizes fuzz targets with generic support and validity check. The experimental results demonstrate that RPG enhances both the quality of the generated fuzz targets and the bug-finding ability through pool-based search and generic support, substantially outperforming the state-of-the-art. Moreover, RPG has discovered 25 previously unknown bugs from 50 well-known Rust libraries available on Crates.io.
Workflow
We propose a pool-based fuzz target generation for Rust libraries, namely RPG. The main workflow of RPG is illustrated in the following Figure, which consists of the following steps:
Rust Crate Analysis: RPG utilizes static analysis to extract information on functions and data types from Rust libraries (a.k.a. crates), based on which an API dependency graph and a parameter provider are constructed for a given library. The graph records the information of functions (i.e., APIs) that can be invoked in the library, which is then used to guide the API sequence generation; and the provider contains the data types defined in the library as well as a number of commonly used data structure types, which is then used to provide type candidates for generic functions.
API Sequence Generation: this step is crucial in RPG, as an API sequence directly reflects the quality of its corresponding fuzz target. RPG starts with a sequence set, created with consideration of unsafe APIs, and generates API sequences based on the API dependency graph as well as an API pool, which consists of the currently available APIs, which may have multiple occurrences. In particular, when working with generic functions, RPG takes the data types from the provider and maintains the consistency of generic type parameters to ensure validity.
API Sequence Optimization: To ensure the syntactic and semantic validity of the fuzz targets, RPG employs a move-borrow cycle check and generic declaration check to remove invalid API sequences. RPG also performs sequence filtering to obtain a minimal set of sequences that cover the most APIs and their dependencies. Finally, the remaining API sequences are synthesized individually, yielding fuzz targets.
Tool
We have made our experimental data set (50 Rust crates) and the source code of the tool prototype fully available to the public (https://zenodo.org/record/8202159), so that future researchers can reproduce our experiments.
Bug Found (25 new bugs)
We have discovered 25 previously unknown bugs in popular Rust libraries. These bugs were not previously reported. We informed the maintainers, and the developers actively confirmed these bugs with our reports. We are confident that RPG is effective and viable in practice.
Remarks on Bug Types:
`arith`: Arithmetic error, eg. overflows
`oor`: Out of range access
`unwrap`: Call to `unwrap` on `None` or `Err(_)`
`utf-8`: Problem with UTF-8 strings handling, eg. get a char not at a char boundary
`other`: Anything that does not fit in another category, or unclear what the problem is
# Bug 1:
Rust Crate: regex v1.5.4 (https://github.com/rust-lang/regex)
Bug location: src/exec.rs:702:28
Bug Type: arith
Buf Information: Program unexpected panic caused by index out of bound when indexing operation for text with Unanchored ty in Method ExecNoSync::find_literals
Original Bug Report: https://github.com/rust-lang/regex/issues/972
POC:
fn main ()
{
let _local0 = regex::Regex::new("(\0\0\0\0\0\0\u{10}|\0\0\0\0\0)\0\0\0\0\0\0\0\0\0\u{10}|\0\0\0\0\0\0\0");
let _local1_param0_helper1 = _local0.unwrap();
let _local1 = regex::Regex::find_at(&(_local1_param0_helper1), "\0\u{4}\0*****\u{17}***************\0\0\0\0\0\0\0\0\0\0", 35184372153856);
let _local2_param0_helper1 = _local1.unwrap();
regex::Match::end(&(_local2_param0_helper1));
}
Interaction with developers:
# Bug 2:
Rust Crate: regex v1.5.4 (https://github.com/rust-lang/regex)
Bug location: regex/src/re_set.rs:419:1, and regex/src/re_trait.rs:23:23
Bug Type: arith
Buf Information: The function pos() is called by CaptureLocation::get. it calculate index out of bound, which cause a unexpected behavior.
Original Bug Report: https://github.com/rust-lang/regex/issues/950
POC:
fn main()
{
let pattern = "\n\n\n\n&;*****\0\u{2}**\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n****\u{f}*\u{19}\r.\u{19}";
let group_index = 9944060567225171988;
let re = regex::Regex::new(pattern).unwrap();
let locs = re.capture_locations();
dbg!(locs.get(group_index));
}
Interaction with developers:
# Bug 3:
Rust Crate: regex v1.5.4 (https://github.com/rust-lang/regex)
Bug location: regex:461:22
Bug Type: arith
Buf Information: Program unexpected panic caused by index out of bound when indexing operation for text with MatchType::DfaAnchoredReverse matched in Method ExecNoSync::shortest_match_at
Original Bug Report: https://github.com/rust-lang/regex/issues/972
POC:
fn main()
{
let _local0 = regex::RegexBuilder::new("$");
let _local1 = regex::RegexBuilder::build(&(&_local0));
let _local2_param0_helper1 = _local1.unwrap();
regex::Regex::shortest_match_at(&(_local2_param0_helper1), "{S", 8897841259371199355);
}
Interaction with developers:
# Bug 4:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: src/format/scan.rs:173:43
Bug Type: utf-8
Buf Information: Program unexpected panic caused by not a char boundary when indexing operation in Method short_or_long_weekday
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
chrono::naive::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}",
"\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a");
let _local1 = chrono::offset::FixedOffset::east_opt(17367308);
let _local2_param0_helper1 = _local1.unwrap();
chrono::offset::Offset::fix(&(_local2_param0_helper1));
}
Interaction with developers:
# Bug 5:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: src/round.rs:231:67
Bug Type: arith
Buf Information: Program unexpected panic caused by substract overflow when delta_down less than 0 in Method DurationRound::duration_trunc
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp_opt(-4227854320, 1678774288);
let _local1 = chrono::Duration::microseconds(-7019067213869040);
let _local2_param0_helper1 = _local0.unwrap();
chrono::DurationRound::duration_trunc(_local2_param0_helper1, _local1);
}
Interaction with developers:
# Bug 6:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: naïve/date.rs:1524:45
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method NaiveDate::with_month
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp(-8377300, 742391807);
chrono::Datelike::with_month0(&(_local0), 4294967295);
chrono::Datelike::with_month(&(_local0), 4294967295);
}
Interaction with developers:
.# Bug 7:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: naïve/date.rs:1524:45
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method NaiveDate::with_month
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp(-8377300, 742391807);
chrono::Datelike::with_month0(&(_local0), 4294967295);
chrono::Datelike::with_month(&(_local0), 4294967295);
}
Interaction with developers:
See Bug 4.
# Bug 8:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: naïve/date.rs:1562:43
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method NaiveDate::with_day
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp_opt(-754576364, 336909572);
let _local1_param0_helper1 = _local0.unwrap();
chrono::Datelike::with_day0(&(_local1_param0_helper1), 4294967295);
}
Interaction with developers:
See Bug 4.
# Bug 9:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: naive/datetime/mod.rs:479:21
Bug Type: arith
Buf Information: Program unexpected panic caused by multiple overflow in Method NaiveDateTime::timestamp_nanos
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp(-11676614656, 15282199);
chrono::naive::NaiveDateTime::timestamp_nanos(&(&_local0));
}
Interaction with developers:
See Bug 5.
# Bug 10:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: round.rs:198:18
Bug Type: arith
Buf Information: Program unexpected panic caused by substract overflow when delta_down less than 0 in Method DurationRound::duration_round
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp_opt(320041586, 1920103021);
let _local1 = chrono::Duration::nanoseconds(-8923838508697114584);
let _local2_param0_helper1 = _local0.unwrap();
chrono::DurationRound::duration_round(_local2_param0_helper1, _local1);
}
Interaction with developers:
See Bug 5.
# Bug 11:
Rust Crate: chrono v0.4.19 (https://github.com/chronotope/chrono)
Bug location: naïve/date.rs:1524:45
Bug Type: arith
Buf Information: Program unexpected panic caused by substract overflow when delta_down greater than 0 in Method DurationRound::duration_round
Original Bug Report: https://github.com/chronotope/chrono/issues/1010
POC:
fn main()
{
let _local0 = chrono::naive::NaiveDateTime::from_timestamp_opt(-2621440, 0);
let _local1 = chrono::Duration::nanoseconds(-9223372036854771421);
let _local2_param0_helper1 = _local0.unwrap();
chrono::DurationRound::duration_round(_local2_param0_helper1, _local1);
}
Interaction with developers:
See Bug 5.
# Bug 12:
Rust Crate: bumpalo v3.9.1 (https://github.com/fitzgen/bumpalo)
Bug location: src/lib.rs:468:46
Bug Type: unwrap
Buf Information: Allocating too much initial capacity using with_capacity caused unwrap() LayoutError, resulting in panic.
Original Bug Report: https://github.com/fitzgen/bumpalo/issues/200
POC:
fn main()
{
let mut _local0 = bumpalo::Bump::with_capacity(10995706271387654244);
}
Interaction with developers:
# Bug 13:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:544:30
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method GraphemeCursor::is_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(18446742978509668351, 18446744073709551615, false);
let _ = unicode_segmentation::GraphemeCursor::is_boundary(&mut (_local0), "t\u{7f}", 18446744073709551615);
}
Interaction with developers:
# Bug 14:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:406:17
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method GraphemeCursor::provide_context.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(18446707789825836799, 18446744073709551615, false);
unicode_segmentation::GraphemeCursor::provide_context(&mut (_local0), "1", 18446744073709551615);
}
Interaction with developers:
See Bug 13.
# Bug 15:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:716:32
Bug Type: arith
Buf Information: Program unexpected panic caused by subtract overflow in Method GraphemeCursor::prev_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(5404402016221612875, 5425481077020773195, false);
let _ = unicode_segmentation::GraphemeCursor::prev_boundary(&mut (_local0), "KKK", 5425512962414627659);
}
Interaction with developers:
See Bug 13.
# Bug 16:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:543:56
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method GraphemeCursor::is_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(18446744073709551615, 8502796096475496447, false);
let _ = unicode_segmentation::GraphemeCursor::is_boundary(&mut (_local0), "\u{6dd}", 18446744073709551615);
}
Interaction with developers:
See Bug 13.
# Bug 17:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:634:30
Bug Type: arith
Buf Information: Program unexpected panic caused by subtract overflow in Method GraphemeCursor::next_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(5208492444341520456, 5208492444341520431, true);
let _ = unicode_segmentation::GraphemeCursor::next_boundary(&mut (_local0), "HHHHHHHHHHHHH", 5208492589950978632);
}
Interaction with developers:
See Bug 13.
# Bug 18:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:642:17
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method GraphemeCursor::next_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(18446744073709551615, 16212958658533785599, false);
let _ = unicode_segmentation::GraphemeCursor::next_boundary(&mut (_local0), "0", 18446744073709551615);
}
Interaction with developers:
See Bug 13.
# Bug 19:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:406:70
Bug Type: unwrap
Buf Information: Created an object with new() whose `pre_context_offset` is None and unwraped on it, resulting in panic.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(7812738666512280684, 7812738666512280684, true);
unicode_segmentation::GraphemeCursor::provide_context(&mut (_local0), "l ", 7812738666512280684);
}
Interaction with developers:
See Bug 13.
# Bug 20:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:635:34
Bug Type: unwrap
Buf Information: iter.next() out of bound caused unwrap() None, resulting in panic.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(4268070197446523707, 4268070196469563392, false);
let _ = unicode_segmentation::GraphemeCursor::next_boundary(&mut (_local0), "; ", 4268070197446523705);
}
Interaction with developers:
See Bug 13.
# Bug 21:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:553:22
Bug Type: utf-8
Buf Information: Program unexpected panic caused by not a char boundary when indexing `chunk` in Method GraphemeCursor::is_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(8463800222054970741, 8463951407229173877, false);
let _ = unicode_segmentation::GraphemeCursor::is_boundary(&mut (_local0), "Ë", 8463800222054970740);
}
Interaction with developers:
See Bug 13.
# Bug 22:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:634:24
Bug Type: utf-8
Buf Information: Program unexpected panic caused by bytes index out of bound in Method GraphemeCursor::next_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(4268070197446523713, 4268070196471726080 ,false);
let _ = unicode_segmentation::GraphemeCursor::next_boundary(&mut (_local0), "\n\n\n\n\n\n\n\n", 4268070197446522939);
}
Interaction with developers:
See Bug 13.
# Bug 23:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:716:24
Bug Type: utf-8
Buf Information: Program unexpected panic caused by not a char boundary in Method GraphemeCursor::prev_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(5208492444341520467 ,3407250190757808200 ,true);
let _ = unicode_segmentation::GraphemeCursor::prev_boundary(&mut (_local0), "HHHZ\\HHH\0\u{e040}HHK", 5208492444341520456);
}
Interaction with developers:
See Bug 13.
# Bug 24:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:553:22
Bug Type: utf-8
Buf Information: Program unexpected panic caused by not a char boundary in Method GraphemeCursor::is_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(8102099357864587379, 8071550526791090176, false);
let _ = unicode_segmentation::GraphemeCursor::is_boundary(&mut (_local0), "ppݶp", 8102099357864587376);
}
Interaction with developers:
See Bug 13.
# Bug 25:
Rust Crate: unicode-segmentation v1.8.0 (https://github.com/unicode-rs/unicode-segmentation)
Bug location: src/grapheme.rs:543:56
Bug Type: arith
Buf Information: Program unexpected panic caused by add overflow in Method GraphemeCursor::is_boundary.
Original Bug Report: https://github.com/unicode-rs/unicode-segmentation/issues/119
POC:
fn main()
{
let mut _local0 = unicode_segmentation::GraphemeCursor::new(18446744073709551615, 18446744069448138751, false);
let _ = unicode_segmentation::GraphemeCursor::is_boundary(&mut (_local0), "\0\u{13}", 18446744073709551615);
}
Interaction with developers:
See Bug 13.