Skip to content

Commit 4bf4f81

Browse files
authored
Limit recursion and output size (#48)
* Limit recursion and output size This commit adds some rudimentary fuzzing for this crate and fixes the fuzz bugs that it immediately encountered, namely very large recurison and huge amounts of output. A recursion depth is added to the v0 printer as well as a limited output printing. For now these values are not configurable, but we could perhaps in the future add configuration if necessary. Closes #47 * Tweak CI config
1 parent 0058a82 commit 4bf4f81

File tree

7 files changed

+139
-21
lines changed

7 files changed

+139
-21
lines changed

.github/workflows/main.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,28 @@ jobs:
99
matrix:
1010
rust: [stable, beta, nightly]
1111
steps:
12-
- uses: actions/checkout@master
12+
- uses: actions/checkout@v2
1313
- name: Install Rust
1414
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
1515
- run: cargo build --all
1616
- run: cargo test --all
1717

18+
fuzz_targets:
19+
name: Fuzz Targets
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v2
23+
# Note that building with fuzzers requires nightly since it uses unstable
24+
# flags to rustc.
25+
- run: rustup update nightly && rustup default nightly
26+
- run: cargo install cargo-fuzz --vers "^0.10"
27+
- run: cargo fuzz build --dev
28+
1829
rustfmt:
1930
name: Rustfmt
2031
runs-on: ubuntu-latest
2132
steps:
22-
- uses: actions/checkout@master
33+
- uses: actions/checkout@v2
2334
- name: Install Rust
2435
run: rustup update stable && rustup default stable && rustup component add rustfmt
2536
- run: cargo fmt -- --check
@@ -28,7 +39,7 @@ jobs:
2839
name: Publish Documentation
2940
runs-on: ubuntu-latest
3041
steps:
31-
- uses: actions/checkout@master
42+
- uses: actions/checkout@v2
3243
- name: Install Rust
3344
run: rustup update stable && rustup default stable
3445
- name: Build documentation
@@ -40,4 +51,4 @@ jobs:
4051
git add .
4152
git -c user.name='ci' -c user.email='ci' commit -m init
4253
git push -f -q https://git:${{ secrets.github_token }}@github.com/${{ github.repository }} HEAD:gh-pages
43-
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master'
54+
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main'

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Rust compiler symbol demangling.
1212
"""
1313

1414
[workspace]
15-
members = ["crates/capi"]
15+
members = ["crates/capi", "fuzz"]
1616

1717
[dependencies]
1818
core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' }

fuzz/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
target
3+
corpus
4+
artifacts

fuzz/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "rustc-demangle-fuzz"
3+
version = "0.0.0"
4+
authors = ["Automatically generated"]
5+
publish = false
6+
edition = "2018"
7+
8+
[package.metadata]
9+
cargo-fuzz = true
10+
11+
[dependencies]
12+
libfuzzer-sys = "0.4"
13+
rustc-demangle = { path = '..' }
14+
15+
[[bin]]
16+
name = "demangle"
17+
path = "fuzz_targets/demangle.rs"
18+
test = false
19+
doc = false

fuzz/fuzz_targets/demangle.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![no_main]
2+
use libfuzzer_sys::fuzz_target;
3+
use std::fmt::Write;
4+
5+
fuzz_target!(|data: &str| {
6+
let mut s = String::new();
7+
let sym = rustc_demangle::demangle(data);
8+
drop(write!(s, "{}", sym));
9+
s.truncate(0);
10+
11+
if let Ok(sym) = rustc_demangle::try_demangle(data) {
12+
drop(write!(s, "{}", sym));
13+
}
14+
});

src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,23 @@ mod tests {
376376
"<core::result::Result<!, E> as std::process::Termination>::report::hfc41d0da4a40b3e8"
377377
);
378378
}
379+
380+
#[test]
381+
fn limit_recursion() {
382+
use std::fmt::Write;
383+
let mut s = String::new();
384+
assert!(write!(s, "{}", super::demangle("_RNvB_1a")).is_err());
385+
}
386+
387+
#[test]
388+
fn limit_output() {
389+
use std::fmt::Write;
390+
let mut s = String::new();
391+
assert!(write!(
392+
s,
393+
"{}",
394+
super::demangle("RYFG_FGyyEvRYFF_EvRYFFEvERLB_B_B_ERLRjB_B_B_")
395+
)
396+
.is_err());
397+
}
379398
}

src/v0.rs

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
use core::char;
22
use core::fmt;
3-
use core::fmt::Display;
3+
use core::fmt::{Display, Write};
4+
5+
// Maximum recursion depth when printing symbols before we just bail out saying
6+
// "this symbol is invalid"
7+
const MAX_DEPTH: u32 = 1_000;
8+
9+
// Approximately the maximum size of the symbol that we'll print. This is
10+
// approximate because it only limits calls writing to `LimitedFormatter`, but
11+
// not all writes exclusively go through `LimitedFormatter`. Some writes go
12+
// directly to the underlying formatter, but when that happens we always write
13+
// at least a little to the `LimitedFormatter`.
14+
const MAX_APPROX_SIZE: usize = 1_000_000;
415

516
/// Representation of a demangled symbol name.
617
pub struct Demangle<'a> {
@@ -58,13 +69,18 @@ pub fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> {
5869

5970
impl<'s> Display for Demangle<'s> {
6071
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72+
let mut remaining = MAX_APPROX_SIZE;
6173
let mut printer = Printer {
6274
parser: Ok(Parser {
6375
sym: self.inner,
6476
next: 0,
6577
}),
66-
out: f,
78+
out: LimitedFormatter {
79+
remaining: &mut remaining,
80+
inner: f,
81+
},
6782
bound_lifetime_depth: 0,
83+
depth: 0,
6884
};
6985
printer.print_path(true)
7086
}
@@ -563,8 +579,9 @@ impl<'s> Parser<'s> {
563579

564580
struct Printer<'a, 'b: 'a, 's> {
565581
parser: Result<Parser<'s>, Invalid>,
566-
out: &'a mut fmt::Formatter<'b>,
582+
out: LimitedFormatter<'a, 'b>,
567583
bound_lifetime_depth: u32,
584+
depth: u32,
568585
}
569586

570587
/// Mark the parser as errored, print `?` and return early.
@@ -603,12 +620,25 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
603620
self.parser_mut().map(|p| p.eat(b)) == Ok(true)
604621
}
605622

623+
fn bump_depth(&mut self) -> fmt::Result {
624+
self.depth += 1;
625+
if self.depth > MAX_DEPTH {
626+
Err(fmt::Error)
627+
} else {
628+
Ok(())
629+
}
630+
}
631+
606632
/// Return a nested parser for a backref.
607633
fn backref_printer<'c>(&'c mut self) -> Printer<'c, 'b, 's> {
608634
Printer {
609635
parser: self.parser_mut().and_then(|p| p.backref()),
610-
out: self.out,
636+
out: LimitedFormatter {
637+
remaining: self.out.remaining,
638+
inner: self.out.inner,
639+
},
611640
bound_lifetime_depth: self.bound_lifetime_depth,
641+
depth: self.depth,
612642
}
613643
}
614644

@@ -625,11 +655,11 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
625655
// Try to print lifetimes alphabetically first.
626656
if depth < 26 {
627657
let c = (b'a' + depth as u8) as char;
628-
c.fmt(self.out)
658+
c.fmt(self.out.inner)
629659
} else {
630660
// Use `'_123` after running out of letters.
631661
self.out.write_str("_")?;
632-
depth.fmt(self.out)
662+
depth.fmt(self.out.inner)
633663
}
634664
}
635665
None => invalid!(self),
@@ -684,16 +714,17 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
684714
}
685715

686716
fn print_path(&mut self, in_value: bool) -> fmt::Result {
717+
self.bump_depth()?;
687718
let tag = parse!(self, next);
688719
match tag {
689720
b'C' => {
690721
let dis = parse!(self, disambiguator);
691722
let name = parse!(self, ident);
692723

693-
name.fmt(self.out)?;
694-
if !self.out.alternate() {
724+
name.fmt(self.out.inner)?;
725+
if !self.out.inner.alternate() {
695726
self.out.write_str("[")?;
696-
fmt::LowerHex::fmt(&dis, self.out)?;
727+
fmt::LowerHex::fmt(&dis, self.out.inner)?;
697728
self.out.write_str("]")?;
698729
}
699730
}
@@ -712,22 +743,22 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
712743
match ns {
713744
'C' => self.out.write_str("closure")?,
714745
'S' => self.out.write_str("shim")?,
715-
_ => ns.fmt(self.out)?,
746+
_ => ns.fmt(self.out.inner)?,
716747
}
717748
if !name.ascii.is_empty() || !name.punycode.is_empty() {
718749
self.out.write_str(":")?;
719-
name.fmt(self.out)?;
750+
name.fmt(self.out.inner)?;
720751
}
721752
self.out.write_str("#")?;
722-
dis.fmt(self.out)?;
753+
dis.fmt(self.out.inner)?;
723754
self.out.write_str("}")?;
724755
}
725756

726757
// Implementation-specific/unspecified namespaces.
727758
None => {
728759
if !name.ascii.is_empty() || !name.punycode.is_empty() {
729760
self.out.write_str("::")?;
730-
name.fmt(self.out)?;
761+
name.fmt(self.out.inner)?;
731762
}
732763
}
733764
}
@@ -761,6 +792,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
761792
}
762793
_ => invalid!(self),
763794
}
795+
self.depth -= 1;
764796
Ok(())
765797
}
766798

@@ -782,6 +814,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
782814
return self.out.write_str(ty);
783815
}
784816

817+
self.bump_depth()?;
785818
match tag {
786819
b'R' | b'Q' => {
787820
self.out.write_str("&")?;
@@ -898,6 +931,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
898931
self.print_path(false)?;
899932
}
900933
}
934+
self.depth -= 1;
901935
Ok(())
902936
}
903937

@@ -932,7 +966,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
932966
}
933967

934968
let name = parse!(self, ident);
935-
name.fmt(self.out)?;
969+
name.fmt(self.out.inner)?;
936970
self.out.write_str(" = ")?;
937971
self.print_type()?;
938972
}
@@ -971,7 +1005,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
9711005
_ => invalid!(self),
9721006
};
9731007

974-
if !self.out.alternate() {
1008+
if !self.out.inner.alternate() {
9751009
self.out.write_str(": ")?;
9761010
let ty = basic_type(ty_tag).unwrap();
9771011
self.out.write_str(ty)?;
@@ -993,7 +1027,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
9931027
for c in hex.chars() {
9941028
v = (v << 4) | (c.to_digit(16).unwrap() as u64);
9951029
}
996-
v.fmt(self.out)
1030+
v.fmt(self.out.inner)
9971031
}
9981032

9991033
fn print_const_int(&mut self) -> fmt::Result {
@@ -1156,3 +1190,20 @@ mod tests {
11561190
);
11571191
}
11581192
}
1193+
1194+
struct LimitedFormatter<'a, 'b> {
1195+
remaining: &'a mut usize,
1196+
inner: &'a mut fmt::Formatter<'b>,
1197+
}
1198+
1199+
impl Write for LimitedFormatter<'_, '_> {
1200+
fn write_str(&mut self, s: &str) -> fmt::Result {
1201+
match self.remaining.checked_sub(s.len()) {
1202+
Some(amt) => {
1203+
*self.remaining = amt;
1204+
self.inner.write_str(s)
1205+
}
1206+
None => Err(fmt::Error),
1207+
}
1208+
}
1209+
}

0 commit comments

Comments
 (0)