Currently, if we want to decode a variable-length array of variable-length items (e.g. a list of transactions), we use a function Reader::read_vec_with that gets the minimum item length and allocates a vector only if the byte buffer has the relevant amount of bytes. This prevents a denial-of-service (DoS) attack where a malformed excessive length prefix forces the user to allocate too much memory. Current design is not optimal as it's pretty hard to read and understand.
Do not pre-allocate the vector at all. Let the system allocator do the growing/reallocation of the vector as we read. This is robust and simple, but not always efficient.
Preallocate min(length_prefix, 100), that is choose a static upper bound one-size-fits-all. This mostly mitigates DoS risk (unless the low constant is used with a very large item type), but still could be inefficient.
Move the length estimate to the length-prefix reader. So when we get the length, we also know it's not impossible to read that many items from the given buffer: Reader::read_length_u32(&mut self, min_item_size: usize)?. This will take the minimum item size and check whether there's enough data left to fit the declared number of items. If it does not - it will fail with an InvalidFormat error. But this requires user not to use "read the vector" without using this particular API. We can use some custom type wrapper for "CheckedUsize", but that's clunky.
Combine length prefix check with vector read. Similar to above, but we are going to read the length prefix and the vec in one go. We'll need two flavors of the function, though: u32 and u64.
Currently, if we want to decode a variable-length array of variable-length items (e.g. a list of transactions), we use a function
Reader::read_vec_with
that gets the minimum item length and allocates a vector only if the byte buffer has the relevant amount of bytes. This prevents a denial-of-service (DoS) attack where a malformed excessive length prefix forces the user to allocate too much memory. Current design is not optimal as it's pretty hard to read and understand.See: https://github.com/stellar/slingshot/blob/9891a7df16546baba6d89a562fb894bb60d38fa9/zkvm/src/contract.rs#L100
Alternatives:
Reader::read_length_u32(&mut self, min_item_size: usize)?
. This will take the minimum item size and check whether there's enough data left to fit the declared number of items. If it does not - it will fail with an InvalidFormat error. But this requires user not to use "read the vector" without using this particular API. We can use some custom type wrapper for "CheckedUsize", but that's clunky.