diff options
Diffstat (limited to 'src/zenserver/storage/workspaces/httpworkspaces.cpp')
| -rw-r--r-- | src/zenserver/storage/workspaces/httpworkspaces.cpp | 62 |
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, |