Skip to content

Commit 29dfc1f

Browse files
committed
feat: add validation for leading whitespace in BOLT 12 bech32 strings
Found through differential fuzzing between C-Lightning and rust-lightning: rust-lightning incorrectly accepted offers starting with whitespace when continuation characters (+) were present, while C-Lightning correctly rejected them per BOLT 12 specification. - Add InvalidLeadingWhitespace error variant to Bolt12ParseError - Validate that bech32 strings don't start with whitespace characters - Separate validation logic for first chunk vs continuation chunks - Add test case for leading whitespace validation
1 parent e01663a commit 29dfc1f

File tree

1 file changed

+25
-1
lines changed

1 file changed

+25
-1
lines changed

lightning/src/offers/parse.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,20 @@ mod sealed {
4343
// Offer encoding may be split by '+' followed by optional whitespace.
4444
let encoded = match s.split('+').skip(1).next() {
4545
Some(_) => {
46-
for chunk in s.split('+') {
46+
let mut chunks = s.split('+');
47+
48+
// Check first chunk without trimming
49+
if let Some(first_chunk) = chunks.next() {
50+
if first_chunk.contains(char::is_whitespace) {
51+
return Err(Bolt12ParseError::InvalidLeadingWhitespace);
52+
}
53+
if first_chunk.is_empty() {
54+
return Err(Bolt12ParseError::InvalidContinuation);
55+
}
56+
}
57+
58+
// Check remaining chunks
59+
for chunk in chunks {
4760
let chunk = chunk.trim_start();
4861
if chunk.is_empty() || chunk.contains(char::is_whitespace) {
4962
return Err(Bolt12ParseError::InvalidContinuation);
@@ -123,6 +136,8 @@ pub enum Bolt12ParseError {
123136
/// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
124137
/// across multiple parts (i.e., '+' followed by whitespace).
125138
InvalidContinuation,
139+
/// The bech32 string starts with whitespace, which violates BOLT 12 encoding requirements.
140+
InvalidLeadingWhitespace,
126141
/// The bech32 encoding's human-readable part does not match what was expected for the message
127142
/// being parsed.
128143
InvalidBech32Hrp,
@@ -322,6 +337,15 @@ mod tests {
322337
}
323338
}
324339

340+
#[test]
341+
fn fails_parsing_bech32_encoded_offer_with_leading_whitespace() {
342+
let encoded_offer = "\u{b}lno1pqpzwyq2p32x2um5ypmx2cm5dae8x93pqthvwfzadd7jejes8q9lhc4rvjxd022zv5l44g6qah+\u{b}\u{b}\u{b}\u{b}82ru5rdpnpj";
343+
match encoded_offer.parse::<Offer>() {
344+
Ok(_) => panic!("Valid offer: {}", encoded_offer),
345+
Err(e) => assert_eq!(e, Bolt12ParseError::InvalidLeadingWhitespace),
346+
}
347+
}
348+
325349
#[test]
326350
fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
327351
let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";

0 commit comments

Comments
 (0)