aboutsummaryrefslogtreecommitdiff
path: root/src/zen/cmds/projectstore_cmd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zen/cmds/projectstore_cmd.cpp')
-rw-r--r--src/zen/cmds/projectstore_cmd.cpp91
1 files changed, 83 insertions, 8 deletions
diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp
index 2bd4803f6..d9ce1cfa7 100644
--- a/src/zen/cmds/projectstore_cmd.cpp
+++ b/src/zen/cmds/projectstore_cmd.cpp
@@ -8,6 +8,7 @@
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
+#include <zencore/except_fmt.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
@@ -120,6 +121,26 @@ namespace projectstore_impl {
}
}
+ // `OplogMirrorCommand::Run` uses a latching boolean flag rather than the
+ // SignalCounter above, because it drives a worker pool that aborts on any
+ // interrupt. Kept separate from SignalCallbackHandler so neither interferes
+ // with the other when both are installed in the same process.
+ static std::atomic<bool> MirrorAbortFlag{false};
+
+ static void MirrorSignalCallbackHandler(int SigNum)
+ {
+ if (SigNum == SIGINT)
+ {
+ MirrorAbortFlag.store(true);
+ }
+#if ZEN_PLATFORM_WINDOWS
+ if (SigNum == SIGBREAK)
+ {
+ MirrorAbortFlag.store(true);
+ }
+#endif
+ }
+
void ExecuteAsyncOperation(HttpClient& Http, std::string_view Url, IoBuffer&& Payload, bool PlainProgress)
{
signal(SIGINT, SignalCallbackHandler);
@@ -577,7 +598,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
m_OplogName = ResolveOplog(Http, m_ProjectName, m_OplogName);
if (m_OplogName.empty())
{
- throw std::runtime_error(fmt::format("Can't find oplog in project '{}'", m_OplogName, m_ProjectName));
+ throw zen::runtime_error("Can't find oplog '{}' in project '{}'", m_OplogName, m_ProjectName);
}
if (m_DryRun)
{
@@ -1098,7 +1119,7 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
std::string TargetUrlBase = m_ZenUrl;
if (TargetUrlBase.find("://") == std::string::npos)
{
- // Assume https URL
+ // Assume http URL
TargetUrlBase = fmt::format("http://{}", TargetUrlBase);
}
@@ -1287,7 +1308,14 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
std::filesystem::path MetadataPath(m_BuildsMetadataPath);
IoBuffer MetaDataJson = ReadFile(MetadataPath).Flatten();
std::string_view Json(reinterpret_cast<const char*>(MetaDataJson.GetData()), MetaDataJson.GetSize());
- CbFieldIterator MetaData = LoadCompactBinaryFromJson(Json);
+ std::string JsonError;
+ CbFieldIterator MetaData = LoadCompactBinaryFromJson(Json, JsonError);
+ if (!JsonError.empty())
+ {
+ throw zen::runtime_error("builds metadata file '{}' is malformed. Reason: '{}'",
+ MetadataPath.string(),
+ JsonError);
+ }
Writer.AddBinary("metadata"sv, MetaData.GetBuffer());
}
if (!m_BuildsMetadata.empty())
@@ -1297,7 +1325,7 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
size_t SplitPos = Pair.find('=');
if (SplitPos == std::string::npos || SplitPos == 0)
{
- throw std::runtime_error(fmt::format("builds metadata key-value pair '{}' is malformed", Pair));
+ throw zen::runtime_error("builds metadata key-value pair '{}' is malformed", Pair);
}
MetaDataWriter.AddString(Pair.substr(0, SplitPos), Pair.substr(SplitPos + 1));
return true;
@@ -1307,7 +1335,7 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
}
}
Writer.EndObject(); // "builds"
- TargetDescription = fmt::format("[builds] {}/{}/{}/{}", m_CloudUrl, m_JupiterNamespace, m_JupiterBucket, m_BuildsId);
+ TargetDescription = fmt::format("[builds] {}/{}/{}/{}", m_BuildsUrl, m_JupiterNamespace, m_JupiterBucket, m_BuildsId);
}
if (!m_ZenUrl.empty())
{
@@ -1716,7 +1744,11 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
}
}
Writer.EndObject(); // "builds"
- SourceDescription = fmt::format("[builds] {}/{}/{}/{}", m_CloudUrl, m_JupiterNamespace, m_JupiterBucket, m_BuildsId);
+ SourceDescription = fmt::format("[builds] {}/{}/{}/{}",
+ m_BuildsHost.empty() ? m_BuildsOverrideHost : m_BuildsHost,
+ m_JupiterNamespace,
+ m_JupiterBucket,
+ m_BuildsId);
}
if (!m_ZenUrl.empty())
{
@@ -2062,16 +2094,59 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
std::unordered_set<std::u8string> FileNames;
std::atomic<uint64_t> WrittenByteCount = 0;
- std::atomic<bool> AbortFlag(false);
+ // Install Ctrl-C handler so SIGINT aborts the worker pool rather than killing
+ // the process. Without this the local AbortFlag would shadow whatever global
+ // handler is installed elsewhere and interrupts would be dropped. RAII so
+ // the previous handler is restored when the function returns or throws.
+ MirrorAbortFlag.store(false);
+ ScopedSignalHandler SigIntGuard(SIGINT, MirrorSignalCallbackHandler);
+#if ZEN_PLATFORM_WINDOWS
+ ScopedSignalHandler SigBreakGuard(SIGBREAK, MirrorSignalCallbackHandler);
+#endif
+ std::atomic<bool>& AbortFlag = MirrorAbortFlag;
Stopwatch WriteStopWatch;
+ // Filenames come from the remote oplog, which may be compromised or untrusted.
+ // Reject anything that could escape the mirror root via an absolute path, drive
+ // letter / UNC / device path prefix, or '..' component before it is joined to
+ // RootPath. Returns nullptr when the filename is safe.
+ auto UnsafeFileNameReason = [](const std::filesystem::path& FileName) -> const char* {
+ if (FileName.empty())
+ {
+ return "filename is empty";
+ }
+ if (FileName.has_root_name())
+ {
+ return "filename has a root name (drive letter, UNC share, or device path)";
+ }
+ if (FileName.has_root_directory())
+ {
+ return "filename is absolute";
+ }
+ for (const std::filesystem::path& Component : FileName)
+ {
+ const std::u8string C = Component.u8string();
+ if (C.empty() || C == u8"..")
+ {
+ return "filename contains a '..' or empty component";
+ }
+ }
+ return nullptr;
+ };
+
auto EmitFilesForDataArray = [&](CbArrayView DataArray) {
for (auto DataIter : DataArray)
{
if (CbObjectView Data = DataIter.AsObjectView())
{
std::filesystem::path FileName(Data["filename"sv].AsU8String());
+ if (const char* Reason = UnsafeFileNameReason(FileName))
+ {
+ ZEN_CONSOLE_ERROR("Rejecting unsafe filename '{}' from remote oplog: {}", FileName.string(), Reason);
+ AbortFlag.store(true);
+ break;
+ }
if (!m_FilenameFilter.empty())
{
std::string FileNameLowerCase = ToLower(FileName.string());
@@ -2217,7 +2292,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
if (AbortFlag)
{
- throw std::runtime_error("Failed top mirror oplog");
+ throw std::runtime_error("Failed to mirror oplog");
}
}
else