diff --git a/src/Deserialization.cpp b/src/Deserialization.cpp index f7f8566326db..9c8d9622b3cc 100644 --- a/src/Deserialization.cpp +++ b/src/Deserialization.cpp @@ -1446,6 +1446,18 @@ Pipeline Deserializer::deserialize(std::istream &in) { } Pipeline Deserializer::deserialize(const std::vector &data) { + // The accessors below chase offsets straight out of the buffer with no + // structural check unless we ask for one, so a malformed .hlpipe can read + // out of bounds (Finish() writes no file identifier, so verify without + // one). Each IR node is one flatbuffer table, and deserialize_stmt/ + // deserialize_expr recurse to a depth matching the buffer's nesting, so + // max_depth is raised well past the default of 64 -- enough for deeply + // nested Exprs, but still a real bound on that recursion. + flatbuffers::Verifier::Options verifier_options; + verifier_options.max_depth = 1000; + flatbuffers::Verifier verifier(data.data(), data.size(), verifier_options); + user_assert(verifier.VerifyBuffer()) + << "malformed serialized pipeline: failed flatbuffer verification\n"; const auto *pipeline_obj = Serialize::GetPipeline(data.data()); if (pipeline_obj == nullptr) { user_warning << "deserialized pipeline is empty\n"; @@ -1573,6 +1585,12 @@ std::map Deserializer::deserialize_parameters(std::istre std::map Deserializer::deserialize_parameters(const std::vector &data) { std::map external_parameters_by_name; + // See the matching verifier in deserialize() above for why max_depth is raised. + flatbuffers::Verifier::Options verifier_options; + verifier_options.max_depth = 1000; + flatbuffers::Verifier verifier(data.data(), data.size(), verifier_options); + user_assert(verifier.VerifyBuffer()) + << "malformed serialized pipeline: failed flatbuffer verification\n"; const auto *pipeline_obj = Serialize::GetPipeline(data.data()); if (pipeline_obj == nullptr) { user_warning << "deserialized pipeline is empty\n"; diff --git a/test/correctness/CMakeLists.txt b/test/correctness/CMakeLists.txt index d88c13fa177f..bdb46e889174 100644 --- a/test/correctness/CMakeLists.txt +++ b/test/correctness/CMakeLists.txt @@ -314,6 +314,7 @@ tests( runtime_prefixes.cpp saturating_casts.cpp scatter.cpp + serialization.cpp set_custom_trace.cpp shadowed_bound.cpp shared_self_references.cpp @@ -571,4 +572,5 @@ set_target_properties( if (WITH_SERIALIZATION) target_compile_definitions(correctness_streaming PRIVATE TEST_WITH_SERIALIZATION) target_compile_definitions(correctness_generator_cache PRIVATE TEST_WITH_SERIALIZATION) + target_compile_definitions(correctness_serialization PRIVATE TEST_WITH_SERIALIZATION) endif () diff --git a/test/correctness/serialization.cpp b/test/correctness/serialization.cpp new file mode 100644 index 000000000000..5c3912bbd61c --- /dev/null +++ b/test/correctness/serialization.cpp @@ -0,0 +1,92 @@ +#include "Halide.h" +#include + +using namespace Halide; + +#ifdef TEST_WITH_SERIALIZATION + +#include +#include + +namespace { + +// A pipeline whose Expr tree nests deeper than flatbuffers::Verifier's +// default max_depth (64), to make sure legitimate pipelines aren't rejected +// by the verifier that guards deserialize(). +Pipeline make_deeply_nested_pipeline(Var x, Var y) { + Expr e = x + y; + for (int i = 0; i < 300; i++) { + e = e + i * (x - y); + } + Func f("deeply_nested"); + f(x, y) = e; + return Pipeline(f); +} + +} // namespace + +int main() { + Var x("x"), y("y"); + + // A pipeline nested well past the verifier's default table-depth limit + // round-trips through serialize/deserialize without error. + { + Pipeline pipeline = make_deeply_nested_pipeline(x, y); + + std::vector data; + std::map params; + serialize_pipeline(pipeline, data, params); + + Pipeline deserialized = deserialize_pipeline(data, params); + Buffer result = deserialized.realize({4, 4}); + + Buffer expected = pipeline.realize({4, 4}); + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + if (result(i, j) != expected(i, j)) { + printf("Mismatch at (%d, %d): expected %d, got %d\n", + i, j, expected(i, j), result(i, j)); + return 1; + } + } + } + } + + // A corrupted buffer is still rejected: raising max_depth must not + // disable the structural verification #9395 added. + { + Func f("f"); + f(x, y) = x + y; + + std::vector data; + std::map params; + serialize_pipeline(Pipeline(f), data, params); + + for (size_t i = data.size() / 2; i < data.size() / 2 + 32 && i < data.size(); i++) { + data[i] ^= 0xff; + } + + bool rejected = false; + try { + deserialize_pipeline(data, params); + } catch (const Error &) { + rejected = true; + } + if (!rejected) { + printf("Deserializing a corrupted buffer should have thrown an error\n"); + return 1; + } + } + + printf("Success!\n"); + return 0; +} + +#else + +int main() { + printf("[SKIP] Halide was compiled without serialization support.\n"); + return 0; +} + +#endif