Skip to content

Commit

Permalink
Fixed an integer overflow bug and a read-1-past-the-array.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Huszagh committed Dec 16, 2018
1 parent 759a74d commit cc87783
Show file tree
Hide file tree
Showing 22 changed files with 223 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
name = "lexical"
readme = "README.md"
repository = "https://github.com/Alexhuszagh/rust-lexical"
version = "1.8.0"
version = "1.8.1"
exclude = [
"data/*",
"benches/*",
Expand Down
2 changes: 1 addition & 1 deletion lexical-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
name = "lexical-core"
readme = "README.md"
repository = "https://github.com/Alexhuszagh/rust-lexical/tree/master/lexical-core"
version = "0.1.1"
version = "0.1.2"

[badges]
travis-ci = { repository = "Alexhuszagh/rust-lexical" }
Expand Down
10 changes: 6 additions & 4 deletions lexical-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Low-level, FFI-compatible, lexical conversion routines for use in a `no_std` con
- [Float to String](#float-to-string)
- [String to Float](#string-to-float)
- [Arbitrary-Precision Arithmetic](#arbitrary-precision-arithmetic)
- [Comparison to Algorithm M and dtoa](#comparison-to-algorithm-m-and-dtoa)
- [Algorithm Background and Comparison](#algorithm-background-and-comparison)

# Features

Expand All @@ -37,13 +37,15 @@ Float parsing is difficult to do correctly, and major bugs have been found in im

Although Lexical may contain bugs leading to rounding error, it is tested against a comprehensive suite of random-data and near-halfway representations, and should be fast and correct for the vast majority of use-cases.

Finally, due to the heavy use of unsafe code, Lexical-core is fuzzed using cargo-fuzz, to avoid memory errors.

# Caveats

Lexical uses unsafe code in the back-end for performance, and therefore may introduce memory-safety issues. Although the code is tested with wide variety of inputs to minimize the risk of memory-safety bugs, and then unittests are run are under Valgrind, no guarantees are made and you should use it at your own risk.

Finally, for non-decimal (base 10) floats, lexical's float-to-string implementation is lossy, resulting in rounding for a small subset of inputs (up to 0.1% of the total value).

# Details
# Implementation Details

## Float to String

Expand All @@ -68,9 +70,9 @@ To use Algorithm M, use the feature `algorithm_m` when compiling lexical.

## Arbitrary-Precision Arithmetic

Lexical uses arbitrary-precision arithmetic to exactly represent strings between two floating-point representations with more than 36 digits, with various optimizations for multiplication and division relative to Rust's current implementation. The arbitrary-precision arithmetic logic is not independent on memory allocation: the default slow-path algorithm only uses the stack, while Algorithm M uses the heap.
Lexical uses arbitrary-precision arithmetic to exactly represent strings between two floating-point representations with more than 36 digits, with various optimizations for multiplication and division relative to Rust's current implementation. The arbitrary-precision arithmetic logic is not independent on memory allocation: the default slow-path algorithm only uses the stack, and Algorithm M only uses the heap when the `radix` feature is enabled.

## Comparison to Algorithm M
## Algorithm Background and Comparison

For close-to-halfway representations of a decimal string `s`, where `s` is close between two representations, `b` and the next float `b+u`, arbitrary-precision arithmetic is used to determine the correct representation. This means `s` is close to `b+h`, where `h` is the halfway point between `b` and `b+u`.

Expand Down
4 changes: 4 additions & 0 deletions lexical-core/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

target
corpus
artifacts
75 changes: 75 additions & 0 deletions lexical-core/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@

[package]
name = "lexical-core-fuzz"
version = "0.0.1"
authors = ["Alex Huszagh <[email protected]>"]
publish = false

[package.metadata]
cargo-fuzz = true

[dependencies.lexical-core]
path = ".."

[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "atof32"
path = "fuzz_targets/atof32.rs"

[[bin]]
name = "atof64"
path = "fuzz_targets/atof64.rs"

[[bin]]
name = "atoi8"
path = "fuzz_targets/atoi8.rs"

[[bin]]
name = "atoi16"
path = "fuzz_targets/atoi16.rs"

[[bin]]
name = "atoi32"
path = "fuzz_targets/atoi32.rs"

[[bin]]
name = "atoi64"
path = "fuzz_targets/atoi64.rs"

[[bin]]
name = "atoi128"
path = "fuzz_targets/atoi128.rs"

[[bin]]
name = "atoisize"
path = "fuzz_targets/atoisize.rs"

[[bin]]
name = "atou8"
path = "fuzz_targets/atou8.rs"

[[bin]]
name = "atou16"
path = "fuzz_targets/atou16.rs"

[[bin]]
name = "atou32"
path = "fuzz_targets/atou32.rs"

[[bin]]
name = "atou64"
path = "fuzz_targets/atou64.rs"

[[bin]]
name = "atou128"
path = "fuzz_targets/atou128.rs"

[[bin]]
name = "atousize"
path = "fuzz_targets/atousize.rs"
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atof32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atof::try_atof32_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atof64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atof::try_atof64_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atoi128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atoi128_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atoi16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atoi16_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atoi32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atoi32_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atoi64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atoi64_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atoi8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atoi8_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atoisize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atoisize_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atou128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atou128_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atou16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atou16_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atou32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atou32_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atou64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atou64_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atou8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atou8_slice(10, data);
});
7 changes: 7 additions & 0 deletions lexical-core/fuzz/fuzz_targets/atousize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate lexical_core;

fuzz_target!(|data: &[u8]| {
let _ = lexical_core::atoi::try_atousize_slice(10, data);
});
15 changes: 11 additions & 4 deletions lexical-core/src/atof/algorithm/correct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,15 +352,15 @@ pub(super) fn normalize_mantissa<M>(mut mantissa: M, radix: u32, mut exponent: i
// to do.
while mantissa >= radix4 && (mantissa % radix4).is_zero() {
mantissa /= radix4;
exponent += 4;
exponent = exponent.saturating_add(4);
}
while mantissa >= radix2 && (mantissa % radix2).is_zero() {
mantissa /= radix2;
exponent += 2;
exponent = exponent.saturating_add(2);
}
if (mantissa % radix).is_zero() {
mantissa /= radix;
exponent += 1;
exponent = exponent.saturating_add(1);
}
(mantissa, exponent)
}
Expand All @@ -386,7 +386,6 @@ unsafe fn parse_float<M>(radix: u32, first: *const u8, last: *const u8)
// we should try to normalize the mantissa exponent if possible.
let exponent = slc.mantissa_exponent();
let (mantissa, exponent) = normalize_mantissa::<M>(mantissa, radix, exponent);

(mantissa, state, slc, exponent)
}

Expand Down Expand Up @@ -738,6 +737,14 @@ unsafe fn pown_to_native<F>(radix: u32, first: *const u8, last: *const u8, lossy
// Literal 0, return early.
// Value cannot be truncated, since we discard leading 0s.
return (F::ZERO, state);
} else if exponent > 0x40000000 {
// Extremely large exponent, will always be infinity.
// Avoid potential overflows in exponent addition.
return (F::INFINITY, state);
} else if exponent < -0x40000000 {
// Extremely small exponent, will always be zero.
// Avoid potential overflows in exponent addition.
return (F::ZERO, state);
} else if !state.is_truncated() {
// Try last fast path to exact, no mantissa truncation
let (float, valid) = fast_path::<F>(mantissa, radix, exponent);
Expand Down
8 changes: 8 additions & 0 deletions lexical-core/src/atof/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,14 @@ mod tests {
assert!(atof64_slice(10, b"INF").is_infinite());
assert!(atof64_slice(10, b"+inf").is_infinite());
assert!(atof64_slice(10, b"-inf").is_infinite());

// Check various reports from a fuzzer
assert_eq!(0.0, atof64_slice(10, b"0e"));
assert_eq!(0.0, atof64_slice(10, b"0.0e"));
assert_eq!(0.0, atof64_slice(10, b".E"));
assert_eq!(0.0, atof64_slice(10, b".e"));
assert_eq!(0.0, atof64_slice(10, b"E2252525225"));
assert_eq!(f64::INFINITY, atof64_slice(10, b"2E200000000000"));
}

#[test]
Expand Down
34 changes: 19 additions & 15 deletions lexical-core/src/atoi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,21 +227,25 @@ pub(crate) unsafe fn filter_sign<T, Cb>(state: &mut ParseState, radix: u32, last
where T: Integer,
Cb: FnOnce(&mut T, &mut ParseState, u32, *const u8)
{
match *state.curr {
b'+' => {
state.increment();
let value = value::<T, Cb>(state, radix, last, cb);
(value, 1)
},
b'-' => {
state.increment();
let value = value::<T, Cb>(state, radix, last, cb);
(value, -1)
},
_ => {
let value = value::<T, Cb>(state, radix, last, cb);
(value, 1)
},
if state.curr == last {
(T::ZERO, 1)
} else {
match *state.curr {
b'+' => {
state.increment();
let value = value::<T, Cb>(state, radix, last, cb);
(value, 1)
},
b'-' => {
state.increment();
let value = value::<T, Cb>(state, radix, last, cb);
(value, -1)
},
_ => {
let value = value::<T, Cb>(state, radix, last, cb);
(value, 1)
},
}
}
}

Expand Down

0 comments on commit cc87783

Please sign in to comment.