Skip to content

fix: Avoid overflows when reading json inputs - #8676

Merged
whoisj merged 21 commits into
mainfrom
jwyman/tri-673
Apr 6, 2026
Merged

fix: Avoid overflows when reading json inputs#8676
whoisj merged 21 commits into
mainfrom
jwyman/tri-673

Conversation

@whoisj

@whoisj whoisj commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Check input from HTTP+JSON sizes before allocating memory for them.

Protect against integer overflows while we're at it.

@whoisj whoisj added the PR: fix A bug fix label Feb 24, 2026
@mudit-eng

Copy link
Copy Markdown
Contributor

LGTM

@mattwittwer

Copy link
Copy Markdown
Contributor

One edge case:
max_input_size_ is checked per-input rather than cumulatively, allowing up to a 4x memory limit bypass.
This PR checks byte_size > max_input_size_ to prevents excessive memory allocation. However, because this check is evaluated on individual inputs rather than the cumulative total of the entire HTTP request, a single request can still cause the server to allocate up to 4x the memory limit.

The scenario:
The upstream EVBufferToJson limits the physical size of the incoming HTTP JSON payload to max_input_size_.
However, the most compact JSON representation of an element is 0, (2 bytes), while an FP64 element allocates 8 bytes in memory.
With a limit set to --http-max-input-size=128000000 (128 MB), a single 128 MB JSON payload formatted like this can allocate 512MB:

{
  "parameters": {
    "in_1": {"datatype": "FP64", "data": [ ... 16 million zeros (32 MB JSON) ... ]},
    "in_2": {"datatype": "FP64", "data": [ ... 16 million zeros (32 MB JSON) ... ]},
    "in_3": {"datatype": "FP64", "data": [ ... 16 million zeros (32 MB JSON) ... ]},
    "in_4": {"datatype": "FP64", "data": [ ... 16 million zeros (32 MB JSON) ... ]}
  }
}

What happens:
The upstream check passes because the total HTTP payload is exactly 128 MB.
ExactMappingInput parses in_1. The calculated byte_size is 128 MB. The check if (byte_size > max_input_size_) passes. Triton allocates 128 MB. This repeats for in_2, in_3, and in_4. Resulting in the server allocating 512 MB of memory for a single request, bypassing the 128 MB max_input_size_ limit.

@yinggeh yinggeh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Though I think it's a bit of over-engineering here.

@whoisj

whoisj commented Feb 27, 2026

Copy link
Copy Markdown
Contributor Author

max_input_size_ is checked per-input rather than cumulatively, allowing up to a 4x memory limit bypass.

Agreed about exceeding the overall limit. I'll create a follow up to address that separately. Thanks for pointing it out.

Comment thread src/http_server.cc
@whoisj
whoisj requested a review from yinggeh March 2, 2026 22:09
@whoisj
whoisj force-pushed the jwyman/tri-673 branch 2 times, most recently from c603e3d to 75c3c95 Compare March 3, 2026 00:09
Comment thread src/http_server.cc
Comment thread src/http_server.cc
@yinggeh

yinggeh commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

PR title isn't following the convention.

@yinggeh

yinggeh commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

I think test is required for such code change. Whether verify its functionality with existing ones or by creating a new test.

@whoisj
whoisj requested a review from yinggeh March 4, 2026 20:03
Comment thread qa/common/gen.ONNXRuntime.gen_qa_model_repository.docker.v2.sh Outdated
@whoisj whoisj changed the title Avoid overflows when reading json inputs fix: Avoid overflows when reading json inputs Mar 5, 2026
@whoisj

whoisj commented Mar 6, 2026

Copy link
Copy Markdown
Contributor Author

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.

@whoisj
whoisj requested a review from yinggeh March 6, 2026 01:48
)
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."}',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to see tests that passing with size 67108864 but failed with 67108864+1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the format.

Comment thread qa/L0_http/test.sh Outdated
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused with the naming here. Can you use a more descriptive name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you have a suggestion?

@whoisj
whoisj force-pushed the jwyman/tri-673 branch 4 times, most recently from 3f045a5 to 52f656f Compare March 9, 2026 15:49
Comment thread qa/L0_http/generate_endpoint_test.py Fixed
@whoisj
whoisj force-pushed the jwyman/tri-673 branch 2 times, most recently from db78671 to a67598d Compare March 11, 2026 20:39
@whoisj
whoisj marked this pull request as ready for review March 23, 2026 16:08
@whoisj
whoisj requested a review from yinggeh March 23, 2026 16:09
@whoisj
whoisj force-pushed the jwyman/tri-673 branch 2 times, most recently from d864e63 to fcaabcf Compare March 23, 2026 20:08
@whoisj
whoisj requested a review from mudit-eng March 23, 2026 21:43
Comment thread src/http_server.cc Outdated
std::vector<char>& serialized = infer_req->serialized_data_.back();
serialized.resize(byte_size);

char* serialized_base = &serialized[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&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();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. I was just using the existing code, but I'll fix up regardless.

Comment thread src/http_server.h
const MappingSchema* request_schema_{nullptr};
const MappingSchema* response_schema_{nullptr};
const bool streaming_{false};
const size_t max_input_size_{0};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTPAPIServer already has protected member:size_t max_input_size_;. Can this create confusion?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how, they're both private fields.

Comment thread src/http_server.cc Outdated
" 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. "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this message is intuitive. This does not convey what is the maximum input size.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the updated format was a request from @yinggeh. Can you two decide between you which you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Thanks for the clarity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed up.

Comment thread src/http_server.cc
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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use unordered_map here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/http_server.h Outdated
const MappingSchema* RequestSchema() { return request_schema_; }
const MappingSchema* ResponseSchema() { return response_schema_; }

size_t MaxInputSize() { return max_input_size_; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not being called anywhere

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove.

Comment thread src/http_server.cc Outdated
// 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_;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can byte_size + consumed_input_byte_size result in an overflow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no because of the check immediately previous.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed up.

@whoisj
whoisj requested a review from mudit-eng March 25, 2026 16:45
update tests to match message changes
@whoisj

whoisj commented Apr 3, 2026

Copy link
Copy Markdown
Contributor Author

@mudit-eng updated the error message per your comment. Updated tests to match.

CI-Pipeline-ID: 47658956

Comment thread qa/L0_http/generate_endpoint_test.py Outdated
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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_type_size_explosion(self):
def test_json_dtype_size_expansion_exceeds_limit_error(self):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment thread qa/L0_http/generate_endpoint_test.py Outdated
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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a description comment to explain what's happening here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@whoisj
whoisj merged commit e5aecb3 into main Apr 6, 2026
3 checks passed
@whoisj
whoisj deleted the jwyman/tri-673 branch April 6, 2026 22:20
@mudit-eng

Copy link
Copy Markdown
Contributor

Thanks for addressing all the feedbacks. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: fix A bug fix

Development

Successfully merging this pull request may close these issues.

5 participants