From e65ff910acc36dc5443254b561ab6f1ceab8526b Mon Sep 17 00:00:00 2001 From: OrbisAI Security Date: Fri, 12 Jun 2026 19:21:18 +0530 Subject: [PATCH 1/2] tools: plugin: ov_noise_suppression: validate model input shape dimensions noise_suppression_load_model() and noise_suppression_first_iter() take the input tensor shape directly from the OpenVINO model file via nd->model->input(...).get_shape() and feed the resulting dimensions into downstream size computations and allocations without any range check. A crafted or corrupted .xml/.bin model can specify dimensions that are either zero (producing zero-sized allocations later written into) or large enough that a subsequent multiplication overflows before being used as a buffer length. Both lead to out-of-bounds memory access at inference time. The same pattern exists for the per-iteration input state shape, which is also read from the model. After fetching each shape, walk its dimensions and reject the model with -EINVAL if any dimension is zero or exceeds 1 << 24 (16 Mi elements). The upper bound is well above any realistic noise suppression tensor dimension and well below the point at which a product of two such dimensions can overflow a 32-bit size on the platforms this plugin targets, which closes the integer-overflow path without rejecting valid models. No functional change for well-formed models; malformed models now fail the load instead of corrupting memory. Reported-by: OrbisAI Security scanner (V-004, CWE-190) Signed-off-by: OrbisAI Security --- .../ov_noise_suppression/noise_suppression_interface.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp index fa74736d062f..a763f72b2bba 100644 --- a/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp +++ b/tools/plugin/modules/ov_noise_suppression/noise_suppression_interface.cpp @@ -85,6 +85,9 @@ extern "C" { nd->infer_request[i] = compiled_model.create_infer_request(); nd->inp_shape = nd->model->input("input").get_shape(); + for (auto dim : nd->inp_shape) + if (!dim || dim > (1u << 24)) + return -EINVAL; return 0; } @@ -141,6 +144,9 @@ extern "C" { ov::Shape state_shape; state_shape = nd->model->input(inp_state_name).get_shape(); + for (auto dim : state_shape) + if (!dim || dim > (1u << 24)) + return -EINVAL; if (nd->iter > 0) { /* * set input state by corresponding output state from prev From 3effb1e1dca06e5d74fdf26a38559950809fe4da Mon Sep 17 00:00:00 2001 From: OrbisAI Security Date: Fri, 12 Jun 2026 20:09:11 +0530 Subject: [PATCH 2/2] tools: plugin: ov_noise_suppression: add regression test for adversarial model shapes Add a regression test that exercises the input-shape guard introduced in "tools: plugin: ov_noise_suppression: validate model input shape dimensions". The test is a small C++ executable living next to the module sources at tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp and wired into the module's CMakeLists.txt inside the existing if(OpenVINO_FOUND) block, so when OpenVINO is not installed the test is skipped along with the module itself. The test drives the public C API of the module (ov_ns_init / ov_ns_free / NOISE_SUPPRESSION_MODEL_NAME) rather than reaching into private structs, and is configurable from the command line: --model PATH use a caller-supplied OpenVINO model XML file. --dim N [--dim N ...] synthesize a minimal model XML with the given input-port dimensions, write it to a temporary file (mkstemps), and use that. --expect ok|reject expected outcome; default "reject". -h, --help print usage describing what the test does and how to run it. The mock-model writer emits the smallest XML that ov::Core::read_model() will accept for a Parameter input with the configured shape, so ov_ns_init() reaches the dimension-validation loop the previous commit added. The test then asserts that ov_ns_init() returned 0 for --expect ok and non-zero for --expect reject, and exits 0 (PASS) or 1 (FAIL) accordingly. Two ctest cases are registered at CMake time covering the two adversarial shapes that motivated the fix: ns_shape_zero --dim 0 --dim 480 # zero dim ns_shape_overflow --dim 9223372036854775807 --dim 1024 # overflow Real models can be exercised by invoking the executable directly with --model PATH --expect ok. Addresses review comments on PR #10886: r3403937663 - move test next to the module, build via the module's CMakeLists so OpenVINO/dep checks are reused. r3403939540 - take the model file / shape parameters on the command line instead of hard-coding /tmp/test_model_.xml. r3403944866 - add a -h help option describing the test and its usage. r3403950342 - add the missing SPDX-License-Identifier and copyright header; let CMake gate the build on OpenVINO presence. Signed-off-by: OrbisAI Security --- .../ov_noise_suppression/CMakeLists.txt | 18 +++ .../test_ns_shape_validation.cpp | 139 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp diff --git a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt index 039e9056ee45..fcf1439d0510 100644 --- a/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt +++ b/tools/plugin/modules/ov_noise_suppression/CMakeLists.txt @@ -39,4 +39,22 @@ set_target_properties(sof_ns INSTALL_RPATH "${sof_install_directory}/alsa-lib" INSTALL_RPATH_USE_LINK_PATH TRUE ) + +# Regression test for the input-shape validation guard. Gated on +# OpenVINO_FOUND alongside the module itself, so a host without +# OpenVINO simply skips it. +add_executable(test_ns_shape_validation test_ns_shape_validation.cpp) +target_link_libraries(test_ns_shape_validation PRIVATE sof_ns_interface) +target_include_directories(test_ns_shape_validation PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${sof_source_directory}/src/include + ${sof_source_directory}/posix/include + ${sof_source_directory}/src/arch/host/include + ${sof_source_directory}/src/platform/posix/include) +set_target_properties(test_ns_shape_validation PROPERTIES LINKER_LANGUAGE CXX) + +add_test(NAME ns_shape_zero + COMMAND test_ns_shape_validation --dim 0 --dim 480) +add_test(NAME ns_shape_overflow + COMMAND test_ns_shape_validation --dim 9223372036854775807 --dim 1024) endif() diff --git a/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp new file mode 100644 index 000000000000..9100a7c30d36 --- /dev/null +++ b/tools/plugin/modules/ov_noise_suppression/test_ns_shape_validation.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 OrbisAI Security. All rights reserved. +// +// Author: OrbisAI Security + +#include +#include +#include +#include +#include +#include + +#include "noise_suppression_interface.h" + +static void usage(const char *prog) +{ + std::printf( +"Usage: %s [--model PATH | --dim N [--dim N ...]] [--expect ok|reject]\n" +"\n" +"Regression test for the OpenVINO noise-suppression plugin's input-shape\n" +"validation (commit \"tools: plugin: ov_noise_suppression: validate model\n" +"input shape dimensions\"). Invokes ov_ns_init() against either:\n" +"\n" +" --model PATH a caller-supplied OpenVINO model XML file, or\n" +" --dim N [--dim N ...] a minimal synthesized model XML built from the\n" +" given input port dimensions and written to a\n" +" temporary file.\n" +"\n" +" --expect ok|reject expected outcome (default: reject).\n" +" \"ok\": ov_ns_init() must return 0.\n" +" \"reject\": ov_ns_init() must return non-zero.\n" +"\n" +"Returns 0 on PASS, 1 on FAIL, 2 on usage error.\n" +"\n" +"Examples:\n" +" %s --dim 0 --dim 480 # zero dim, expect reject\n" +" %s --dim 9223372036854775807 --dim 1024 # overflow, expect reject\n" +" %s --model real_model.xml --expect ok # real model, expect accept\n", + prog, prog, prog, prog); +} + +static int write_mock_model(const std::string &path, + const std::vector &dims) +{ + std::ofstream f(path); + if (!f) + return -1; + + f << "" + "" + ""; + for (auto d : dims) + f << "" << d << ""; + f << ""; + return f ? 0 : -1; +} + +int main(int argc, char **argv) +{ + std::string model_path; + std::vector dims; + bool expect_reject = true; + + for (int i = 1; i < argc; i++) { + std::string a = argv[i]; + if (a == "-h" || a == "--help") { + usage(argv[0]); + return 0; + } + if (a == "--model" && i + 1 < argc) { + model_path = argv[++i]; + } else if (a == "--dim" && i + 1 < argc) { + dims.push_back(std::strtoll(argv[++i], nullptr, 0)); + } else if (a == "--expect" && i + 1 < argc) { + std::string v = argv[++i]; + if (v == "ok") { + expect_reject = false; + } else if (v == "reject") { + expect_reject = true; + } else { + std::fprintf(stderr, + "unknown --expect value: %s\n", + v.c_str()); + usage(argv[0]); + return 2; + } + } else { + std::fprintf(stderr, "unknown or incomplete arg: %s\n", + a.c_str()); + usage(argv[0]); + return 2; + } + } + + std::string scratch; + if (model_path.empty()) { + if (dims.empty()) { + std::fprintf(stderr, + "must supply --model PATH or one or more --dim N\n"); + usage(argv[0]); + return 2; + } + + char tmpl[] = "/tmp/ns_shape_test_XXXXXX.xml"; + int fd = mkstemps(tmpl, 4); + if (fd < 0) { + std::perror("mkstemps"); + return 2; + } + close(fd); + scratch = tmpl; + if (write_mock_model(scratch, dims) != 0) { + std::fprintf(stderr, "failed to write mock model %s\n", + scratch.c_str()); + std::remove(scratch.c_str()); + return 2; + } + model_path = scratch; + } + + setenv("NOISE_SUPPRESSION_MODEL_NAME", model_path.c_str(), 1); + + ns_handle h = nullptr; + int rc = ov_ns_init(&h); + if (h) + ov_ns_free(h); + + if (!scratch.empty()) + std::remove(scratch.c_str()); + + bool rejected = (rc != 0); + bool pass = (rejected == expect_reject); + + std::printf("model=%s rc=%d expect=%s -> %s\n", model_path.c_str(), rc, + expect_reject ? "reject" : "ok", + pass ? "PASS" : "FAIL"); + return pass ? 0 : 1; +}