aldanor / fast-float-rust

Super-fast float parser in Rust (now part of Rust core)
https://docs.rs/fast-float
Apache License 2.0
275 stars 20 forks source link

Null Pointer Dereference Vulnerability in AsciiStr Struct #38

Open ydongyeon opened 2 weeks ago

ydongyeon commented 2 weeks ago

Summary

An unsafe memory access vulnerability in the first method of the AsciiStr struct allows arbitrary memory access when empty buffer is provided, potentially triggering undefined behavior.

Details

Hi,

First, I want to extend my gratitude for maintaining this excellent crate. I’ve identified a potential security vulnerability: Null Pointer Dereference.

Environment:

Steps to reproduce:

(1) Replace fast-float-rust/src/common.rs with the modified common.rs file as below.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_read_write_u64() {
        let bytes = b"01234567";
        let string = AsciiStr::new(bytes);
        let int = string.read_u64();
        assert_eq!(int, 0x3736353433323130);

        let int = bytes.read_u64();
        assert_eq!(int, 0x3736353433323130);

        let mut slc = [0u8; 8];
        slc.write_u64(0x3736353433323130);
        assert_eq!(&slc, bytes);
    }

    #[test]
    fn test_first() {
        let bytes = b"";
        let string = AsciiStr::new(bytes);
        let _int = string.first();

    }
}

(2) Run the test using the ASan flag.

RUSTFLAGS="-Zsanitizer=address" cargo +nightly test test_first --target x86_64-unknown-linux-gnu

Details: // fast-float-rust/src/common.rs

#[inline]
pub fn first(&self) -> u8 {
    unsafe { *self.ptr }
}

In this case, the first method within the AsciiStr struct uses the unsafe keyword to access memory without performing bounds checking. Specifically, it directly dereferences a pointer offset by self.ptr. This approach violates Rust’s memory safety guarantees, as it can lead to invalid memory access if empty buffer is provided.

Actual results : running 1 test AddressSanitizer:DEADLYSIGNAL ==287389==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff41ff0e1 at pc 0x555555fb451b bp 0x7ffff4cfe390 sp 0x7ffff4cfe388 READ of size 1 at 0x7ffff41ff0e1 thread T1

...

running 1 test AddressSanitizer:DEADLYSIGNAL ==1314890==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000001 (pc 0x555555686ff8 bp 0x7ffff4afe500 sp 0x7ffff4afe430 T1) ==1314890==The signal is caused by a READ memory access. ==1314890==Hint: address points to the zero page.

0 0x555555686ff8 in fast_float::common::AsciiStr::first::h7c7d95f37c6a3fac /home/dy3199/Fuzzing-Test/fast-float-rust/src/common.rs:39:18

