aboutsummaryrefslogtreecommitdiff
path: root/src/zenhorde/hordebundle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenhorde/hordebundle.cpp')
-rw-r--r--src/zenhorde/hordebundle.cpp63
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)