diff --git a/qa/L0_infer/infer_test.py b/qa/L0_infer/infer_test.py index a195092c1b..d5d80294c4 100755 --- a/qa/L0_infer/infer_test.py +++ b/qa/L0_infer/infer_test.py @@ -35,6 +35,7 @@ import infer_util as iu import numpy as np +import requests import test_util as tu from tritonclient.utils import * @@ -314,6 +315,60 @@ def test_raw_uuu(self): np.uint8, np.uint8, np.uint8, output0_raw=True, output1_raw=True, swap=True ) + @unittest.skipIf(not USE_HTTP, "HTTP testing is disabled") + @unittest.skipIf("onnx" not in BACKENDS, "ONNX backend is disabled") + def test_http_json_tensor_data_out_of_range(self): + if BATCH: + model_prefix = "onnx" + shape = [1, 16] + elif NOBATCH: + model_prefix = "onnx_nobatch" + shape = [16] + else: + self.skipTest("No ONNX model is enabled") + + test_cases = [ + (np.uint8, "UINT8", 256, "UINT8 data value 256 is out of range"), + (np.int8, "INT8", 128, "INT8 data value 128 is out of range"), + ] + + for input_dtype, datatype, invalid_value, expected_error in test_cases: + with self.subTest(datatype=datatype): + model_name = tu.get_model_name( + model_prefix, input_dtype, input_dtype, input_dtype + ) + tensor_data = [invalid_value] + ([0] * 15) + payload = { + "inputs": [ + { + "name": "INPUT0", + "shape": shape, + "datatype": datatype, + "data": tensor_data, + }, + { + "name": "INPUT1", + "shape": shape, + "datatype": datatype, + "data": [0] * 16, + }, + ], + "outputs": [{"name": "OUTPUT0"}, {"name": "OUTPUT1"}], + } + + infer_url = ( + f"http://{os.environ.get('TRITONSERVER_IPADDR', 'localhost')}:8000" + f"/v2/models/{model_name}/infer" + ) + response = requests.post( + infer_url, + json=payload, + timeout=NETWORK_TIMEOUT, + ) + + self.assertEqual(response.status_code, 400, response.text) + self.assertIn(expected_error, response.json()["error"]) + def test_raw_bbb(self): self._full_exact( np.int8, np.int8, np.int8, output0_raw=True, output1_raw=True, swap=True diff --git a/src/http_server.cc b/src/http_server.cc index b8d9ea00da..75a08d764a 100644 --- a/src/http_server.cc +++ b/src/http_server.cc @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -476,6 +477,37 @@ AllocEVBuffer(const size_t byte_size, evbuffer** evb, void** base) return nullptr; // success } +template +TRITONSERVER_Error* +ValidateJsonUIntRange(const uint64_t value, const char* datatype) +{ + if (value > static_cast(std::numeric_limits::max())) { + return TRITONSERVER_ErrorNew( + TRITONSERVER_ERROR_INVALID_ARG, + (std::string(datatype) + " data value " + std::to_string(value) + + " is out of range") + .c_str()); + } + + return nullptr; +} + +template +TRITONSERVER_Error* +ValidateJsonIntRange(const int64_t value, const char* datatype) +{ + if ((value < static_cast(std::numeric_limits::min())) || + (value > static_cast(std::numeric_limits::max()))) { + return TRITONSERVER_ErrorNew( + TRITONSERVER_ERROR_INVALID_ARG, + (std::string(datatype) + " data value " + std::to_string(value) + + " is out of range") + .c_str()); + } + + return nullptr; +} + // Recursively adds to byte_size from multi dimensional data input TRITONSERVER_Error* JsonBytesArrayByteSize( @@ -563,34 +595,34 @@ ReadDataFromJsonHelper( bool b = false; RETURN_IF_ERR(tensor_data.AsBool(&b)); uint8_t* data_vec = reinterpret_cast(base); - // FIXME for unsigned should bounds check and raise error - // since otherwise the actually used value will be - // unexpected. - data_vec[*counter] = (uint8_t)(b ? 1 : 0); + data_vec[*counter] = static_cast(b ? 1 : 0); *counter += 1; break; } case TRITONSERVER_TYPE_UINT8: { uint64_t ui = 0; RETURN_IF_ERR(tensor_data.AsUInt(&ui)); + RETURN_IF_ERR(ValidateJsonUIntRange(ui, "UINT8")); uint8_t* data_vec = reinterpret_cast(base); - data_vec[*counter] = (uint8_t)ui; + data_vec[*counter] = static_cast(ui); *counter += 1; break; } case TRITONSERVER_TYPE_UINT16: { uint64_t ui = 0; RETURN_IF_ERR(tensor_data.AsUInt(&ui)); + RETURN_IF_ERR(ValidateJsonUIntRange(ui, "UINT16")); uint16_t* data_vec = reinterpret_cast(base); - data_vec[*counter] = (uint16_t)ui; + data_vec[*counter] = static_cast(ui); *counter += 1; break; } case TRITONSERVER_TYPE_UINT32: { uint64_t ui = 0; RETURN_IF_ERR(tensor_data.AsUInt(&ui)); + RETURN_IF_ERR(ValidateJsonUIntRange(ui, "UINT32")); uint32_t* data_vec = reinterpret_cast(base); - data_vec[*counter] = (uint32_t)ui; + data_vec[*counter] = static_cast(ui); *counter += 1; break; } @@ -603,29 +635,29 @@ ReadDataFromJsonHelper( break; } case TRITONSERVER_TYPE_INT8: { - // FIXME for signed type just assigning to smaller type is - // "implementation defined" and so really need to bounds - // check. int64_t si = 0; RETURN_IF_ERR(tensor_data.AsInt(&si)); + RETURN_IF_ERR(ValidateJsonIntRange(si, "INT8")); int8_t* data_vec = reinterpret_cast(base); - data_vec[*counter] = (int8_t)si; + data_vec[*counter] = static_cast(si); *counter += 1; break; } case TRITONSERVER_TYPE_INT16: { int64_t si = 0; RETURN_IF_ERR(tensor_data.AsInt(&si)); + RETURN_IF_ERR(ValidateJsonIntRange(si, "INT16")); int16_t* data_vec = reinterpret_cast(base); - data_vec[*counter] = (int16_t)si; + data_vec[*counter] = static_cast(si); *counter += 1; break; } case TRITONSERVER_TYPE_INT32: { int64_t si = 0; RETURN_IF_ERR(tensor_data.AsInt(&si)); + RETURN_IF_ERR(ValidateJsonIntRange(si, "INT32")); int32_t* data_vec = reinterpret_cast(base); - data_vec[*counter] = (int32_t)si; + data_vec[*counter] = static_cast(si); *counter += 1; break; }