-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
jsonbuilder: attempt to handle memory allocation errors - v2 #8855
Conversation
Some very minor changes to formatting.
Convert "new_object" and "new_array" functions that return a Result and use "try_reserve" to allocate the amount of data requested. This should allow memory allocation errors to be detected and handled in a Rust-ful matter without resorting to catching a panic. Ticket: OISF#6057
Provide a wrapper around "push" and "push_str" on the internal buffer that will "try_reserve" data before growing in an attempt to handle memory allocation errors. Ticket: OISF#6057
Convert encode_string() to use try_reserve to catch memory allocation errors.
As base64 encoding is done by a 3rd party library directly into the buffer and there is no "try" style function, first reserve enough data to fit the complete base64 encoded string (and then some).
Required minor updates to users of the base64 crate.
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## master #8855 +/- ##
==========================================
+ Coverage 82.25% 82.26% +0.01%
==========================================
Files 969 969
Lines 273176 273207 +31
==========================================
+ Hits 224689 224752 +63
+ Misses 48487 48455 -32
Flags with carried forward coverage won't be shown. Click here to find out more. |
WARNING:
Pipeline 13706 |
Have you tried running jsonbuilder unit tests with an allocation failure framework ? (ie a LD_PRELOAD overload malloc to fail, running the tests X times, X being the number of allocations, and making each allocation fail) |
No. Any existing examples of this? |
Like https://github.com/tavianator/oomify but there may be others (and it is quite easy to recode) |
Jason, I had created this https://redmine.openinfosecfoundation.org/issues/5851 for your info |
Will look. I did test in a low memory Docker container, unfortunately the Linux OOM kicks in before Rust is able to detect an allocation error. So while this may test if the code works OK, I wonder if it would actually ever error out in production on Linux, or just get killed by the kernel anyways. |
Quick test with
The panic there is just a result on my unwrap, so that shows that if Rust isn't killed by the kernel, the error is rippled back to the application. |
Where can I see test-jsonbuilder.rs source ? |
Its just in my working directory, not something suitable to check in, so no PR...
|
@@ -635,18 +635,26 @@ impl JsonBuilder { | |||
/// The string is encoded into an intermediate vector as its faster | |||
/// than building onto the buffer. | |||
/// | |||
/// TODO: Allocate memory in a fallible way. | |||
/// TODO: Revisit this, would be nice to build directly onto the |
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.
Is it to do in this PR ?
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.
In a way, that one todo had to be removed, and another way came to mind at the same time.
fn encode_base64(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> { | ||
// Before base64 encoding, make sure enough space is reserved. | ||
if self.buf.capacity() < self.buf.len() + val.len() * 2 { | ||
self.buf.try_reserve(val.len() * 2)?; |
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.
base64 is rather ((val.len()+2)/3)*4 no ?
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.
like one byte gets encoded as 4 bytes
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.
Ah yeah, mines too small. base64
has a function for this, will just use that.
Do not you need to check also |
And make |
|
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.
Some nits to address ;-)
I wonder if some cases should do a big reserve before multiple push_str like open_array
doing
self.push('"')?;
self.push_str(key)?;
self.push_str("\":[")?;
Maybe. But as the reserve behind these does 4k size, the first that needs it will allocate enough. My first iteration did try to estimate the size and reserve at once, but it did seem a little error prone. But not harm to estimate it, reserve once so the reserve shouldn't be triggered again during the call into jsonbuilder. Not sure if it'll have a noticeable affect other than fail a little earlier if it was going to fail. |
Commented addressed (well most of them) here: #8901 |
Previous PR: #8847
Ticket: https://redmine.openinfosecfoundation.org/issues/6057
Attempt to handle all "normal" memory allocation errors in JsonBuilder where
the Rust APIs allow us. This primarily means growing the underlying String
buffer as data is added.
As the JsonBuilder already used a Result for most methods, most users of
JsonBuilder already handle errors.
Changes from last PR:
Things to investigate:
experimental API that will allow us to handle this better.