Skip to content

Matrix::new() drops uninitialized memory

https://gitlab.com/dvshapkin/alg-ds/-/blob/a533f2a1520dc1a3688e8bd3a1e7c0b60eb5f3a9/src/ds/matrix.rs#L106-112

https://gitlab.com/dvshapkin/alg-ds/-/blob/a533f2a1520dc1a3688e8bd3a1e7c0b60eb5f3a9/src/ds/matrix.rs#L140-142

Description

Matrix::new() internally calls Matrix::fill_with() which uses *ptr = value pattern to initialize the buffer. This pattern assumes that there is an initialized struct at the address and drops it, which results in dropping of uninitialized struct.

Demonstration

  • Crate: alg_ds
  • Version: 0.3.1
  • OS: Ubuntu 18.04.5 LTS
  • Rust: rustc 1.45.2 (d3fb005a3 2020-07-31)
  • Cargo flags: --release
#![forbid(unsafe_code)]

use alg_ds::ds::matrix::Matrix;
use std::sync::atomic::{AtomicUsize, Ordering};

static creation_cnt: AtomicUsize = AtomicUsize::new(0);
static drop_cnt: AtomicUsize = AtomicUsize::new(0);

#[derive(Clone)]
struct DropDetector(u32);

impl Default for DropDetector {
    fn default() -> Self {
        creation_cnt.fetch_add(1, Ordering::Relaxed);
        DropDetector(12345)
    }
}

impl Drop for DropDetector {
    fn drop(&mut self) {
        drop_cnt.fetch_add(1, Ordering::Relaxed);
        println!("Dropping {}", self.0);
    }
}

fn main() {
    // Please check along with the code snippets above.
    {
        // `*ptr = value` acts by dropping existing contents at `ptr`.
        // `Matrix::fill_with()` uses this pattern which result in dropping
        // uninitialized, unallocated struct.
        //
        // Note that the creation of a mutable reference to uninitialized memory
        // region is already UB by itself.
        // `ptr::write` and `MaybeUninit` should be used for the initialization.
        let _ = Matrix::<DropDetector>::new(1, 1);
    }
    {
        // (Bonus) Integer overflow in `layout()` allows to create a huge matrix.
        // Fortunately, every access to the internal buffer are bound-checked,
        // so this doesn't lead to obvious UB by itself.
        let mat = Matrix::<usize>::new(15326306685794188004, 0x123456789);
        println!(
            "rows: {}, cols: {}, number of elements: {}",
            mat.rows(),
            mat.cols(),
            mat.elements_number()
        );
    }
    assert_eq!(
        creation_cnt.load(Ordering::Relaxed),
        drop_cnt.load(Ordering::Relaxed)
    );
}

Output:

Dropping 0
Dropping 12345
rows: 0, cols: 4886718345, number of elements: 4
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `2`', src/main.rs:72:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Return Code: 101