voice_audio_multi/tests/unittests/test_safetensors_offsets.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

102 lines
3.8 KiB
C++

// A safetensors header carries data_offsets straight from the file. Casting a
// negative value to size_t yields ~SIZE_MAX, and data_end < data_begin makes the
// (data_end - data_begin) that every consumer computes underflow to the same.
// Either then wraps the caller's `offset + size > blob.size()` bounds check back
// under the limit, producing an enormous span over out-of-bounds memory from a
// malicious model file.
//
// These offsets are now rejected where they are parsed, so no consumer can
// inherit the underflow.
#include "engine/framework/io/safetensors.h"
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
void require(bool condition, const std::string & message) {
if (!condition) {
throw std::runtime_error(message);
}
}
// Minimal safetensors container: u64 little-endian header length, then the
// JSON header, then the tensor data region.
std::filesystem::path write_safetensors(const std::string & header_json, size_t data_bytes) {
static int counter = 0;
const auto path = std::filesystem::temp_directory_path() /
("safetensors_offsets_" + std::to_string(counter++) + ".safetensors");
std::ofstream out(path, std::ios::binary);
require(out.good(), "failed to open temp safetensors file");
const uint64_t header_len = header_json.size();
out.write(reinterpret_cast<const char *>(&header_len), sizeof(header_len));
out.write(header_json.data(), static_cast<std::streamsize>(header_json.size()));
const std::vector<char> data(data_bytes, 0);
out.write(data.data(), static_cast<std::streamsize>(data.size()));
require(out.good(), "failed to write temp safetensors file");
return path;
}
bool rejects(const std::string & header_json) {
const auto path = write_safetensors(header_json, 64);
bool threw = false;
try {
(void) engine::io::load_safetensors_index(path);
} catch (const std::runtime_error &) {
threw = true;
}
std::filesystem::remove(path);
return threw;
}
void test_inverted_offsets_are_rejected() {
// data_end < data_begin: the subtraction underflows to ~SIZE_MAX.
require(rejects(R"({"t":{"dtype":"F32","shape":[4],"data_offsets":[32,0]}})"),
"inverted data_offsets must be rejected");
}
void test_negative_offsets_are_rejected() {
require(rejects(R"({"t":{"dtype":"F32","shape":[4],"data_offsets":[-1,16]}})"),
"negative data_begin must be rejected");
require(rejects(R"({"t":{"dtype":"F32","shape":[4],"data_offsets":[0,-1]}})"),
"negative data_end must be rejected");
}
void test_valid_offsets_still_parse() {
// The guard must not reject legitimate headers, including empty tensors
// where data_begin == data_end.
const auto path = write_safetensors(
R"({"a":{"dtype":"F32","shape":[4],"data_offsets":[0,16]},)"
R"("b":{"dtype":"F32","shape":[0],"data_offsets":[16,16]}})", 64);
const auto index = engine::io::load_safetensors_index(path);
std::filesystem::remove(path);
require(index.tensors.size() == 2, "both tensors should parse");
const auto & a = index.tensors.at("a");
require(a.data_begin == 0 && a.data_end == 16, "tensor a offsets should round-trip");
const auto & b = index.tensors.at("b");
require(b.data_begin == b.data_end, "an empty tensor should remain valid");
}
} // namespace
int main() {
try {
test_inverted_offsets_are_rejected();
test_negative_offsets_are_rejected();
test_valid_offsets_still_parse();
} catch (const std::exception & error) {
std::cerr << "safetensors offsets test failed: " << error.what() << "\n";
return 1;
}
std::cout << "safetensors offsets test passed\n";
return 0;
}