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

Use after Free when parsing this XML Document #47

Closed
CryZe opened this issue Jun 28, 2017 · 4 comments
Closed

Use after Free when parsing this XML Document #47

CryZe opened this issue Jun 28, 2017 · 4 comments

Comments

@CryZe
Copy link
Contributor

CryZe commented Jun 28, 2017

Found by cargo-fuzz:

crash-52cdb28f04f0c80d84609394d18ed2c0b8fedb7f.zip

Caused at:

...
<sxd_document::string_pool::InternedString as core::cmp::PartialEq>::eq
...
std::collections::hash::map::search_hashed
...
sxd_document::string_pool::StringPool::intern
sxd_document::raw::Storage::intern
sxd_document::raw::Storage::create_attribute
sxd_document::dom::Element::set_attribute_value
sxd_document::parser::DomBuilder::finish_opening_tag
sxd_document::parser::DomBuilder::consume
sxd_document::parser::parse

Freed at:

...
alloc::raw_vec::RawVec<...>::dealloc_buffer
RawVec<...>::drop
core::ptr::drop_in_place
core::ptr::drop_in_place
core::ptr::drop_in_place
core::ptr::drop_in_place
sxd_document::parser::DomBuilder::finish_opening_tag
sxd_document::parser::DomBuilder::consume
sxd_document::parser::parse

Allocated at:

...
<alloc::vec::Vec<T>>::extend_from_slice
alloc::string::String::push_str
sxd_document::parser::AttributeValueBuilder::ingest
sxd_document::parser::DomBuilder::finish_opening_tag
sxd_document::parser::DomBuilder::consume
sxd_document::parser::parse
@CryZe
Copy link
Contributor Author

CryZe commented Jun 28, 2017

Looks like the String Pool is super unsafe.

    pub fn intern<'s>(&'s self, s: &str) -> &'s str {
        if s == "" { return ""; }

        let search_string = InternedString::from_str(s);

        let mut index = self.index.borrow_mut();
        let interned_str = *index.entry(search_string).or_insert_with(|| self.do_intern(s));

        // The lifetime is really matched to us
        unsafe { mem::transmute(interned_str) }
    }

It takes a random str slice and inserts it into the pool as the key. However there's no guarantee that the key will still be alive at a later point in time, as the key itself is not interned. So later when using the HashMap, it'll compare the keys and so it'll compare to memory that isn't allocated anymore.

CryZe added a commit to CryZe/sxd-document that referenced this issue Jun 29, 2017
The String Pool now uses just a HashSet, that stores the actual Interned
Strings. The old code also stored the str slices that we where looking
for as keys, but there were never interned properly, so they were super
likely to get freed at some point and cause a Use after Free.

Fixes shepmaster#47
@shepmaster
Copy link
Owner

Thanks for the report! However, I don't agree with your assessment so I'd like your help to understand what I'm missing...

It takes a random str slice and inserts it into the pool as the key

This isn't quite true... it does take a string slice and converts it to a InternedString:

let search_string = InternedString::from_str(s);

This is only used to look up in the HashMap. If the value is found, we return the value from the pool. If it is not found, we add it to the pool and return that value. In both cases, the return value is one of the pooled values

let interned_str = *index.entry(search_string).or_insert_with(|| self.do_intern(s));

At no point is the input string returned. In fact, there's a test to make sure that specific case doesn't happen (does_not_reuse_the_pointer_of_the_input).

@CryZe
Copy link
Contributor Author

CryZe commented Jul 1, 2017

It's not returned, but it's stored as the key of the HashMap through the Entry API, where it later gets compared when looking up more values. The or_insert_with closure only provides the value of the HashMap, it takes the key from the entry parameter.

@shepmaster
Copy link
Owner

shepmaster commented Jul 1, 2017

stored as the key of the HashMap

Ah, that makes sense! I always wanted to switch to a HashSet, but couldn't originally because the feature to get the value stored in the HashSet wasn't present. It's a good thing that they finally added it, although I guess Rust 1.9.0 was a while ago now!

CryZe added a commit to CryZe/sxd-document that referenced this issue Jul 1, 2017
The String Pool now uses just a HashSet, that stores the actual Interned
Strings. The old code also stored the str slices that we where looking
for as keys, but there were never interned properly, so they were super
likely to get freed at some point and cause a Use after Free.

Fixes shepmaster#47
CryZe added a commit to CryZe/sxd-document that referenced this issue Jul 1, 2017
The String Pool now uses just a HashSet, that stores the actual Interned
Strings. The old code also stored the str slices that we where looking
for as keys, but there were never interned properly, so they were super
likely to get freed at some point and cause a Use after Free.

Fixes shepmaster#47
CryZe added a commit to CryZe/sxd-document that referenced this issue Jul 1, 2017
The String Pool now uses just a HashSet, that stores the actual Interned
Strings. The old code also stored the str slices that we where looking
for as keys, but there were never interned properly, so they were super
likely to get freed at some point and cause a Use after Free.

Fixes shepmaster#47
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