Skip to content

Commit

Permalink
Use checked arithmetic in encoded_size
Browse files Browse the repository at this point in the history
previously encoded_size could silently overflow usize, resulting in
write past the bounds of the buffer allocated by reserve. this changes
encoded_size to return an option, with none if overflow occurs.
presently callers simply panic on this case, but it could conceivably be
rendered as an error in the future

credit to Andrew Ayer for reporting this vulnerability
  • Loading branch information
alicemaz committed May 3, 2017
1 parent 21a9389 commit 24ead98
Showing 1 changed file with 75 additions and 61 deletions.
136 changes: 75 additions & 61 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,31 +171,36 @@ pub fn decode<T: ?Sized + AsRef<[u8]>>(input: &T) -> Result<Vec<u8>, DecodeError
///}
///```
pub fn encode_config<T: ?Sized + AsRef<[u8]>>(input: &T, config: Config) -> String {
let mut buf = String::with_capacity(encoded_size(input.as_ref().len(), config));
let mut buf = match encoded_size(input.as_ref().len(), config) {
Some(n) => String::with_capacity(n),
None => panic!("integer overflow when calculating buffer size")
};

encode_config_buf(input, config, &mut buf);

buf
}

/// calculate the base64 encoded string size, including padding
fn encoded_size(bytes_len: usize, config: Config) -> usize {
let rem = bytes_len % 3;

let complete_input_chunks = bytes_len / 3;
let complete_output_chars = complete_input_chunks * 4;
let printing_output_chars = if rem == 0 {
complete_output_chars
} else {
complete_output_chars + 4
};
fn encoded_size(bytes_len: usize, config: Config) -> Option<usize> {
let printing_output_chars = bytes_len
.checked_add(2)
.map(|x| x / 3)
.and_then(|x| x.checked_mul(4));

//TODO this is subtly wrong but in a not dangerous way
//pushing patch with identical to previous behavior, then fixing
let line_ending_output_chars = match config.line_wrap {
LineWrap::NoWrap => 0,
LineWrap::Wrap(n, LineEnding::CRLF) => printing_output_chars / n * 2,
LineWrap::Wrap(n, LineEnding::LF) => printing_output_chars / n,
LineWrap::NoWrap => Some(0),
LineWrap::Wrap(n, LineEnding::CRLF) =>
printing_output_chars.map(|y| y / n).and_then(|y| y.checked_mul(2)),
LineWrap::Wrap(n, LineEnding::LF) =>
printing_output_chars.map(|y| y / n),
};

return printing_output_chars + line_ending_output_chars;
printing_output_chars.and_then(|x|
line_ending_output_chars.and_then(|y| x.checked_add(y))
)
}

///Encode arbitrary octets as base64.
Expand Down Expand Up @@ -224,7 +229,11 @@ pub fn encode_config_buf<T: ?Sized + AsRef<[u8]>>(input: &T, config: Config, buf
};

// reserve to make sure the memory we'll be writing to with unsafe is allocated
buf.reserve(encoded_size(input_bytes.len(), config));
let resv_size = match encoded_size(input_bytes.len(), config) {
Some(n) => n,
None => panic!("integer overflow when calculating buffer size"),
};
buf.reserve(resv_size);

let orig_buf_len = buf.len();
let mut fast_loop_output_buf_len = orig_buf_len;
Expand Down Expand Up @@ -579,52 +588,52 @@ mod tests {

#[test]
fn encoded_size_correct() {
assert_eq!(0, encoded_size(0, STANDARD));
assert_eq!(Some(0), encoded_size(0, STANDARD));

assert_eq!(4, encoded_size(1, STANDARD));
assert_eq!(4, encoded_size(2, STANDARD));
assert_eq!(4, encoded_size(3, STANDARD));
assert_eq!(Some(4), encoded_size(1, STANDARD));
assert_eq!(Some(4), encoded_size(2, STANDARD));
assert_eq!(Some(4), encoded_size(3, STANDARD));

assert_eq!(8, encoded_size(4, STANDARD));
assert_eq!(8, encoded_size(5, STANDARD));
assert_eq!(8, encoded_size(6, STANDARD));
assert_eq!(Some(8), encoded_size(4, STANDARD));
assert_eq!(Some(8), encoded_size(5, STANDARD));
assert_eq!(Some(8), encoded_size(6, STANDARD));

assert_eq!(12, encoded_size(7, STANDARD));
assert_eq!(12, encoded_size(8, STANDARD));
assert_eq!(12, encoded_size(9, STANDARD));
assert_eq!(Some(12), encoded_size(7, STANDARD));
assert_eq!(Some(12), encoded_size(8, STANDARD));
assert_eq!(Some(12), encoded_size(9, STANDARD));

assert_eq!(72, encoded_size(54, STANDARD));
assert_eq!(Some(72), encoded_size(54, STANDARD));

assert_eq!(76, encoded_size(55, STANDARD));
assert_eq!(76, encoded_size(56, STANDARD));
assert_eq!(76, encoded_size(57, STANDARD));
assert_eq!(Some(76), encoded_size(55, STANDARD));
assert_eq!(Some(76), encoded_size(56, STANDARD));
assert_eq!(Some(76), encoded_size(57, STANDARD));

assert_eq!(80, encoded_size(58, STANDARD));
assert_eq!(Some(80), encoded_size(58, STANDARD));
}

#[test]
fn encoded_size_correct_mime() {
assert_eq!(0, encoded_size(0, MIME));
assert_eq!(Some(0), encoded_size(0, MIME));

assert_eq!(4, encoded_size(1, MIME));
assert_eq!(4, encoded_size(2, MIME));
assert_eq!(4, encoded_size(3, MIME));
assert_eq!(Some(4), encoded_size(1, MIME));
assert_eq!(Some(4), encoded_size(2, MIME));
assert_eq!(Some(4), encoded_size(3, MIME));

assert_eq!(8, encoded_size(4, MIME));
assert_eq!(8, encoded_size(5, MIME));
assert_eq!(8, encoded_size(6, MIME));
assert_eq!(Some(8), encoded_size(4, MIME));
assert_eq!(Some(8), encoded_size(5, MIME));
assert_eq!(Some(8), encoded_size(6, MIME));

assert_eq!(12, encoded_size(7, MIME));
assert_eq!(12, encoded_size(8, MIME));
assert_eq!(12, encoded_size(9, MIME));
assert_eq!(Some(12), encoded_size(7, MIME));
assert_eq!(Some(12), encoded_size(8, MIME));
assert_eq!(Some(12), encoded_size(9, MIME));

assert_eq!(72, encoded_size(54, MIME));
assert_eq!(Some(72), encoded_size(54, MIME));

assert_eq!(78, encoded_size(55, MIME));
assert_eq!(78, encoded_size(56, MIME));
assert_eq!(78, encoded_size(57, MIME));
assert_eq!(Some(78), encoded_size(55, MIME));
assert_eq!(Some(78), encoded_size(56, MIME));
assert_eq!(Some(78), encoded_size(57, MIME));

assert_eq!(82, encoded_size(58, MIME));
assert_eq!(Some(82), encoded_size(58, MIME));
}

#[test]
Expand All @@ -636,26 +645,31 @@ mod tests {
LineWrap::Wrap(76, LineEnding::LF)
);

assert_eq!(0, encoded_size(0, config));
assert_eq!(Some(0), encoded_size(0, config));

assert_eq!(Some(4), encoded_size(1, config));
assert_eq!(Some(4), encoded_size(2, config));
assert_eq!(Some(4), encoded_size(3, config));

assert_eq!(4, encoded_size(1, config));
assert_eq!(4, encoded_size(2, config));
assert_eq!(4, encoded_size(3, config));
assert_eq!(Some(8), encoded_size(4, config));
assert_eq!(Some(8), encoded_size(5, config));
assert_eq!(Some(8), encoded_size(6, config));

assert_eq!(8, encoded_size(4, config));
assert_eq!(8, encoded_size(5, config));
assert_eq!(8, encoded_size(6, config));
assert_eq!(Some(12), encoded_size(7, config));
assert_eq!(Some(12), encoded_size(8, config));
assert_eq!(Some(12), encoded_size(9, config));

assert_eq!(12, encoded_size(7, config));
assert_eq!(12, encoded_size(8, config));
assert_eq!(12, encoded_size(9, config));
assert_eq!(Some(72), encoded_size(54, config));

assert_eq!(72, encoded_size(54, config));
assert_eq!(Some(77), encoded_size(55, config));
assert_eq!(Some(77), encoded_size(56, config));
assert_eq!(Some(77), encoded_size(57, config));

assert_eq!(77, encoded_size(55, config));
assert_eq!(77, encoded_size(56, config));
assert_eq!(77, encoded_size(57, config));
assert_eq!(Some(81), encoded_size(58, config));
}

assert_eq!(81, encoded_size(58, config));
#[test]
fn encoded_size_overflow() {
assert_eq!(None, encoded_size(std::usize::MAX, STANDARD));
}
}

0 comments on commit 24ead98

Please sign in to comment.