srctree

David Zero parent 6d945d7c dc0de121
archive/zstd: Fix faulty output buffer sizing

inlinesplit
archive/zstd.cpp added: 136, removed: 8, total 128
@@ -7,7 +7,6 @@
#include <tl/expected.hpp>
#include <zstd.h>
 
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
@@ -56,11 +55,13 @@ tl::expected<std::vector<std::uint8_t>, ZstdError> zstd_decode(std::span<uint8_t
 
ZSTD_inBuffer in_buf = {input.data(), input.size_bytes(), 0};
 
std::size_t count = 1;
std::size_t count = 0;
std::size_t last_ret = 0;
std::size_t last_pos = 0;
 
while (in_buf.pos < in_buf.size) {
count++;
 
if ((chunk_size * count) > kMaxOutSize) {
return tl::unexpected{ZstdError::MaximumOutputLengthExceeded};
}
@@ -77,16 +78,19 @@ tl::expected<std::vector<std::uint8_t>, ZstdError> zstd_decode(std::span<uint8_t
 
last_ret = ret;
last_pos = out_buf.pos;
count++;
}
 
assert(last_pos > 0);
 
if (last_ret != 0) {
return tl::unexpected{ZstdError::DecodeEarlyTermination};
}
 
auto const out_size = (chunk_size * count) - (chunk_size * count - last_pos);
std::size_t out_size = 0;
 
if (count == 1) {
out_size = last_pos;
} else {
out_size = last_pos == 0 ? chunk_size * count : (chunk_size * count) - (chunk_size - last_pos);
}
 
// Shrink buffer to match what we actually decoded
out.resize(out_size);
 
archive/zstd_test.cpp added: 136, removed: 8, total 128
@@ -59,6 +59,7 @@ int main() {
tl::expected<std::vector<std::uint8_t>, ZstdError> ret = zstd_decode(kCompress);
 
a.expect(ret.has_value());
a.expect_eq(ret->size(), 22ul);
a.expect_eq(std::string(ret->begin(), ret->end()), "This is a test string\n");
});
 
@@ -69,6 +70,129 @@ int main() {
a.expect_eq(ret.error(), ZstdError::InputEmpty);
});
 
s.add_test("zero-sized output", [](etest::IActions &a) {
constexpr auto kCompress = std::to_array<std::uint8_t>(
{0x28, 0xb5, 0x2f, 0xfd, 0x24, 0x00, 0x01, 0x00, 0x00, 0x99, 0xe9, 0xd8, 0x51});
 
tl::expected<std::vector<std::uint8_t>, ZstdError> ret = zstd_decode(kCompress);
 
a.expect(ret.has_value());
a.expect(ret->empty());
});
 
s.add_test("decoding terminates on even chunk size", [](etest::IActions &a) {
constexpr auto kCompress = std::to_array<std::uint8_t>({0x28,
0xb5,
0x2f,
0xfd,
0x04,
0x58,
0x55,
0x00,
0x00,
0x10,
0x41,
0x41,
0x01,
0x00,
0xfb,
0xff,
0x39,
0xc0,
0x02,
0xe7,
0x8e,
0x9e,
0xc3});
 
tl::expected<std::vector<std::uint8_t>, ZstdError> ret = zstd_decode(kCompress);
 
a.expect(ret.has_value());
a.expect_eq(ret->size(), 131072ul); // ZSTD_DStreamOutSize, the default chunk value
 
for (std::uint8_t byte : *ret) {
a.expect_eq(byte, 0x41);
}
});
 
s.add_test("decoding terminates on even chunk size * 2", [](etest::IActions &a) {
constexpr auto kCompress = std::to_array<std::uint8_t>({0x28,
0xb5,
0x2f,
0xfd,
0x04,
0x58,
0x54,
0x00,
0x00,
0x10,
0x41,
0x41,
0x01,
0x00,
0xfb,
0xff,
0x39,
0xc0,
0x02,
0x03,
0x00,
0x10,
0x41,
0x42,
0x70,
0xf6,
0x4a});
 
tl::expected<std::vector<std::uint8_t>, ZstdError> ret = zstd_decode(kCompress);
 
a.expect(ret.has_value());
a.expect_eq(ret->size(), 262144ul); // ZSTD_DStreamOutSize * 2
 
for (std::uint8_t byte : *ret) {
a.expect_eq(byte, 0x41);
}
});
 
s.add_test("decoding terminates on chunk size + 20", [](etest::IActions &a) {
constexpr auto kCompress = std::to_array<std::uint8_t>({0x28,
0xb5,
0x2f,
0xfd,
0x04,
0x58,
0x54,
0x00,
0x00,
0x10,
0x41,
0x41,
0x01,
0x00,
0xfb,
0xff,
0x39,
0xc0,
0x02,
0xa3,
0x00,
0x00,
0x41,
0x65,
0xa2,
0xc2,
0xad});
 
tl::expected<std::vector<std::uint8_t>, ZstdError> ret = zstd_decode(kCompress);
 
a.expect(ret.has_value());
a.expect_eq(ret->size(), 131092ul); // ZSTD_DStreamOutSize + 20
 
for (std::uint8_t byte : *ret) {
a.expect_eq(byte, 0x41);
}
});
 
s.add_test("junk input", [](etest::IActions &a) {
constexpr auto kCompress = std::to_array<std::uint8_t>({0x00,
0x00,