Confine config import to the preset directory (#15608)

import_presets reduced each zip entry to a basename by stripping only
'/', so on Windows an entry named with '\' separators kept its
directory components and was extracted wherever they pointed. Strip
both separators, and reject any entry whose name still escapes the
extraction folder.

The preset name from the JSON and the bundle id from
bundle_structure.json were joined onto the preset directory unchecked
as well, which let either of them write outside it on every platform.
Both are now validated before anything is written.

The check is the is_path_within_root helper the 3MF importer already
had, moved to Utils so both importers share it. It treats '/' and '\'
as separators on every platform, so a bundle that would escape on one
OS is rejected on all of them.
This commit is contained in:
HanifKoh
2026-09-09 15:35:21 +08:00
committed by GitHub
parent 4deadc9dce
commit 8a291f9d56
5 changed files with 133 additions and 40 deletions
+24
View File
@@ -1088,6 +1088,30 @@ bool is_json_file(const std::string& path)
return boost::iends_with(path, ".json");
}
bool is_path_within_root(const std::string &rel_path, const boost::filesystem::path &root)
{
auto is_separator = [](char c) { return c == '/' || c == '\\'; };
if (rel_path.empty() || is_separator(rel_path.front()) || (rel_path.size() > 1 && rel_path[1] == ':'))
return false;
for (size_t start = 0; start <= rel_path.size();) {
size_t end = start;
while (end < rel_path.size() && !is_separator(rel_path[end]))
++end;
if (rel_path.compare(start, end - start, "..") == 0)
return false;
start = end + 1;
}
// Resolve against the canonical root so a symlink inside it cannot lead back out.
try {
const std::string root_str = boost::filesystem::weakly_canonical(root).string();
const std::string full_str = boost::filesystem::weakly_canonical(root / rel_path).string();
return full_str.compare(0, root_str.size(), root_str) == 0 &&
(full_str.size() == root_str.size() || full_str[root_str.size()] == boost::filesystem::path::preferred_separator);
} catch (const boost::filesystem::filesystem_error &) {
return false;
}
}
bool is_img_file(const std::string &path)
{
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");