diff options
Diffstat (limited to 'src/zenhorde/hordebundle.cpp')
| -rw-r--r-- | src/zenhorde/hordebundle.cpp | 63 |
1 files changed, 36 insertions, 27 deletions
diff --git a/src/zenhorde/hordebundle.cpp b/src/zenhorde/hordebundle.cpp index d3974bc28..8493a9456 100644 --- a/src/zenhorde/hordebundle.cpp +++ b/src/zenhorde/hordebundle.cpp @@ -10,6 +10,7 @@ #include <zencore/logging.h> #include <zencore/process.h> #include <zencore/trace.h> +#include <zencore/uid.h> #include <algorithm> #include <chrono> @@ -48,7 +49,7 @@ static constexpr uint8_t BlobType_DirectoryV1[20] = {0x11, 0xEC, 0x14, 0x07, 0x1 static constexpr size_t BlobTypeSize = 20; -// ─── VarInt helpers (UE format) ───────────────────────────────────────────── +// --- VarInt helpers (UE format) --------------------------------------------- static size_t MeasureVarInt(size_t Value) @@ -57,7 +58,7 @@ MeasureVarInt(size_t Value) { return 1; } - return (FloorLog2(static_cast<unsigned int>(Value)) / 7) + 1; + return (FloorLog2_64(static_cast<uint64_t>(Value)) / 7) + 1; } static void @@ -76,7 +77,7 @@ WriteVarInt(std::vector<uint8_t>& Buffer, size_t Value) Output[0] = static_cast<uint8_t>((0xFF << (9 - static_cast<int>(ByteCount))) | static_cast<uint8_t>(Value)); } -// ─── Binary helpers ───────────────────────────────────────────────────────── +// --- Binary helpers --------------------------------------------------------- static void WriteLE32(std::vector<uint8_t>& Buffer, int32_t Value) @@ -121,7 +122,7 @@ PatchLE32(std::vector<uint8_t>& Buffer, size_t Offset, int32_t Value) memcpy(Buffer.data() + Offset, &Value, 4); } -// ─── Packet builder ───────────────────────────────────────────────────────── +// --- Packet builder --------------------------------------------------------- // Builds a single uncompressed Horde V2 packet. Layout: // [Signature(3) + Version(1) + PacketLength(4)] 8 bytes (header) @@ -229,7 +230,7 @@ struct PacketBuilder { AlignTo4(Data); - // ── Type table: count(int32) + count * BlobTypeSize bytes ── + // -- Type table: count(int32) + count * BlobTypeSize bytes -- const int32_t TypeTableOffset = static_cast<int32_t>(Data.size()); WriteLE32(Data, static_cast<int32_t>(Types.size())); for (const uint8_t* TypeEntry : Types) @@ -237,12 +238,12 @@ struct PacketBuilder WriteBytes(Data, TypeEntry, BlobTypeSize); } - // ── Import table: count(int32) + (count+1) offsets(int32 each) + import data ── + // -- Import table: count(int32) + (count+1) offsets(int32 each) + import data -- const int32_t ImportTableOffset = static_cast<int32_t>(Data.size()); const int32_t ImportCount = static_cast<int32_t>(Imports.size()); WriteLE32(Data, ImportCount); - // Reserve space for (count+1) offset entries — will be patched below + // Reserve space for (count+1) offset entries - will be patched below const size_t ImportOffsetsStart = Data.size(); for (int32_t i = 0; i <= ImportCount; ++i) { @@ -266,7 +267,7 @@ struct PacketBuilder // Sentinel offset (points past the last import's data) PatchLE32(Data, ImportOffsetsStart + static_cast<size_t>(ImportCount) * 4, static_cast<int32_t>(Data.size())); - // ── Export table: count(int32) + (count+1) offsets(int32 each) ── + // -- Export table: count(int32) + (count+1) offsets(int32 each) -- const int32_t ExportTableOffset = static_cast<int32_t>(Data.size()); const int32_t ExportCount = static_cast<int32_t>(ExportOffsets.size()); WriteLE32(Data, ExportCount); @@ -278,7 +279,7 @@ struct PacketBuilder // Sentinel: points to the start of the type table (end of export data region) WriteLE32(Data, TypeTableOffset); - // ── Patch header ── + // -- Patch header -- // PacketLength = total packet size including the 8-byte header const int32_t PacketLength = static_cast<int32_t>(Data.size()); PatchLE32(Data, 4, PacketLength); @@ -290,7 +291,7 @@ struct PacketBuilder } }; -// ─── Encoded packet wrapper ───────────────────────────────────────────────── +// --- Encoded packet wrapper ------------------------------------------------- // Wraps an uncompressed packet with the encoded header: // [Signature(3) + Version(1) + HeaderLength(4)] 8 bytes @@ -327,24 +328,22 @@ EncodePacket(std::vector<uint8_t> UncompressedPacket) return Encoded; } -// ─── Bundle blob name generation ──────────────────────────────────────────── +// --- Bundle blob name generation -------------------------------------------- static std::string GenerateBlobName() { - static std::atomic<uint32_t> s_Counter{0}; - - const int Pid = GetCurrentProcessId(); - - auto Now = std::chrono::steady_clock::now().time_since_epoch(); - auto Ms = std::chrono::duration_cast<std::chrono::milliseconds>(Now).count(); - - ExtendableStringBuilder<64> Name; - Name << Pid << "_" << Ms << "_" << s_Counter.fetch_add(1); - return std::string(Name.ToView()); + // Oid is a 12-byte identifier built from a timestamp, a monotonic serial number + // initialised from std::random_device, and a per-process run id also drawn from + // std::random_device. The 24-hex-char rendering gives ~80 bits of effective + // name-prediction entropy, so a local attacker cannot race-create the blob + // path before we open it. Previously the name was pid+ms+counter, which two + // zenserver processes with the same PID could collide on and which was + // entirely predictable. + return zen::Oid::NewOid().ToString(); } -// ─── File info for bundling ───────────────────────────────────────────────── +// --- File info for bundling ------------------------------------------------- struct FileInfo { @@ -357,7 +356,7 @@ struct FileInfo IoHash RootExportHash; // IoHash of the root export for this file }; -// ─── CreateBundle implementation ──────────────────────────────────────────── +// --- CreateBundle implementation -------------------------------------------- bool BundleCreator::CreateBundle(const std::vector<BundleFile>& Files, const std::filesystem::path& OutputDir, BundleResult& OutResult) @@ -534,7 +533,7 @@ BundleCreator::CreateBundle(const std::vector<BundleFile>& Files, const std::fil FileInfo& Info = ValidFiles[i]; DirImports.push_back(Info.DirectoryExportImportIndex); - // IoHash of target (20 bytes) — import is consumed sequentially from the + // IoHash of target (20 bytes) - import is consumed sequentially from the // export's import list by ReadBlobRef, not encoded in the payload WriteBytes(DirPayload, Info.RootExportHash.Hash, sizeof(IoHash)); // name (string) @@ -557,8 +556,16 @@ BundleCreator::CreateBundle(const std::vector<BundleFile>& Files, const std::fil std::vector<uint8_t> UncompressedPacket = Packet.Finish(); std::vector<uint8_t> EncodedPacket = EncodePacket(std::move(UncompressedPacket)); - // Write .blob file + // Write .blob file. Refuse to proceed if a file with this name already exists - + // the Oid-based BlobName should make collisions astronomically unlikely, so an + // existing file implies either an extraordinary collision or an attacker having + // pre-seeded the path; either way, we do not want to overwrite it. const std::filesystem::path BlobFilePath = OutputDir / (BlobName + ".blob"); + if (std::filesystem::exists(BlobFilePath, Ec)) + { + ZEN_ERROR("blob file already exists at {} - refusing to overwrite", BlobFilePath.string()); + return false; + } { BasicFile BlobFile(BlobFilePath, BasicFile::Mode::kTruncate, Ec); if (Ec) @@ -574,8 +581,10 @@ BundleCreator::CreateBundle(const std::vector<BundleFile>& Files, const std::fil Locator << BlobName << "#pkt=0," << uint64_t(EncodedPacket.size()) << "&exp=" << DirExportIndex; const std::string LocatorStr(Locator.ToView()); - // Write .ref file (use first file's name as the ref base) - const std::filesystem::path RefFilePath = OutputDir / (ValidFiles[0].Name + ".Bundle.ref"); + // Write .ref file. Include the Oid-based BlobName so that two concurrent + // CreateBundle() calls into the same OutputDir that happen to share the first + // filename don't clobber each other's ref file. + const std::filesystem::path RefFilePath = OutputDir / (ValidFiles[0].Name + "." + BlobName + ".Bundle.ref"); { BasicFile RefFile(RefFilePath, BasicFile::Mode::kTruncate, Ec); if (Ec) |