Skip to content

Commit 2138a27

Browse files
authored
Fix an infinite loop in wasm-smith (#1834)
* Fix an infinite loop in wasm-smith This fixes a possible infinite loop in wasm-smith when the gc proposal is enabled. When the GC proposal is enabled and a minimum number of types were specified it could get wasm-smith stuck in an infinite loop where it would either always create an empty rec group or it would always try to clone an empty rec group. The fix here is to thread a boolean which toggles whether an empty rec group is allowed based on whether we're generating the minimum number of types or any extra types. This is a relatively old bug so I'm not sure why this hasn't been discovered prior to this. Local fuzzing in Wasmtime found this quite quickly, so I may have just gotten unlucky. We also tend to not look at timeouts on OSS-Fuzz that closely as well, so perhaps this is a reminder to actually do that. * Review feedback
1 parent 60b9475 commit 2138a27

File tree

1 file changed

+37
-10
lines changed

1 file changed

+37
-10
lines changed

crates/wasm-smith/src/core.rs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@ pub(crate) enum DuplicateImportsBehavior {
174174
Disallowed,
175175
}
176176

177+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178+
enum AllowEmptyRecGroup {
179+
Yes,
180+
No,
181+
}
182+
177183
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178184
enum MaxTypeLimit {
179185
ModuleTypes,
@@ -549,14 +555,14 @@ impl Module {
549555
fn arbitrary_types(&mut self, u: &mut Unstructured) -> Result<()> {
550556
assert!(self.config.min_types <= self.config.max_types);
551557
while self.types.len() < self.config.min_types {
552-
self.arbitrary_rec_group(u)?;
558+
self.arbitrary_rec_group(u, AllowEmptyRecGroup::No)?;
553559
}
554560
while self.types.len() < self.config.max_types {
555561
let keep_going = u.arbitrary().unwrap_or(false);
556562
if !keep_going {
557563
break;
558564
}
559-
self.arbitrary_rec_group(u)?;
565+
self.arbitrary_rec_group(u, AllowEmptyRecGroup::Yes)?;
560566
}
561567
Ok(())
562568
}
@@ -586,20 +592,28 @@ impl Module {
586592
index
587593
}
588594

589-
fn arbitrary_rec_group(&mut self, u: &mut Unstructured) -> Result<()> {
595+
fn arbitrary_rec_group(
596+
&mut self,
597+
u: &mut Unstructured,
598+
kind: AllowEmptyRecGroup,
599+
) -> Result<()> {
590600
let rec_group_start = self.types.len();
591601

592602
assert!(matches!(self.max_type_limit, MaxTypeLimit::ModuleTypes));
593603

594604
if self.config.gc_enabled {
595605
// With small probability, clone an existing rec group.
596-
if self.clonable_rec_groups().next().is_some() && u.ratio(1, u8::MAX)? {
597-
return self.clone_rec_group(u);
606+
if self.clonable_rec_groups(kind).next().is_some() && u.ratio(1, u8::MAX)? {
607+
return self.clone_rec_group(u, kind);
598608
}
599609

600610
// Otherwise, create a new rec group with multiple types inside.
601611
let max_rec_group_size = self.config.max_types - self.types.len();
602-
let rec_group_size = u.int_in_range(0..=max_rec_group_size)?;
612+
let min_rec_group_size = match kind {
613+
AllowEmptyRecGroup::Yes => 0,
614+
AllowEmptyRecGroup::No => 1,
615+
};
616+
let rec_group_size = u.int_in_range(min_rec_group_size..=max_rec_group_size)?;
603617
let type_ref_limit = u32::try_from(self.types.len() + rec_group_size).unwrap();
604618
self.max_type_limit = MaxTypeLimit::Num(type_ref_limit);
605619
for _ in 0..rec_group_size {
@@ -621,22 +635,35 @@ impl Module {
621635

622636
/// Returns an iterator of rec groups that we could currently clone while
623637
/// still staying within the max types limit.
624-
fn clonable_rec_groups(&self) -> impl Iterator<Item = Range<usize>> + '_ {
638+
fn clonable_rec_groups(
639+
&self,
640+
kind: AllowEmptyRecGroup,
641+
) -> impl Iterator<Item = Range<usize>> + '_ {
625642
self.rec_groups
626643
.iter()
627-
.filter(|r| r.end - r.start <= self.config.max_types.saturating_sub(self.types.len()))
644+
.filter(move |r| {
645+
match kind {
646+
AllowEmptyRecGroup::Yes => {}
647+
AllowEmptyRecGroup::No => {
648+
if r.is_empty() {
649+
return false;
650+
}
651+
}
652+
}
653+
r.end - r.start <= self.config.max_types.saturating_sub(self.types.len())
654+
})
628655
.cloned()
629656
}
630657

631-
fn clone_rec_group(&mut self, u: &mut Unstructured) -> Result<()> {
658+
fn clone_rec_group(&mut self, u: &mut Unstructured, kind: AllowEmptyRecGroup) -> Result<()> {
632659
// NB: this does *not* guarantee that the cloned rec group will
633660
// canonicalize the same as the original rec group and be
634661
// deduplicated. That would reqiure a second pass over the cloned types
635662
// to rewrite references within the original rec group to be references
636663
// into the new rec group. That might make sense to do one day, but for
637664
// now we don't do it. That also means that we can't mark the new types
638665
// as "subtypes" of the old types and vice versa.
639-
let candidates: Vec<_> = self.clonable_rec_groups().collect();
666+
let candidates: Vec<_> = self.clonable_rec_groups(kind).collect();
640667
let group = u.choose(&candidates)?.clone();
641668
let new_rec_group_start = self.types.len();
642669
for index in group {

0 commit comments

Comments
 (0)