Skip to content

Commit e8a1a5e

Browse files
committed
Add Hrp type
Instead of accepting `&str` for hrp strings we can add a type `Hrp` and abstract the checking of the string. This is the "parse don't validate" methodology suggested by Clark.
1 parent eb3c06e commit e8a1a5e

File tree

10 files changed

+689
-162
lines changed

10 files changed

+689
-162
lines changed

.github/workflows/fuzz.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
fuzz_target: [decode_rnd, encode_decode]
13+
fuzz_target: [decode_rnd, encode_decode, parse_hrp]
1414
steps:
1515
- name: Install test dependencies
1616
run: sudo apt-get update -y && sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev

embedded/no-allocator/src/main.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use panic_halt as _;
1111

1212
use arrayvec::{ArrayString, ArrayVec};
13-
use bech32::{self, u5, ComboError, FromBase32, ToBase32, Variant};
13+
use bech32::{self, u5, ComboError, FromBase32, ToBase32, Variant, Hrp};
1414
use cortex_m_rt::entry;
1515
use cortex_m_semihosting::{debug, hprintln};
1616

@@ -24,7 +24,9 @@ fn main() -> ! {
2424

2525
[0x00u8, 0x01, 0x02].write_base32(&mut base32).unwrap();
2626

27-
bech32::encode_to_fmt_anycase(&mut encoded, "bech32", &base32, Variant::Bech32)
27+
let hrp = Hrp::parse("bech32").unwrap();
28+
29+
bech32::encode_to_fmt_anycase(&mut encoded, hrp, &base32, Variant::Bech32)
2830
.unwrap()
2931
.unwrap();
3032
test(&*encoded == "bech321qqqsyrhqy2a");
@@ -35,9 +37,9 @@ fn main() -> ! {
3537

3638
let mut scratch = ArrayVec::<u5, 30>::new();
3739

38-
let (hrp, data, variant) =
40+
let (got_hrp, data, variant) =
3941
bech32::decode_lowercase::<ComboError, _, _>(&encoded, &mut decoded, &mut scratch).unwrap();
40-
test(hrp == "bech32");
42+
test(got_hrp == hrp);
4143
let res = ArrayVec::<u8, 30>::from_base32(&data).unwrap();
4244
test(&res == [0x00, 0x01, 0x02].as_ref());
4345
test(variant == Variant::Bech32);

embedded/with-allocator/src/main.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use self::alloc::vec::Vec;
1111
use core::alloc::Layout;
1212

1313
use alloc_cortex_m::CortexMHeap;
14-
use bech32::{self, FromBase32, ToBase32, Variant};
14+
use bech32::{self, FromBase32, ToBase32, Variant, Hrp};
1515
use cortex_m::asm;
1616
use cortex_m_rt::entry;
1717
use cortex_m_semihosting::{debug, hprintln};
@@ -26,8 +26,9 @@ fn main() -> ! {
2626
// Initialize the allocator BEFORE you use it
2727
unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) }
2828

29+
let hrp = Hrp::parse("bech32").unwrap();
2930
let encoded = bech32::encode(
30-
"bech32",
31+
hrp,
3132
vec![0x00, 0x01, 0x02].to_base32(),
3233
Variant::Bech32,
3334
)
@@ -36,8 +37,8 @@ fn main() -> ! {
3637

3738
hprintln!("{}", encoded).unwrap();
3839

39-
let (hrp, data, variant) = bech32::decode(&encoded).unwrap();
40-
test(hrp == "bech32");
40+
let (got_hrp, data, variant) = bech32::decode(&encoded).unwrap();
41+
test(got_hrp == hrp);
4142
test(Vec::<u8>::from_base32(&data).unwrap() == vec![0x00, 0x01, 0x02]);
4243
test(variant == Variant::Bech32);
4344

fuzz/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ path = "fuzz_targets/decode_rnd.rs"
2727

2828
[[bin]]
2929
name = "encode_decode"
30-
path = "fuzz_targets/encode_decode.rs"
30+
path = "fuzz_targets/encode_decode.rs"
31+
32+
[[bin]]
33+
name = "parse_hrp"
34+
path = "fuzz_targets/parse_hrp.rs"

fuzz/fuzz_targets/decode_rnd.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
extern crate bech32;
22

3-
use std::str::FromStr;
4-
53
fn do_test(data: &[u8]) {
64
let data_str = String::from_utf8_lossy(data);
75
let decoded = bech32::decode(&data_str);
@@ -10,7 +8,7 @@ fn do_test(data: &[u8]) {
108
Err(_) => return,
119
};
1210

13-
assert_eq!(bech32::encode(&b32.0, b32.1, b32.2).unwrap(), data_str);
11+
assert_eq!(bech32::encode(b32.0, b32.1, b32.2).unwrap(), data_str);
1412
}
1513

1614
#[cfg(feature = "afl")]

fuzz/fuzz_targets/encode_decode.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
extern crate bech32;
22

33
use std::convert::TryFrom;
4+
use std::str;
5+
6+
use bech32::Hrp;
47

58
fn do_test(data: &[u8]) {
69
if data.len() < 1 {
@@ -13,10 +16,6 @@ fn do_test(data: &[u8]) {
1316
return;
1417
}
1518

16-
let hrp = String::from_utf8_lossy(&data[1..hrp_end])
17-
.to_lowercase()
18-
.to_string();
19-
2019
let dp = data[hrp_end..]
2120
.iter()
2221
.map(|b| bech32::u5::try_from(b % 32).unwrap())
@@ -28,11 +27,21 @@ fn do_test(data: &[u8]) {
2827
bech32::Variant::Bech32
2928
};
3029

31-
if let Ok(data_str) = bech32::encode(&hrp, &dp, variant).map(|b32| b32.to_string()) {
32-
let decoded = bech32::decode(&data_str);
33-
let b32 = decoded.expect("should be able to decode own encoding");
30+
match str::from_utf8(&data[1..hrp_end]) {
31+
Err(_) => return,
32+
Ok(s) => {
33+
match Hrp::parse(&s) {
34+
Err(_) => return,
35+
Ok(hrp) => {
36+
if let Ok(data_str) = bech32::encode(hrp, &dp, variant).map(|b32| b32.to_string()) {
37+
let decoded = bech32::decode(&data_str);
38+
let b32 = decoded.expect("should be able to decode own encoding");
3439

35-
assert_eq!(bech32::encode(&b32.0, &b32.1, b32.2).unwrap(), data_str);
40+
assert_eq!(bech32::encode(b32.0, &b32.1, b32.2).unwrap(), data_str);
41+
}
42+
}
43+
}
44+
}
3645
}
3746
}
3847

fuzz/fuzz_targets/parse_hrp.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
extern crate bech32;
2+
3+
use bech32::Hrp;
4+
5+
fn do_test(data: &[u8]) {
6+
let s = String::from_utf8_lossy(data);
7+
8+
// Make sure parsing garbage doesn't make us crash (from_utf8_lossy should
9+
// contain some garbage, perhaps even invalid chars).
10+
let _ = Hrp::parse(&s);
11+
}
12+
13+
#[cfg(feature = "afl")]
14+
extern crate afl;
15+
#[cfg(feature = "afl")]
16+
fn main() {
17+
afl::read_stdio_bytes(|data| {
18+
do_test(&data);
19+
});
20+
}
21+
22+
#[cfg(feature = "honggfuzz")]
23+
#[macro_use]
24+
extern crate honggfuzz;
25+
#[cfg(feature = "honggfuzz")]
26+
fn main() {
27+
loop {
28+
fuzz!(|data| {
29+
do_test(data);
30+
});
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
37+
let mut b = 0;
38+
for (idx, c) in hex.as_bytes().iter().filter(|&&c| c != b'\n').enumerate() {
39+
b <<= 4;
40+
match *c {
41+
b'A'...b'F' => b |= c - b'A' + 10,
42+
b'a'...b'f' => b |= c - b'a' + 10,
43+
b'0'...b'9' => b |= c - b'0',
44+
_ => panic!("Bad hex"),
45+
}
46+
if (idx & 1) == 1 {
47+
out.push(b);
48+
b = 0;
49+
}
50+
}
51+
}
52+
53+
#[test]
54+
fn duplicate_crash() {
55+
let mut a = Vec::new();
56+
extend_vec_from_hex("ff6c2d", &mut a);
57+
super::do_test(&a);
58+
}
59+
}

0 commit comments

Comments
 (0)