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

Some usage of Allocator::deallocate() is UB #233

Closed
inahga opened this issue Oct 22, 2024 · 5 comments
Closed

Some usage of Allocator::deallocate() is UB #233

inahga opened this issue Oct 22, 2024 · 5 comments

Comments

@inahga
Copy link
Contributor

inahga commented Oct 22, 2024

I believe any situation where we take a reference and directly deallocate it is undefined behavior.

Consider deflate::Window::drop_in:

    pub unsafe fn drop_in(&mut self, alloc: &Allocator) {
        if !self.buf.is_empty() {
            let buf = core::mem::take(&mut self.buf);
            alloc.deallocate(buf.as_mut_ptr(), buf.len());
        }
    }

We are correct to take(&mut self.buf), so that we don't leave a dangling reference inside the struct. But, we then assign the reference to buf, then deallocate it afterwards. After the deallocate() call, buf is now dangling. It is indeed dropped immediately after, but references must not be dangling while they're live.

We can trivially tweak this into:

            let (buf, len) = {
                let buf = core::mem::take(&mut self.buf);
                (buf.as_mut_ptr(), buf.len())
            };
            alloc.deallocate(buf, len);

Now buf will be dropped at the end of the let scope, so it won't have the chance to become dangling.

But, the allocator API lends itself to this UB, since it returns references. It may be better for the allocator to only issue pointers, and structs should store pointers and cast to short-lived references only when required (i.e. whenever you can guarantee that the pointer outlives the reference). AFAICT other allocating structures in the standard library, e.g. Vec, more or less follow this pattern.

This isn't caught by Miri. I'm not 100% sure why, but it seems to be a limitation of its memory model .

@inahga
Copy link
Contributor Author

inahga commented Oct 22, 2024

This isn't caught by Miri. I'm not 100% sure why, but it seems to be a limitation of its memory model .

I raised the question in Zulip https://rust-lang.zulipchat.com/#narrow/channel/122651-general/topic/Is.20this.20deallocation.20UB.3F/near/478350147. My conclusion is more strongly that the Allocator should only deal in pointers (that is, even my "fixed" example is questionable). I can play around with the code to see if this is reasonably feasible.

@folkertdev
Copy link
Collaborator

In general I'm on board with removing the deallocate functions and coming up with something better/safer 👍

@inahga
Copy link
Contributor Author

inahga commented Oct 23, 2024

I played around for a bit with pointers main...inahga:zlib-rs:inahga/dealloc-safe, and it looks quite tractable (though I don't guarantee what I wrote works 😄). Mostly mechanical changes. I do suggest simply having the allocator deal in raw pointers.

@inahga
Copy link
Contributor Author

inahga commented Nov 4, 2024

@folkertdev I believe you have resolved this? Looking at usage of deallocate() looks like we're only deallocating from the pointer from allocate() 🎉. Was there anything else you were going to do related to this issue?

@folkertdev
Copy link
Collaborator

no this is done, fixed by

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