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

fix(ffi): unsound FreeContextBuffer #141

Merged
merged 2 commits into from
Aug 10, 2023
Merged

Conversation

CBenoit
Copy link
Member

@CBenoit CBenoit commented Aug 10, 2023

The FreeContextBuffer function is using Box::<T>::from_raw to free
memory from a raw pointer of type *mut c_void. Sadly, this is unsound.

Indeed, knowledge of the original type is required when deallocating memory in
Rust. At the bare minimum, knowledge of the original type’s Layout is required
since this information is required by the default global allocator (actually
it is a safety invariant of any global allocator in Rust).
The correct layout for a given type can easily be retrieved by using
Layout::new::<T> or Layout::for_value::<T>, but this requires knowledge of
the concrete type T. That’s why Box::<T>::from_raw safety invariant is defined
as follows:

For non-zero-sized values, a Box will use the Global allocator for its
allocation. It is valid to convert both ways between a Box and a raw pointer
allocated with the Global allocator, given that the Layout used with the
allocator is correct for the type. More precisely, a value: *mut T that has
been allocated with the Global allocator with Layout::for_value(&*value)
may be converted into a box using Box::<T>::from_raw(value). Conversely,
the memory backing a value: *mut T obtained from Box::<T>::into_raw may be
deallocated using the Global allocator with Layout::for_value(&*value).

References:

This invariant is violated because c_void definitely does not have the same
layout as structs such as SecPkgInfoW or SecurityBuffer.

We could allocate a global table protected by a Mutex. This table would
associate each address pointing to a block we allocated with the layout or some
hint for the original type, and using that information we could cast back into
the original type when freeing the memory. However, I finally settled on using
libc allocator:

1/ libc allocator is doing its own bookkeeping by reserving a metadata chunk in
front of the actual "user" data chunk so that it can retrieve the length of the
allocated block later when free is called. By using libc allocator, we don’t
have to re-invent our own bookkeeping logic.

2/ Currently, some structures are tightly packed in memory by allocating a
bigger chunk in order to hold both the "top level" struct as well as
other data that fields from the said top level struct are pointing to.
The sec_pkg_info.rs file contains several occurrence
of this pattern. A Layout is manually constructed: its size is set to the
sum of the sizes of all the elements packed together and the aligment is
assumed to be the same as the top level struct (hardcoded to 8). There is
no comment explaining why this "crime" is safe, but this doesn’t sound quite
right to me. This also breaks the invariant for Box::<T>::from_raw even if
we know the type of the original ("top level") type, because we didn’t use the
correct layout for a T when allocating the memory in the first place.

That’s why, as of now, I think that using libc allocator is best.

I was able to inject the patched dll into FreeRDP and connect to my Devolutions Lab
without getting any crash, so it doesn’t appear to break anything so far.
image
(Note: username and password on this screenshot are not sensitive
since this is a local Devolutions Lab)

It’s likely that we still need to audit the unsafe code more in depth.

cc @RRRadicalEdward
I’m not very familiar with sspi-rs codebase, so if you spot anything wrong let me know. Thank you!

@CBenoit CBenoit enabled auto-merge (squash) August 10, 2023 23:25
@CBenoit CBenoit merged commit c339695 into master Aug 10, 2023
40 checks passed
@CBenoit CBenoit deleted the free-context-buffer-soundness branch August 10, 2023 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants