Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key struct is fragile, possibly unsound #196

Open
Shnatsel opened this issue Feb 2, 2021 · 1 comment
Open

Key struct is fragile, possibly unsound #196

Shnatsel opened this issue Feb 2, 2021 · 1 comment

Comments

@Shnatsel
Copy link

Shnatsel commented Feb 2, 2021

The Key struct is currently initialized in two phases. First it is created, like this:

json-rust/src/object.rs

Lines 71 to 78 in 0775592

fn new(hash: u64, len: usize) -> Self {
Key {
buf: [0; KEY_BUF_LEN],
len: len,
ptr: ptr::null_mut(),
hash: hash
}
}

And only later it is attached to its actual contents.

Before the key is attached, calling as_bytes results in undefined behavior:

json-rust/src/object.rs

Lines 81 to 85 in 0775592

fn as_bytes(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self.ptr, self.len)
}
}

because a null pointer is passed to slice::from_raw_parts, violating its safety invariant. Replacing a null pointer with NonNull::dangling() will not solve the problem because then the slice will non-zero length would to unclaimed memory, which is also UB.

If the two-phase initialization is truly necessary, a less fragile way to approach it is to explicitly encode the initialization state in the type system: have UnattachedKey struct without any accessor methods for the partly initialized state, and provide a method to turn it into a proper Key by calling attach().


Key also seems to contain a hand-rolled implementation of the small string optimization that, in addition to the usual dangers of custom unsafe code, leaves performance on the table. The current implementation always zero-initializes the underlying buffer. Using smallstr crate or something along those lines will (even though I'm not really a fan of the underlying smallvec crate).

Using slotmap to store the strings in a single allocation instead of using the small string optimization would probably work even better, since it would avoid inflating the struct, and also remove the need for pointer fixups on cloning and reallocation altogether. It would also remove the need for two-phase initialization, and Key would no longer have to be self-referential.

@brunocodutra
Copy link

Miri doesn't like it either

error: Undefined Behavior: trying to retag from <3928> for SharedReadOnly permission at alloc1799[0x18], but that tag does not exist in the borrow stack for this location
   --> /home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/raw.rs:97:9
    |
97  |         &*ptr::slice_from_raw_parts(data, len)
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |         |
    |         trying to retag from <3928> for SharedReadOnly permission at alloc1799[0x18], but that tag does not exist in the borrow stack for this location
    |         this error occurs as part of retag at alloc1799[0x18..0x1c]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information

brunocodutra added a commit to brunocodutra/tree-edit-distance that referenced this issue Sep 11, 2022
brunocodutra added a commit to brunocodutra/tree-edit-distance that referenced this issue Sep 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants