Skip to content

Commit 5019dd5

Browse files
authored
Merge pull request #949 from hsivonen/overflow
Adjust Punycode overflow checks
2 parents dcfbed3 + 4a14519 commit 5019dd5

File tree

2 files changed

+44
-32
lines changed

2 files changed

+44
-32
lines changed

idna/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "idna"
3-
version = "1.0.1"
3+
version = "1.0.2"
44
authors = ["The rust-url developers"]
55
description = "IDNA (Internationalizing Domain Names in Applications) and Punycode."
66
categories = ["no_std"]

idna/src/punycode.rs

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,16 @@ pub fn decode(input: &str) -> Option<Vec<char>> {
7171
/// Marker for internal vs. external caller to retain old API behavior
7272
/// while tweaking behavior for internal callers.
7373
///
74-
/// External callers retain the old behavior of the pre-existing
75-
/// public entry points to this module by 1) limiting input length
76-
/// to the 32-bit accumulator overflowing and 2) by not performing
77-
/// ASCII case folding.
74+
/// External callers need overflow checks when encoding, but internal
75+
/// callers don't, because `PUNYCODE_ENCODE_MAX_INPUT_LENGTH` is set
76+
/// to 1000, and per RFC 3492 section 6.4, the integer variable does
77+
/// not need to be able to represent values larger than
78+
/// (char::MAX - INITIAL_N) * (PUNYCODE_ENCODE_MAX_INPUT_LENGTH + 1),
79+
/// which is less than u32::MAX.
7880
///
79-
/// Internal callers omit overflow checks due to the input length
80-
/// being constrained before calling into this module. Additionally,
81-
/// when the code unit is `u8`, upper-case ASCII is replaced with
82-
/// lower-case ASCII.
81+
/// External callers need to handle upper-case ASCII when decoding,
82+
/// but internal callers don't, because the internal code calls the
83+
/// decoder only with lower-case inputs.
8384
pub(crate) trait PunycodeCaller {
8485
const EXTERNAL_CALLER: bool;
8586
}
@@ -162,8 +163,6 @@ pub(crate) struct Decoder {
162163

163164
impl Decoder {
164165
/// Split the input iterator and return a Vec with insertions of encoded characters
165-
///
166-
/// XXX: Add a policy parameter to skip overflow checks
167166
pub(crate) fn decode<'a, T: PunycodeCodeUnit + Copy, C: PunycodeCaller>(
168167
&'a mut self,
169168
input: &'a [T],
@@ -192,7 +191,7 @@ impl Decoder {
192191
let mut length = base_len as u32;
193192
let mut code_point = INITIAL_N;
194193
let mut bias = INITIAL_BIAS;
195-
let mut i = 0;
194+
let mut i = 0u32;
196195
let mut iter = input.iter();
197196
loop {
198197
let previous_i = i;
@@ -211,10 +210,8 @@ impl Decoder {
211210
} else {
212211
return Err(());
213212
};
214-
if C::EXTERNAL_CALLER && (digit > (u32::MAX - i) / weight) {
215-
return Err(()); // Overflow
216-
}
217-
i = i.checked_add(digit * weight).ok_or(())?;
213+
let product = digit.checked_mul(weight).ok_or(())?;
214+
i = i.checked_add(product).ok_or(())?;
218215
let t = if k <= bias {
219216
T_MIN
220217
} else if k >= bias + T_MAX {
@@ -225,10 +222,7 @@ impl Decoder {
225222
if digit < t {
226223
break;
227224
}
228-
if C::EXTERNAL_CALLER && (weight > u32::MAX / (BASE - t)) {
229-
return Err(()); // Overflow
230-
}
231-
weight *= BASE - t;
225+
weight = weight.checked_mul(BASE - t).ok_or(())?;
232226
k += BASE;
233227
byte = match iter.next() {
234228
None => return Err(()), // End of input before the end of this delta
@@ -237,13 +231,10 @@ impl Decoder {
237231
}
238232

239233
bias = adapt(i - previous_i, length + 1, previous_i == 0);
240-
if C::EXTERNAL_CALLER && (i / (length + 1) > u32::MAX - code_point) {
241-
return Err(()); // Overflow
242-
}
243234

244235
// i was supposed to wrap around from length+1 to 0,
245236
// incrementing code_point each time.
246-
code_point += i / (length + 1);
237+
code_point = code_point.checked_add(i / (length + 1)).ok_or(())?;
247238
i %= length + 1;
248239
let c = match char::from_u32(code_point) {
249240
Some(c) => c,
@@ -381,11 +372,24 @@ where
381372
}
382373
}
383374

375+
if !C::EXTERNAL_CALLER {
376+
// We should never get an overflow here with the internal caller being
377+
// length-limited, but let's check anyway once here trusting the math
378+
// from RFC 3492 section 6.4 and then omit the overflow checks in the
379+
// loop below.
380+
let len_plus_one = input_length
381+
.checked_add(1)
382+
.ok_or(PunycodeEncodeError::Overflow)?;
383+
len_plus_one
384+
.checked_mul(u32::from(char::MAX) - INITIAL_N)
385+
.ok_or(PunycodeEncodeError::Overflow)?;
386+
}
387+
384388
if basic_length > 0 {
385389
output.write_char('-')?;
386390
}
387391
let mut code_point = INITIAL_N;
388-
let mut delta = 0;
392+
let mut delta = 0u32;
389393
let mut bias = INITIAL_BIAS;
390394
let mut processed = basic_length;
391395
while processed < input_length {
@@ -397,18 +401,26 @@ where
397401
.filter(|&c| c >= code_point)
398402
.min()
399403
.unwrap();
400-
if C::EXTERNAL_CALLER
401-
&& (min_code_point - code_point > (u32::MAX - delta) / (processed + 1))
402-
{
403-
return Err(PunycodeEncodeError::Overflow); // Overflow
404-
}
405404
// Increase delta to advance the decoder’s <code_point,i> state to <min_code_point,0>
406-
delta += (min_code_point - code_point) * (processed + 1);
405+
if C::EXTERNAL_CALLER {
406+
let product = (min_code_point - code_point)
407+
.checked_mul(processed + 1)
408+
.ok_or(PunycodeEncodeError::Overflow)?;
409+
delta = delta
410+
.checked_add(product)
411+
.ok_or(PunycodeEncodeError::Overflow)?;
412+
} else {
413+
delta += (min_code_point - code_point) * (processed + 1);
414+
}
407415
code_point = min_code_point;
408416
for c in input.clone() {
409417
let c = c as u32;
410418
if c < code_point {
411-
delta = delta.checked_add(1).ok_or(PunycodeEncodeError::Overflow)?;
419+
if C::EXTERNAL_CALLER {
420+
delta = delta.checked_add(1).ok_or(PunycodeEncodeError::Overflow)?;
421+
} else {
422+
delta += 1;
423+
}
412424
}
413425
if c == code_point {
414426
// Represent delta as a generalized variable-length integer:

0 commit comments

Comments
 (0)