Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,18 @@ Pipeline Deserializer::deserialize(std::istream &in) {
}

Pipeline Deserializer::deserialize(const std::vector<uint8_t> &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<Serialize::Pipeline>())
<< "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";
Expand Down Expand Up @@ -1573,6 +1585,12 @@ std::map<std::string, Parameter> Deserializer::deserialize_parameters(std::istre

std::map<std::string, Parameter> Deserializer::deserialize_parameters(const std::vector<uint8_t> &data) {
std::map<std::string, Parameter> 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<Serialize::Pipeline>())
<< "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";
Expand Down
2 changes: 2 additions & 0 deletions test/correctness/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ()
92 changes: 92 additions & 0 deletions test/correctness/serialization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include "Halide.h"
#include <cstdio>

using namespace Halide;

#ifdef TEST_WITH_SERIALIZATION

#include <map>
#include <vector>

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<uint8_t> data;
std::map<std::string, Parameter> params;
serialize_pipeline(pipeline, data, params);

Pipeline deserialized = deserialize_pipeline(data, params);
Buffer<int> result = deserialized.realize({4, 4});

Buffer<int> 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<uint8_t> data;
std::map<std::string, Parameter> 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
Loading