From cf223c22f45267e64ebd077c1f3ca5cb0c1702ac Mon Sep 17 00:00:00 2001 From: Soma Aishwarya Date: Mon, 20 Jul 2026 02:06:27 +0530 Subject: [PATCH] guard uninitialized count from descr sscanf in npy parser --- test/correctness/image_io.cpp | 33 +++++++++++++++++++++++++++++++++ tools/halide_image_io.h | 8 ++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/test/correctness/image_io.cpp b/test/correctness/image_io.cpp index e24bccb9091b..581b8947b098 100644 --- a/test/correctness/image_io.cpp +++ b/test/correctness/image_io.cpp @@ -268,6 +268,38 @@ void test_mat_header() { } } +// A .npy whose 'descr' is missing its closing quote used to walk the header +// parser's cursor off the end of the buffer (sscanf matched %c%c%d and returned +// 3, but the %n was never reached, leaving the byte count uninitialized). The +// loader should just reject the file. +void test_malformed_npy_header() { + std::ostringstream o; + o << Internal::get_test_tmp_dir() << "malformed_descr.npy"; + std::string filename = o.str(); + + // v1 header: magic, version 1.0, 2-byte header length, then the dict. + // (6 + 2 + 2 + header_len) must be a multiple of 64. + std::string dict = "{'descr': '> 8) & 0xff)}; + fs.write(len_le, 2); + fs.write(dict.data(), dict.size()); + const std::vector payload(64, 0); + fs.write(payload.data(), payload.size()); + fs.close(); + + Buffer<> im; + if (Tools::load>(filename, &im)) { + std::cout << "Malformed .npy header was accepted by the loader\n"; + exit(1); + } +} + int main(int argc, char **argv) { do_test(); do_test(); @@ -283,6 +315,7 @@ int main(int argc, char **argv) { #endif do_test(); test_mat_header(); + test_malformed_npy_header(); printf("Success!\n"); return 0; } diff --git a/tools/halide_image_io.h b/tools/halide_image_io.h index 1f3f8dd01f99..980936e2d28a 100644 --- a/tools/halide_image_io.h +++ b/tools/halide_image_io.h @@ -1234,8 +1234,12 @@ struct NpyHeader { } while (true) { char endian; - int consumed; - if (std::sscanf(ptr, "'descr': '%c%c%d'%n", &endian, &type_code, &type_bytes, &consumed) == 3) { + int consumed = 0; + // %n is not counted in sscanf's return value and is only reached + // if the trailing quote matches, so a descr missing its closing + // quote returns 3 with consumed still 0. Requiring consumed > 0 + // avoids advancing ptr by an indeterminate amount. + if (std::sscanf(ptr, "'descr': '%c%c%d'%n", &endian, &type_code, &type_bytes, &consumed) == 3 && consumed > 0) { if (endian != '<' && endian != '|') { return false; }