#1 0x5555556863f9 in fast_float::common::tests::test_first::h3c5ff0d067513552 /home/dy3199/Fuzzing-Test/fast-float-rust/src/common.rs:233:20
#2 0x5555556851c6 in fast_float::common::tests::test_first::_$u7b$$u7b$closure$u7d$$u7d$::ha3d5811e9d0d67ad /home/dy3199/Fuzzing-Test/fast-float-rust/src/common.rs:230:20
#3 0x5555556865a5 in core::ops::function::FnOnce::call_once::h74662c1aabf327be /home/dy3199/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
#4 0x5555556cc4aa in core::ops::function::FnOnce::call_once::h556141b0b8fdbb6d /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/core/src/ops/function.rs:250:5
#5 0x5555556cc4aa in test::__rust_begin_short_backtrace::h0db03bcef8350635 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:621:18
#6 0x5555556cbdd7 in test::run_test_in_process::_$u7b$$u7b$closure$u7d$$u7d$::h2b26e78103d00faf /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:644:60
#7 0x5555556cbdd7 in _$LT$core..panic..unwind_safe..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h9b9bdb051f35126f /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/core/src/panic/unwind_safe.rs:272:9
#8 0x5555556cbdd7 in std::panicking::try::do_call::he60eac3431009064 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:557:40
#9 0x5555556cbdd7 in std::panicking::try::h557550d22ddb3954 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:520:19
#10 0x5555556cbdd7 in std::panic::catch_unwind::hdcc5278601cde996 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panic.rs:358:14
#11 0x5555556cbdd7 in test::run_test_in_process::h8aa3c0adb7acfe05 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:644:27
#12 0x5555556cbdd7 in test::run_test::_$u7b$$u7b$closure$u7d$$u7d$::he4cb7f7454d67ec7 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:565:43
#13 0x55555568fff3 in test::run_test::_$u7b$$u7b$closure$u7d$$u7d$::hbf7c34f88a9b7c99 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:595:41
#14 0x55555568fff3 in std::sys::backtrace::__rust_begin_short_backtrace::hd1596cbf522e6291 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/sys/backtrace.rs:154:18
#15 0x5555556936a1 in std::thread::Builder::spawn_unchecked_::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$::h1477a43ebce5a9b4 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/thread/mod.rs:521:17
#16 0x5555556936a1 in _$LT$core..panic..unwind_safe..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::hc950408692c13207 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/core/src/panic/unwind_safe.rs:272:9
#17 0x5555556936a1 in std::panicking::try::do_call::h6fa8fa2b2d7081fc /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:557:40
#18 0x5555556936a1 in std::panicking::try::he538ba63cebd8a21 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:520:19
#19 0x5555556936a1 in std::panic::catch_unwind::h96162f68493eada2 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panic.rs:358:14
#20 0x5555556936a1 in std::thread::Builder::spawn_unchecked_::_$u7b$$u7b$closure$u7d$$u7d$::he08e8b93402f8ad5 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/thread/mod.rs:520:30
#21 0x5555556936a1 in core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::h3a43bb91522745d7 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/core/src/ops/function.rs:250:5
#22 0x555555725cba in _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::h8054fe12b89a640e /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/alloc/src/boxed.rs:2454:9
#23 0x555555725cba in _$LT$alloc..boxed..Box$LT$F$C$A$GT$$u20$as$u20$core..ops..function..FnOnce$LT$Args$GT$$GT$::call_once::heda44ff6113dd81b /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/alloc/src/boxed.rs:2454:9
#24 0x555555725cba in std::sys::pal::unix::thread::Thread::new::thread_start::h44e9704e75fad799 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/sys/pal/unix/thread.rs:105:17
#25 0x555555656d56 in asan_thread_start(void*) /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:239:28
#26 0x7ffff7c94ac2 in start_thread nptl/pthread_create.c:442:8
#27 0x7ffff7d2684f  misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

==1314890==Register values: rax = 0x0000000000000001 rbx = 0x00007ffff4afe460 rcx = 0x0000000000000001 rdx = 0x0000000000000001
rdi = 0x00000ffffe7bfe04 rsi = 0x0000000000000000 rbp = 0x00007ffff4afe500 rsp = 0x00007ffff4afe430
r8 = 0x0000000000000000 r9 = 0x00007fffffffff01 r10 = 0x00007fffffffff01 r11 = 0x142ad95dd2047d01
r12 = 0x0000000000000000 r13 = 0x00007ffff4afecf0 r14 = 0x000000003b9aca00 r15 = 0x7fffffffffffffff
AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV /home/dy3199/Fuzzing-Test/fast-float-rust/src/common.rs:39:18 in fast_float::common::AsciiStr::first::h7c7d95f37c6a3fac Thread T1 created by T0 here:

0 0x55555563ebf1 in pthread_create /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_interceptors.cpp:250:3

