-
Notifications
You must be signed in to change notification settings - Fork 52
fix soundness problems in String and StringBuilder #218
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
base: main
Are you sure you want to change the base?
Conversation
@MarcusGrass in case you want to check it out. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice solution to start by writing 0. Also good doc-comment add, tricky that you can't send, but can get nulls there!
E: would have made an approving commit but slipped up on the phone
pub fn finish(mut self) -> String { | ||
// when constructing the final string, the pointer it contains must always be non null and | ||
// terminated with a null byte | ||
if self.inner.data.is_null() { | ||
// ensure we are in a valid state | ||
assert!(self.inner.is_empty()); | ||
assert_eq!(self.cap, 0); | ||
|
||
// The pointer of `String` should never be null, and it must be | ||
// terminated by a null byte. | ||
if s.data.is_null() { | ||
unsafe { | ||
let ptr = libc::malloc(1) as *mut i8; | ||
if ptr.is_null() { | ||
unable_to_alloc_memory(); | ||
} | ||
ptr.write(0); | ||
|
||
s.data = ptr; | ||
} | ||
} | ||
// Create a null terminated empty string | ||
self.inner = String::from_bytes(&[]); | ||
} else { | ||
debug_assert!(self.cap > self.inner.len()); | ||
assert!(self.cap > self.inner.len()); | ||
} | ||
|
||
assert_eq!( | ||
unsafe { *self.inner.data.add(self.inner.len()) }, | ||
0, | ||
"StringBuilder should always return a null terminated string" | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are all just sanity checks, right? If so, debug_assert
ions should be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assertions in the null check is just a sanity check so a debug_assert
is fine there and can change it if its preferred.
The other assertions involve soundness so the assertion failing can result in UB.
For example assert!(self.cap > self.inner.len());
failing can result in UB as we might be passing bytes that are not initialized.
As for the other assertion, not being null terminated can cause UB depending on how the string is cloned inside neovim.
There were a few methods where a null pointer was used or the string was not null terminated.
This fixes possible undefined behaviors. To avoid these issues in the future a few assertions were added into
StringBuilder::finish
to ensure that aString
is never initialized with a null pointer and is always terminated with a null byte.