From 2ae1fdbd0157327e2b8bfbeb1894148e978a5017 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Mon, 28 Sep 2026 16:38:14 +0800 Subject: [PATCH] Validate Plugin Symlink Targets Before Replacing Existing Files A symlink entry's target is now read and checked before anything already at its destination is removed or renamed aside, so an archive rejected for its link target leaves the installed plugin files in place. --- src/slic3r/GUI/GUI_App.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index dd2a9d8269..72849dd78e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1520,6 +1520,21 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP } auto dest_path = plugin_folder / dest_file; std::string dest_zip_file = encode_path(dest_path.string().c_str()); +#ifndef WIN32 + // Validate a symlink's target before anything at the destination is replaced. + const bool is_link = S_ISLNK(stat.m_external_attr >> 16); + std::string link; + if (is_link) { + link.assign(stat.m_uncomp_size, 0); + if (!mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0) || + !is_symlink_target_within_root(dest_file, link, plugin_folder)) { + BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " is unreadable or resolves outside " << plugin_folder.string(); + close_zip_reader(&archive); + if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); } + return InstallStatusUnzipFailed; + } + } +#endif try { boost::filesystem::create_directories(dest_path.parent_path()); // symlink_status so that an existing symlink, dangling or not, is replaced rather than written through. @@ -1551,15 +1566,8 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP } mz_bool res = 0; #ifndef WIN32 - if (S_ISLNK(stat.m_external_attr >> 16)) { - std::string link(stat.m_uncomp_size, 0); - res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, link.data(), stat.m_uncomp_size, 0); - if (res && !is_symlink_target_within_root(dest_file, link, plugin_folder)) { - BOOST_LOG_TRIVIAL(error) << "[install_plugin] link " << dest_file << " -> " << link << " resolves outside " << plugin_folder.string(); - close_zip_reader(&archive); - if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); } - return InstallStatusUnzipFailed; - } + if (is_link) { + res = 1; try { boost::filesystem::create_symlink(link, dest_path); } catch (const std::exception &e) {