#1 0x555555725af1 in std::sys::pal::unix::thread::Thread::new::hb67b1d5b2580523d /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/sys/pal/unix/thread.rs:84:19
#2 0x5555556c9dd3 in std::thread::Builder::spawn_unchecked_::hc86feaa291a60338 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/thread/mod.rs:560:30
#3 0x5555556c9dd3 in std::thread::Builder::spawn_unchecked::hc983240132661237 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/thread/mod.rs:441:32
#4 0x5555556c9dd3 in std::thread::Builder::spawn::h3a197a5d5820ab55 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/thread/mod.rs:374:18
#5 0x5555556c9dd3 in test::run_test::h88139ddd3d467ad1 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:595:27
#6 0x5555556aa385 in test::run_tests::h80cc0c21bf0f58b6 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:405:21
#7 0x5555556aa385 in test::console::run_tests_console::h8c103401cf2149f7 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/console.rs:322:5
#8 0x5555556c6e6b in test::test_main::hbfe100dfe8e6cf70 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:138:19
#9 0x5555556c7d1a in test::test_main_static::h2642b7736db7c98a /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/test/src/lib.rs:160:5
#10 0x555555686472 in fast_float::main::h42499d73809b0109 /home/dy3199/Fuzzing-Test/fast-float-rust/src/lib.rs
#11 0x555555716d8f in core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::ha1f1642b8e9bd74a /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/core/src/ops/function.rs:284:13
#12 0x555555716d8f in std::panicking::try::do_call::hecea85fe7d6edda5 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:557:40
#13 0x555555716d8f in std::panicking::try::h4f8eb6ebee171f35 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:520:19
#14 0x555555716d8f in std::panic::catch_unwind::h77a3b376b0c0e5e5 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panic.rs:358:14
#15 0x555555716d8f in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h24187c45e225a599 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/rt.rs:143:48
#16 0x555555716d8f in std::panicking::try::do_call::hf548e8a6255337a0 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:557:40
#17 0x555555716d8f in std::panicking::try::h147ab63ac654d01d /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panicking.rs:520:19
#18 0x555555716d8f in std::panic::catch_unwind::h67c6eb5eb863d18c /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/panic.rs:358:14
#19 0x555555716d8f in std::rt::lang_start_internal::h84c7f39b990c0649 /rustc/18b1161ec9eeab8927f91405bca0ddf59a4a26c9/library/std/src/rt.rs:143:20
#20 0x555555685ca8 in std::rt::lang_start::he27b66d3540504f2 /home/dy3199/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:163:17
#21 0x55555568649d in main (/home/dy3199/Fuzzing-Test/fast-float-rust/target/x86_64-unknown-linux-gnu/debug/deps/fast_float-a992c632fa42aaed+0x13249d) (BuildId: cbabe39baadd923d6dbe556b8d2f331cd6db1da2)

==1314890==ABORTING

Screenshot 2024-10-13 at 3 30 12 PM

Recommended Patch: Given the potential memory safety issues, I would suggest: Implementing input validation in first to safely handle unexpected empty struct.

#[inline]
pub fn first(&self) -> u8 {
    if !self.is_empty() {
        unsafe { *self.ptr }
    }
}

Impact

The first method in the AsciiStr struct of the fast-float-rust library introduces an unsafe vulnerability by allowing arbitrary memory access without bounds checking. This flaw can lead to undefined behavior. Although exploiting this vulnerability may be challenging, it undermines Rust’s core memory safety guarantees.

Although these bugs may be difficult to exploit in practice, I understand that the Rust community reports such issues to RUSTSEC in an effort to further enhance memory safety. Rust considers these issues critical to memory safety, regardless of whether they have been exploited, and takes proactive measures to either fix or report them. I have included references to similar cases below for your consideration.

Panic on overflow in subtraction RUSTSEC-2023-0078 RUSTSEC-2022-0078 RUSTSEC-2022-0012 [Potential unsoundnesses (not yet determined) with use of unsafe · Issue #37 · aldanor/fast-float-rust](https://github.com/aldanor/fast-float-rust/issues/37) Fix Undefined Behavior in check_len · Issue #28 · aldanor/fast-float-rust

Alexhuszagh commented 2 weeks ago

I think this should be changed to use first(&self) -> Option<u8> and move the call of is_empty in parse_float to parse_number to guarantee local safety invariants.

ydongyeon commented 2 weeks ago

Thank you for the quick response! I agree that if you apply the patch as you suggested, it will indeed help ensure the local safety invariants. I appreciate your insight and look forward to the update.

Alexhuszagh commented 1 hour ago

This is patched in fast-float2 as of the latest commits, and soon to be in release 0.2.2: https://github.com/Alexhuszagh/fast-float-rust/pull/7