util/crc32.hpp — header-only CRC-32 (zlib polynomial, reflected), used to integrity-check the sidecar. meta/veloxpart — the <name>.veloxpart.meta resume file (docs/04 §5). Little-endian, versioned, CRC-32 over the whole record. Layout: magic, version, flags, total_size, downloaded, url set (original/effective/ mirrors), etag/last-modified/content-type, segment records (start, end INCLUSIVE, completed), optional sha256 streaming-hash blob. parse_veloxpart() is the attacker-facing surface (the file sits in a world-writable-ish download dir) and is total on any byte string: CRC checked before any field is interpreted; magic, a version it understands, every count and length bounded by a hard cap AND checked against the remaining buffer; ByteReader latches on overrun; trailing bytes rejected. Every malformation is meta_corrupt / meta_version_unsupported, never a crash or an unbounded allocation. serialize_veloxpart() is deterministic (unchanged sidecar isn't rewritten). File helpers write atomically (temp + rename) and fdatasync the file and its directory. Tests: crc32 known vector; full + minimal round-trips; deterministic serialize; file round-trip; and a truncation/corruption table — bad magic, CRC mismatch (payload and CRC-field flips), future version, truncation at every stage, hostile url_count / segment_count / lp_string length (the case the brief singles out), trailing bytes, impossible segment.completed. tools/fuzz/fuzz_veloxpart — feeds raw bytes and bytes-with-valid-CRC (so the field parser and ByteReader bounds checks are actually reached), and round-trip-stability-checks anything accepted. Ran 1.1M execs clean under ASan+UBSan+libFuzzer (clang++-21); fuzz_content_disposition and fuzz_url likewise re-run to 1.1M. tools/fuzz gains a -runs=0 seed-replay CTest smoke per target (regression tripwire; the campaign stays manual). Fuzz-found and fixed: parse_content_disposition could emit a filename containing NUL / control bytes from a mangled filename* ext-value — strip_path only removed path separators. Now sanitize_leaf() also drops C0 controls and DEL (rules/ still owns the authoritative sanitize; `..` and printable-unsafe content pass through as before). Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
49 lines
1.5 KiB
C++
49 lines
1.5 KiB
C++
// vdm/util/crc32.hpp — CRC-32 (IEEE 802.3 / zlib polynomial), header-only.
|
|
//
|
|
// Used to integrity-check the .veloxpart.meta resume sidecar (docs/04 §5). Standard
|
|
// reflected CRC-32 with 0xEDB88320, init/xorout 0xFFFFFFFF — byte-compatible with
|
|
// zlib's crc32() and `cksum -o3` — so the value is reproducible outside this codebase.
|
|
//
|
|
// This header compiles standalone.
|
|
|
|
#ifndef VDM_UTIL_CRC32_HPP
|
|
#define VDM_UTIL_CRC32_HPP
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#include "vdm/util/bytes.hpp"
|
|
|
|
namespace vdm {
|
|
|
|
namespace detail {
|
|
inline constexpr std::array<std::uint32_t, 256> make_crc32_table() {
|
|
std::array<std::uint32_t, 256> t{};
|
|
for (std::uint32_t i = 0; i < 256; ++i) {
|
|
std::uint32_t c = i;
|
|
for (int k = 0; k < 8; ++k)
|
|
c = (c & 1u) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
|
|
t[i] = c;
|
|
}
|
|
return t;
|
|
}
|
|
inline constexpr std::array<std::uint32_t, 256> kCrc32Table = make_crc32_table();
|
|
} // namespace detail
|
|
|
|
// Incremental: pass the previous result back as `seed` to continue over split buffers.
|
|
[[nodiscard]] inline std::uint32_t crc32_update(std::uint32_t seed, ConstByteSpan data) noexcept {
|
|
std::uint32_t c = seed ^ 0xFFFFFFFFu;
|
|
for (std::byte b : data)
|
|
c = detail::kCrc32Table[(c ^ std::to_integer<std::uint8_t>(b)) & 0xFFu] ^ (c >> 8);
|
|
return c ^ 0xFFFFFFFFu;
|
|
}
|
|
|
|
[[nodiscard]] inline std::uint32_t crc32(ConstByteSpan data) noexcept {
|
|
return crc32_update(0u, data);
|
|
}
|
|
|
|
} // namespace vdm
|
|
|
|
#endif // VDM_UTIL_CRC32_HPP
|