aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/storage/workspaces/httpworkspaces.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenserver/storage/workspaces/httpworkspaces.cpp')
-rw-r--r--src/zenserver/storage/workspaces/httpworkspaces.cpp62
1 files changed, 59 insertions, 3 deletions
diff --git a/src/zenserver/storage/workspaces/httpworkspaces.cpp b/src/zenserver/storage/workspaces/httpworkspaces.cpp
index 12e7bae73..ba3bc00dd 100644
--- a/src/zenserver/storage/workspaces/httpworkspaces.cpp
+++ b/src/zenserver/storage/workspaces/httpworkspaces.cpp
@@ -4,6 +4,7 @@
#include <zencore/basicfile.h>
#include <zencore/compactbinarybuilder.h>
+#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/trace.h>
@@ -29,6 +30,48 @@ namespace {
return {};
}
+ // Validate a workspace root_path supplied via HTTP. Rejects empty / non-absolute
+ // paths, Windows UNC (\\server\share) and device-namespace prefixes (\\?\, \\.\),
+ // and strings containing control characters. Canonicalises the result so any later
+ // joins and stored config anchor at a resolved, existing directory — a follow-up
+ // symlink swap on disk can no longer redirect the workspace root.
+ std::optional<std::filesystem::path> ValidateWorkspaceRootPath(std::string_view RawInput)
+ {
+ if (RawInput.empty())
+ {
+ return std::nullopt;
+ }
+ for (char C : RawInput)
+ {
+ if (static_cast<unsigned char>(C) < 0x20 || C == 0x7F)
+ {
+ return std::nullopt;
+ }
+ }
+ if (RawInput.starts_with("\\\\") || RawInput.starts_with("//"))
+ {
+ return std::nullopt;
+ }
+
+ std::filesystem::path Requested(RawInput);
+ if (!Requested.is_absolute())
+ {
+ return std::nullopt;
+ }
+
+ std::error_code Ec;
+ std::filesystem::path Canonical = std::filesystem::canonical(Requested, Ec);
+ if (Ec)
+ {
+ return std::nullopt;
+ }
+ if (!std::filesystem::is_directory(Canonical, Ec) || Ec)
+ {
+ return std::nullopt;
+ }
+ return Canonical;
+ }
+
void WriteWorkspaceConfig(CbWriter& Writer, const Workspaces::WorkspaceConfiguration& Config)
{
Writer << "id" << Config.Id;
@@ -505,14 +548,17 @@ HttpWorkspacesService::WorkspaceRequest(HttpRouterRequest& Req)
{
case HttpVerb::kPut:
{
- std::filesystem::path WorkspacePath = GetPathParameter(ServerRequest, "root_path"sv);
- if (WorkspacePath.empty())
+ const std::string RawRootPath = HttpServerRequest::Decode(ServerRequest.GetQueryParams().GetValue("root_path"sv));
+ std::optional<std::filesystem::path> ValidatedRootPath = ValidateWorkspaceRootPath(RawRootPath);
+ if (!ValidatedRootPath)
{
m_WorkspacesStats.BadRequestCount++;
+ ZEN_WARN("workspace PUT rejected unsafe 'root_path' parameter '{}'", RawRootPath);
return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
HttpContentType::kText,
"Invalid 'root_path' parameter");
}
+ std::filesystem::path WorkspacePath = std::move(*ValidatedRootPath);
if (Req.GetCapture(1) == Oid::Zero.ToString())
{
@@ -1096,6 +1142,16 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req, const Oid& Workspace
fmt::format("Workspace '{}' does not exist", WorkspaceId));
}
+ std::optional<std::filesystem::path> ResolvedSharePath = ResolveSafeRelativePath(Workspace.RootPath, SharePath.string());
+ if (!ResolvedSharePath)
+ {
+ m_WorkspacesStats.BadRequestCount++;
+ ZEN_WARN("share PUT in workspace '{}' rejected unsafe 'share_path' parameter '{}'", WorkspaceId, SharePath);
+ return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
+ HttpContentType::kText,
+ "Invalid 'share_path' parameter");
+ }
+
if (!Workspace.AllowShareCreationFromHttp)
{
if (!MayChangeConfiguration(ServerRequest))
@@ -1143,7 +1199,7 @@ HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req, const Oid& Workspace
}
}
- if (!IsDir(Workspace.RootPath / NewConfig.SharePath))
+ if (!IsDir(*ResolvedSharePath))
{
return ServerRequest.WriteResponse(HttpResponseCode::NotFound,
HttpContentType::kText,