Security fix: path traversal in 3MF import (#12860)

This commit is contained in:
SoftFever
2026-03-20 17:11:22 +08:00
committed by GitHub
parent f201997d9e
commit be5741d822

View File

@@ -100,6 +100,45 @@ struct ZipUnicodePathExtraField
}
};
// Validate that a relative file path does not escape the root directory via path traversal.
static bool is_path_within_root(const std::string& file_path, const boost::filesystem::path& root)
{
if (file_path.empty())
return false;
boost::filesystem::path p(file_path);
if (p.is_absolute())
return false;
// Reject any path component that is ".."
for (const auto& component : p) {
if (component == "..")
return false;
}
// Resolve the full path and verify it starts with the canonical root (also catches symlink escapes)
try {
boost::filesystem::path full_path = root / p;
boost::filesystem::path canonical_root = boost::filesystem::weakly_canonical(root);
boost::filesystem::path canonical_full = boost::filesystem::weakly_canonical(full_path);
auto root_str = canonical_root.string();
auto full_str = canonical_full.string();
if (full_str.length() < root_str.length())
return false;
if (full_str.compare(0, root_str.length(), root_str) != 0)
return false;
// Ensure it's a proper prefix (not just a substring of a longer directory name)
if (full_str.length() > root_str.length() &&
full_str[root_str.length()] != boost::filesystem::path::preferred_separator)
return false;
} catch (const boost::filesystem::filesystem_error&) {
return false;
}
return true;
}
// VERSION NUMBERS
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
@@ -1774,8 +1813,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("extract %1%th file %2%, total=%3%")%(i+1)%name%num_entries;
if (name.find("/../") != std::string::npos) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", find file path including /../, not valid, skip it\n");
if (name.find("/../") != std::string::npos ||
name.find("../") == 0 ||
(name.length() >= 3 && name.compare(name.length() - 3, 3, "/..") == 0) ||
name == "..") {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", find file path including path traversal, not valid, skip it\n");
continue;
}
@@ -2649,6 +2691,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
else
return;
if (!is_path_within_root(dest_file, dir)) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", path traversal detected in auxiliary file: %1%, skipping") % dest_file;
return;
}
if (dest_file.find('/') != std::string::npos) {
boost::filesystem::path src_path = boost::filesystem::path(dest_file);
boost::filesystem::path parent_path = src_path.parent_path();
@@ -2672,6 +2719,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
{
if (stat.m_uncomp_size > 0) {
std::string src_file = decode_path(stat.m_filename);
boost::filesystem::path backup_root(m_backup_path);
if (!is_path_within_root(src_file, backup_root)) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", path traversal detected in file: %1%, skipping") % src_file;
return;
}
// BBS: use backup path
//aux directory from model
boost::filesystem::path dest_path = boost::filesystem::path(m_backup_path + "/" + src_file);