Skip to content

Commit 7c0100d

Browse files
authored
Merge pull request #135 from Alexhuszagh/writesafe
This introduces numerous different layers of security enhancements: 1. Removal of most unsafe code (according to count-unsafe, the code went from 160 unsafe functions and 3088 unsafe exprs to 8 unsafe functions and 1248 unsafe exprs, However, all the remaining unsafe code has much clearly documented safety guarantees and is isolated into safe abstractions. 2. Clear documentation of the locations where unsafe code is used and at the crate-level documentation so it's clearly visible. A security policy has also been added, with stricter requirements for soundness with PRs. Closes #100.
2 parents 0970a59 + 7df1e74 commit 7c0100d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+570
-1007
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Cargo.lock
66
/build
77
*.pyc
88
TODO.md
9+
*.diff
910

1011
# Perftools
1112
perf.data

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3232
- Improved performance of integer and float parsing, particularly with small integers.
3333
- Removed almost all unsafety in `lexical-util` and clearly documented the preconditions to use safely.
3434
- Removed almost all unsafety in `lexical-write-integer` and clearly documented the preconditions to use safely.
35+
- Writing special numbers even with invalid float formats is now always memory safe.
3536

3637
### Removed
3738

3839
- Support for mips (MIPS), mipsel (MIPS LE), mips64 (MIPS64 BE), and mips64el (MIPS64 LE) on Linux.
3940
- All `_unchecked` API methods, since the performance benefits are dubious and it makes safety invariant checking much harder.
41+
- The `safe` and `nightly` features, since ASM is now supported by the MSRV on stable and opt-in for memory-safe indexing is no longer relevant.
4042

4143
## [0.8.5] 2022-06-06
4244

README.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ Lexical is highly customizable, and contains numerous other optional features:
139139
<blockquote>With format enabled, the number format is dictated through bitflags and masks packed into a <code>u128</code>. These dictate the valid syntax of parsed and written numbers, including enabling digit separators, requiring integer or fraction digits, and toggling case-sensitive exponent characters.</blockquote>
140140
- **compact**: &ensp; Optimize for binary size at the expense of performance.
141141
<blockquote>This minimizes the use of pre-computed tables, producing significantly smaller binaries.</blockquote>
142-
- **safe**: &ensp; Requires all array indexing to be bounds-checked.
143-
<blockquote>This has limited impact for number parsers, since they use safe indexing except where indexing without bounds checking and can general be shown to be sound. The number writers frequently use unsafe indexing, since we can easily over-estimate the number of digits in the output due to the fixed-length input. We use comprehensive fuzzing, UB detection via miri, and proving local safe invariants to ensure correctness without impacting performance.</blockquote>
144142
- **f16**: &ensp; Add support for numeric conversions to-and-from 16-bit floats.
145143
<blockquote>Adds <code>f16</code>, a half-precision IEEE-754 floating-point type, and <code>bf16</code>, the Brain Float 16 type, and numeric conversions to-and-from these floats. Note that since these are storage formats, and therefore do not have native arithmetic operations, all conversions are done using an intermediate <code>f32</code>.</blockquote>
146144

@@ -331,16 +329,12 @@ lexical-core should also work on a wide variety of other architectures and ISAs.
331329

332330
The currently supported versions are:
333331
- v1.0.x
334-
- v0.8.x
335-
- v0.7.x (Maintenance)
336-
- v0.6.x (Maintenance)
332+
333+
Due to security considerations, all other versions have been yanked.
337334

338335
**Rustc Compatibility**
339336

340-
- v0.8.x supports 1.63+, including stable, beta, and nightly.
341-
- v0.8.x supports 1.51+, including stable, beta, and nightly.
342-
- v0.7.x supports 1.37+, including stable, beta, and nightly.
343-
- v0.6.x supports Rustc 1.24+, including stable, beta, and nightly.
337+
- v1.0.x supports 1.63+, including stable, beta, and nightly.
344338

345339
Please report any errors compiling a supported lexical-core version on a compatible Rustc version.
346340

lexical-benchmark/parse-float/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ power-of-two = ["lexical-util/power-of-two", "lexical-parse-float/power-of-two"]
3030
format = ["lexical-util/format", "lexical-parse-float/format"]
3131
compact = ["lexical-util/compact", "lexical-parse-float/compact"]
3232
asm = []
33-
nightly = ["lexical-parse-float/nightly"]
3433
integers = ["lexical-util/integers"]
3534
floats = ["lexical-util/floats"]
3635
json = []
Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// Optimized black box using the nicer assembly syntax.
2-
#[cfg(feature = "asm")]
32
pub fn black_box(mut dummy: f64) -> f64 {
43
// THe `asm!` macro was stabilized in 1.59.0.
54
use core::arch::asm;
@@ -12,14 +11,3 @@ pub fn black_box(mut dummy: f64) -> f64 {
1211
dummy
1312
}
1413
}
15-
16-
// Optimized black box using the nicer assembly syntax.
17-
#[cfg(not(feature = "asm"))]
18-
#[allow(forgetting_copy_types)]
19-
pub fn black_box(dummy: f64) -> f64 {
20-
unsafe {
21-
let x = core::ptr::read_volatile(&dummy);
22-
core::mem::forget(dummy);
23-
x
24-
}
25-
}

lexical-benchmark/parse-float/denormal30.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
// Inline ASM was stabilized in 1.59.0.
2-
// FIXME: Remove when the MSRV for Rustc >= 1.59.0.
3-
#![allow(stable_features)]
4-
#![cfg_attr(feature = "nightly", feature(asm))]
5-
61
mod black_box;
72
use black_box::black_box;
83
use lexical_parse_float::FromLexical;

lexical-benchmark/parse-float/denormal6400.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
// Inline ASM was stabilized in 1.59.0.
2-
// FIXME: Remove when the MSRV for Rustc >= 1.59.0.
3-
#![allow(stable_features)]
4-
#![cfg_attr(feature = "nightly", feature(asm))]
5-
61
mod black_box;
72
use black_box::black_box;
83
use lexical_parse_float::FromLexical;

lexical-benchmark/write-float/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ harness = false
4444
name = "random"
4545
path = "random.rs"
4646
harness = false
47+
48+
[[bench]]
49+
name = "special"
50+
path = "special.rs"
51+
harness = false
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#[macro_use]
2+
mod input;
3+
4+
use core::mem;
5+
use core::time::Duration;
6+
7+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
8+
use lexical_write_float::ToLexical;
9+
10+
// Default random data size.
11+
const COUNT: usize = 1000;
12+
13+
// BENCHES
14+
15+
macro_rules! gen_vec {
16+
($exp_mask:expr, $i:ident, $f:ident) => {{
17+
let mut vec: Vec<$f> = Vec::with_capacity(COUNT);
18+
for _ in 0..COUNT {
19+
let value = fastrand::$i($exp_mask..);
20+
// NOTE: We want mem::transmute, not from_bits because we
21+
// don't want the special handling of from_bits
22+
#[allow(clippy::transmute_int_to_float)]
23+
vec.push(unsafe { mem::transmute::<$i, $f>(value) });
24+
}
25+
vec
26+
}};
27+
}
28+
29+
macro_rules! bench {
30+
($fn:ident, $name:literal) => {
31+
fn $fn(criterion: &mut Criterion) {
32+
let mut group = criterion.benchmark_group($name);
33+
group.measurement_time(Duration::from_secs(5));
34+
let exp32_mask: u32 = 0x7F800000;
35+
let exp64_mask: u64 = 0x7FF0000000000000;
36+
37+
let f32_data = gen_vec!(exp32_mask, u32, f32);
38+
let f64_data = gen_vec!(exp64_mask, u64, f64);
39+
40+
write_float_generator!(group, "f32", f32_data.iter(), format32);
41+
write_float_generator!(group, "f64", f64_data.iter(), format64);
42+
}
43+
};
44+
}
45+
46+
bench!(random_special, "random:special");
47+
criterion_group!(special_benches, random_special);
48+
criterion_main!(special_benches);

lexical-core/Cargo.toml

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,6 @@ compact = [
100100
"lexical-parse-integer?/compact",
101101
"lexical-parse-float?/compact"
102102
]
103-
# Ensure only safe indexing is used.
104-
# This is only relevant for the number writers, since the parsers
105-
# are memory safe by default (and only use memory unsafety when
106-
# is the trivial to prove correct).
107-
safe = [
108-
"lexical-write-integer?/safe",
109-
"lexical-write-float?/safe",
110-
"lexical-parse-integer?/safe",
111-
"lexical-parse-float?/safe"
112-
]
113-
# Add support for nightly-only features.
114-
nightly = [
115-
"lexical-write-integer?/nightly",
116-
"lexical-write-float?/nightly",
117-
"lexical-parse-integer?/nightly",
118-
"lexical-parse-float?/nightly"
119-
]
120103
# Enable support for 16-bit floats.
121104
f16 = [
122105
"lexical-util/f16",

lexical-parse-float/Cargo.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,6 @@ compact = [
6868
"lexical-util/compact",
6969
"lexical-parse-integer/compact"
7070
]
71-
# Ensure only safe indexing is used. This is effectively a no-op, since all
72-
# examples of potential memory unsafety are trivial to prove safe.
73-
safe = ["lexical-parse-integer/safe"]
74-
# Add support for nightly-only features.
75-
nightly = ["lexical-parse-integer/nightly"]
7671
# Enable support for 16-bit floats.
7772
f16 = ["lexical-util/f16"]
7873

lexical-parse-float/src/bigint.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,6 @@ use crate::table::get_large_int_power;
1818
/// # Safety
1919
///
2020
/// Safe if `index < array.len()`.
21-
#[cfg(feature = "safe")]
22-
macro_rules! index_unchecked {
23-
($x:ident[$i:expr]) => {
24-
$x[$i]
25-
};
26-
}
27-
28-
#[cfg(not(feature = "safe"))]
2921
macro_rules! index_unchecked {
3022
($x:ident[$i:expr]) => {
3123
// SAFETY: safe if `index < array.len()`.

lexical-parse-float/src/fpu.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//!
77
//! It is therefore also subject to a Apache2.0/MIT license.
88
9-
#![cfg(feature = "nightly")]
109
#![doc(hidden)]
1110

1211
pub use fpu_precision::set_precision;

lexical-parse-float/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
//! * `radix` - Add support for strings of any radix.
3131
//! * `format` - Add support for parsing custom integer formats.
3232
//! * `compact` - Reduce code size at the cost of performance.
33-
//! * `safe` - Ensure only memory-safe indexing is used.
34-
//! * `nightly` - Enable assembly instructions to control FPU rounding modes.
3533
//!
3634
//! # Note
3735
//!

lexical-parse-float/src/libm.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,6 @@
2828
/// # Safety
2929
///
3030
/// Safe if `index < array.len()`.
31-
#[cfg(feature = "safe")]
32-
macro_rules! i {
33-
($x:ident, $i:expr) => {
34-
$x[$i]
35-
};
36-
}
37-
38-
/// Index an array without bounds checking.
39-
///
40-
/// # Safety
41-
///
42-
/// Safe if `index < array.len()`.
43-
#[cfg(not(feature = "safe"))]
4431
macro_rules! i {
4532
($x:ident, $i:expr) => {
4633
unsafe { *$x.get_unchecked($i) }

lexical-parse-float/src/number.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use lexical_util::format::NumberFormat;
99

1010
use crate::float::RawFloat;
11-
#[cfg(feature = "nightly")]
1211
use crate::fpu::set_precision;
1312

1413
/// Representation of a number as the significant digits and exponent.
@@ -65,7 +64,6 @@ impl<'a> Number<'a> {
6564
// function takes care of setting the precision on architectures which
6665
// require setting it by changing the global state (like the control word of the
6766
// x87 FPU).
68-
#[cfg(feature = "nightly")]
6967
let _cw = set_precision::<F>();
7068

7169
if self.is_fast_path::<F, FORMAT>() {
@@ -105,7 +103,6 @@ impl<'a> Number<'a> {
105103
let format = NumberFormat::<FORMAT> {};
106104
debug_assert!(format.mantissa_radix() == format.exponent_base());
107105

108-
#[cfg(feature = "nightly")]
109106
let _cw = set_precision::<F>();
110107

111108
let radix = format.radix();

lexical-parse-integer/Cargo.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ radix = ["lexical-util/radix", "power-of-two"]
4646
format = ["lexical-util/format"]
4747
# Reduce code size at the cost of performance.
4848
compact = ["lexical-util/compact"]
49-
# Ensure only safe indexing is used. This is a no-op, since all
50-
# examples of potential memory unsafety are trivial to prove safe.
51-
safe = []
52-
# Add support for nightly-only features.
53-
nightly = []
5449

5550
# Internal only features.
5651
# Enable the lint checks.

lexical-util/src/algorithm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::num::Integer;
44

55
/// Copy bytes from source to destination.
66
///
7-
/// This is only used in our compactt and radix integer formatted, so
7+
/// This is only used in our compact and radix integer formatted, so
88
/// performance isn't the highest consideration here.
99
#[inline(always)]
1010
#[cfg(feature = "write")]

lexical-util/src/num.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,15 @@ pub trait Float: Number + ops::Neg<Output = Self> {
713713
!self.is_odd()
714714
}
715715

716+
/// Returns true if the float needs a negative sign when serializing it.
717+
///
718+
/// This is true if it's `-0.0` or it's below 0 and not NaN. But inf values
719+
/// need the sign.
720+
#[inline(always)]
721+
fn needs_negative_sign(self) -> bool {
722+
self.is_sign_negative() && !self.is_nan()
723+
}
724+
716725
/// Get exponent component from the float.
717726
#[inline(always)]
718727
fn exponent(self) -> i32 {

lexical-util/src/skip.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,12 +789,12 @@ macro_rules! skip_iterator_bytesiter_base {
789789

790790
#[inline(always)]
791791
fn read_u32(&self) -> Option<u32> {
792-
unsafe { self.byte.read_u32() }
792+
self.byte.read_u32()
793793
}
794794

795795
#[inline(always)]
796796
fn read_u64(&self) -> Option<u64> {
797-
unsafe { self.byte.read_u64() }
797+
self.byte.read_u64()
798798
}
799799

800800
#[inline(always)]

lexical-write-float/Cargo.toml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,6 @@ compact = [
6767
"lexical-util/compact",
6868
"lexical-write-integer/compact"
6969
]
70-
# Ensure only safe indexing is used.
71-
# This is not enabled by default for writers, due to the performance
72-
# costs, and since input can be easily validated to avoid buffer overwrites.
73-
safe = ["lexical-write-integer/safe"]
74-
# Add support for nightly-only features.
75-
nightly = ["lexical-write-integer/nightly"]
7670
# Enable support for 16-bit floats.
7771
f16 = ["lexical-util/f16"]
7872

0 commit comments

Comments
 (0)