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/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 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; +}