fix: Avoid overflows when reading json inputs - #8676
Conversation
|
LGTM |
|
One edge case: The scenario: What happens: |
yinggeh
left a comment
There was a problem hiding this comment.
LGTM. Though I think it's a bit of over-engineering here.
Agreed about exceeding the overall limit. I'll create a follow up to address that separately. Thanks for pointing it out. |
c603e3d to
75c3c95
Compare
|
PR title isn't following the convention. |
|
I think test is required for such code change. Whether verify its functionality with existing ones or by creating a new test. |
Added 2 new tests. Let me know if they look good to you. |
| ) | ||
| error_msg = response.content.decode() | ||
| self.assertEqual( | ||
| '{"error":"Request JSON size of 89478576 bytes exceeds the maximum allowed value of 67108864 bytes. Use --http-max-input-size to increase the limit."}', |
There was a problem hiding this comment.
Would like to see tests that passing with size 67108864 but failed with 67108864+1.
| fi | ||
|
|
||
| # Run test to verify that large inputs fail with default limit | ||
| python http_input_size_limit_test.py InferSizeLimitTest.test_type_size_explosion >> $CLIENT_LOG 2>&1 |
There was a problem hiding this comment.
I am confused with the naming here. Can you use a more descriptive name?
There was a problem hiding this comment.
do you have a suggestion?
3f045a5 to
52f656f
Compare
db78671 to
a67598d
Compare
d864e63 to
fcaabcf
Compare
| std::vector<char>& serialized = infer_req->serialized_data_.back(); | ||
| serialized.resize(byte_size); | ||
|
|
||
| char* serialized_base = &serialized[0]; |
There was a problem hiding this comment.
&serialized[0] is undefined behavior if vector is empty. Safer to do something like:
char* serialized_base = serialized.data();
or
char* serialized_base = serialized.empty() ? nullptr : serialized.data();
There was a problem hiding this comment.
sure. I was just using the existing code, but I'll fix up regardless.
| const MappingSchema* request_schema_{nullptr}; | ||
| const MappingSchema* response_schema_{nullptr}; | ||
| const bool streaming_{false}; | ||
| const size_t max_input_size_{0}; |
There was a problem hiding this comment.
HTTPAPIServer already has protected member:size_t max_input_size_;. Can this create confusion?
There was a problem hiding this comment.
I don't see how, they're both private fields.
| " bytes. Use --http-max-input-size to increase the limit.") | ||
| ("Request JSON size of " + std::to_string(length) + " + " + | ||
| std::to_string(overrun) + | ||
| " bytes exceeds the maximum allowed input size. " |
There was a problem hiding this comment.
Not sure if this message is intuitive. This does not convey what is the maximum input size.
There was a problem hiding this comment.
the updated format was a request from @yinggeh. Can you two decide between you which you prefer?
There was a problem hiding this comment.
Difference would be:
New log: Request JSON size of 1044 + 20 bytes exceeds the maximum allowed input size.
Old log: Request JSON size of 1044 bytes exceeds the maximum allowed value of 1024.
I talked to Yingge. May be there is a misunderstanding. We can keep the old message.
Plus, there is a bug in the new code. We should have printed max_input_size_ + overrun instead of 'length + overrun`. Can we validate the numbers too in the test?
There was a problem hiding this comment.
Updated. Thanks for the clarity.
| const std::string& name, | ||
| triton::common::TritonJson::Value& generate_request, | ||
| std::map<std::string, triton::common::TritonJson::Value>& input_metadata) | ||
| std::map<std::string, triton::common::TritonJson::Value>& input_metadata, |
There was a problem hiding this comment.
Can we use unordered_map here?
There was a problem hiding this comment.
no idea, but that would be changing existing code and I'd rather not because I feel that is outside the scope of this PR.
| const MappingSchema* RequestSchema() { return request_schema_; } | ||
| const MappingSchema* ResponseSchema() { return response_schema_; } | ||
|
|
||
| size_t MaxInputSize() { return max_input_size_; } |
There was a problem hiding this comment.
this is not being called anywhere
| // allowed input size. | ||
| if (byte_size + consumed_input_byte_size > max_input_size_ || | ||
| byte_size + consumed_input_byte_size < consumed_input_byte_size) { | ||
| auto overrun = byte_size + consumed_input_byte_size - max_input_size_; |
There was a problem hiding this comment.
Can byte_size + consumed_input_byte_size result in an overflow?
There was a problem hiding this comment.
no because of the check immediately previous.
There was a problem hiding this comment.
Yes, you checked for overflow here: byte_size + consumed_input_byte_size < consumed_input_byte_size) but can't there be an overflow here: auto overrun = byte_size + consumed_input_byte_size - max_input_size_
There was a problem hiding this comment.
while true, this value is only to produce an error message.
not sure what we would do with an overrun of a size_t value.
update tests to match message changes
|
@mudit-eng updated the error message per your comment. Updated tests to match. CI-Pipeline-ID: 47658956 |
| self.generate_expect_failure(self._model_name, inputs, error_msg) | ||
| self.generate_stream_expect_failure(self._model_name, inputs, error_msg) | ||
|
|
||
| def test_type_size_explosion(self): |
There was a problem hiding this comment.
| def test_type_size_explosion(self): | |
| def test_json_dtype_size_expansion_exceeds_limit_error(self): |
| self.generate_expect_failure(self._model_name, inputs, error_msg) | ||
| self.generate_stream_expect_failure(self._model_name, inputs, error_msg) | ||
|
|
||
| def test_type_size_explosion(self): |
There was a problem hiding this comment.
Please add a description comment to explain what's happening here.
|
Thanks for addressing all the feedbacks. :) |
Check input from HTTP+JSON sizes before allocating memory for them.
Protect against integer overflows while we're at it.