mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 08:11:14 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d8a81a14e |
@@ -11,7 +11,6 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
@@ -145,28 +144,29 @@ static bool is_fs_category(AuditEventCategory category)
|
||||
// Path safety
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// weakly_canonical resolves symlinks but does NOT require the path to exist: it canonicalizes
|
||||
// the prefix that exists and appends the non-existing tail lexically. Falls back to an absolute,
|
||||
// lexically normal path when even that fails.
|
||||
static boost::filesystem::path canonical_or_normal(const boost::filesystem::path& path)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
boost::system::error_code ec;
|
||||
fs::path canon = fs::weakly_canonical(path, ec);
|
||||
if (ec) {
|
||||
canon = fs::absolute(path, ec).lexically_normal();
|
||||
if (ec)
|
||||
canon = path;
|
||||
}
|
||||
return canon;
|
||||
}
|
||||
|
||||
bool is_inside_allowed_root(const boost::filesystem::path& candidate, const boost::filesystem::path& allowed_root)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
fs::path canon_candidate = canonical_or_normal(candidate);
|
||||
fs::path canon_root = canonical_or_normal(allowed_root);
|
||||
boost::system::error_code ec;
|
||||
|
||||
// Canonicalize both paths. weakly_canonical resolves symlinks but does
|
||||
// NOT require the path to exist — it canonicalizes the prefix that exists
|
||||
// and appends the non-existing tail lexically.
|
||||
fs::path canon_candidate = fs::weakly_canonical(candidate, ec);
|
||||
if (ec) {
|
||||
// Fall back to lexically_normal + absolute
|
||||
canon_candidate = fs::absolute(candidate, ec).lexically_normal();
|
||||
if (ec)
|
||||
canon_candidate = candidate;
|
||||
}
|
||||
|
||||
fs::path canon_root = fs::weakly_canonical(allowed_root, ec);
|
||||
if (ec) {
|
||||
canon_root = fs::absolute(allowed_root, ec).lexically_normal();
|
||||
if (ec)
|
||||
canon_root = allowed_root;
|
||||
}
|
||||
|
||||
// Component-wise comparison: the root must be a prefix of candidate,
|
||||
// and the next component must not be ".." or missing.
|
||||
@@ -354,34 +354,19 @@ bool PluginAuditManager::is_denied_path_keyword(const boost::filesystem::path& c
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
fs::path canon = canonical_or_normal(candidate);
|
||||
boost::system::error_code ec;
|
||||
fs::path canon = fs::weakly_canonical(candidate, ec);
|
||||
if (ec) {
|
||||
canon = fs::absolute(candidate, ec).lexically_normal();
|
||||
if (ec)
|
||||
canon = candidate;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_denied_path_keywords.empty())
|
||||
return false;
|
||||
|
||||
// Only the part of the path the plugin chose is judged. The components of the allowed root it
|
||||
// sits in are the host's (on Linux the data directory is ~/.config/OrcaSlicer), so a keyword
|
||||
// there must not deny everything underneath; a keyword below the root still does, which keeps
|
||||
// resources_dir()/cert/... unreachable.
|
||||
fs::path below = canon;
|
||||
size_t depth = 0;
|
||||
auto consider = [&](const AllowedRoot& root) {
|
||||
if (!is_inside_allowed_root(canon, root.path))
|
||||
return;
|
||||
const fs::path canon_root = canonical_or_normal(root.path);
|
||||
const size_t root_depth = static_cast<size_t>(std::distance(canon_root.begin(), canon_root.end()));
|
||||
if (root_depth > depth) {
|
||||
depth = root_depth;
|
||||
below = canon.lexically_relative(canon_root);
|
||||
}
|
||||
};
|
||||
for (const auto& root : m_scoped_allowed_roots)
|
||||
consider(root);
|
||||
for (const auto& root : m_global_allowed_roots)
|
||||
consider(root);
|
||||
|
||||
for (const auto& component : below) {
|
||||
for (const auto& component : canon) {
|
||||
std::string name = component.string();
|
||||
if (name.empty())
|
||||
continue;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <wx/app.h>
|
||||
#include <wx/defs.h>
|
||||
#include <wx/thread.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/toplevel.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
@@ -415,6 +416,44 @@ struct UiDockPanelHandle
|
||||
int id{0};
|
||||
};
|
||||
|
||||
// Polls until the plater is shown on screen, then runs the callback with it (null when the app
|
||||
// is closing instead) and deletes itself. A plugin loaded at startup opens its panes from
|
||||
// on_load, before the main window exists, and a pane added before that window is laid out is
|
||||
// sized against the unsized frame and keeps that width (AuiMgr::track_docked_size).
|
||||
class PlaterShownWaiter : public wxTimer
|
||||
{
|
||||
public:
|
||||
explicit PlaterShownWaiter(std::function<void(GUI::Plater*)> fn) : m_fn(std::move(fn)) {}
|
||||
|
||||
// True once the callback ran.
|
||||
bool try_run()
|
||||
{
|
||||
const bool closing = GUI::wxGetApp().is_closing();
|
||||
GUI::Plater* plater = closing ? nullptr : GUI::wxGetApp().plater();
|
||||
if (!closing && (plater == nullptr || !plater->IsShownOnScreen()))
|
||||
return false;
|
||||
Stop();
|
||||
m_fn(plater);
|
||||
// Off the timer callback's stack: wxGTK's timeout callback still reads the timer after Notify().
|
||||
GUI::wxGetApp().CallAfter([this]() { delete this; });
|
||||
return true;
|
||||
}
|
||||
|
||||
void Notify() override { try_run(); }
|
||||
|
||||
private:
|
||||
std::function<void(GUI::Plater*)> m_fn;
|
||||
};
|
||||
|
||||
void run_when_plater_shown(std::function<void(GUI::Plater*)> fn)
|
||||
{
|
||||
GUI::wxGetApp().CallAfter([fn = std::move(fn)]() mutable {
|
||||
auto* waiter = new PlaterShownWaiter(std::move(fn));
|
||||
if (!waiter->try_run())
|
||||
waiter->Start(100);
|
||||
});
|
||||
}
|
||||
|
||||
py::object ui_create_dock_panel(const std::string& html, const std::string& title, int width, int height,
|
||||
py::object on_message, py::object on_close, const std::string& dock)
|
||||
{
|
||||
@@ -434,14 +473,13 @@ py::object ui_create_dock_panel(const std::string& html, const std::string& titl
|
||||
const int new_id = UiRegistry::instance().reserve_id();
|
||||
UiRegistry::instance().bind(new_id, nullptr, plugin_key);
|
||||
|
||||
GUI::wxGetApp().CallAfter([new_id, plugin_key, html, title, dock, w, h,
|
||||
msg_adapter = std::move(msg_adapter),
|
||||
close_holder = std::move(close_holder)]() mutable {
|
||||
run_when_plater_shown([new_id, plugin_key, html, title, dock, w, h,
|
||||
msg_adapter = std::move(msg_adapter),
|
||||
close_holder = std::move(close_holder)](GUI::Plater* plater) mutable {
|
||||
if (!UiRegistry::instance().is_open(new_id))
|
||||
return;
|
||||
|
||||
GUI::Plater* plater = GUI::wxGetApp().plater();
|
||||
if (plater == nullptr || GUI::wxGetApp().is_closing()) {
|
||||
if (plater == nullptr) {
|
||||
UiRegistry::instance().remove(new_id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -253,44 +253,6 @@ TEST_CASE("Plugin audit denies secret/certificate/config-like paths by keyword",
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Plugin audit keyword deny ignores the allowed root's own path", "[audit]")
|
||||
{
|
||||
seed_denied_names();
|
||||
seed_denied_keywords();
|
||||
PluginAuditManager& mgr = PluginAuditManager::instance();
|
||||
|
||||
// The data directory of a Linux install: its own path carries the "conf" keyword.
|
||||
const fs::path root = fs::temp_directory_path() / "plugin-audit-xdg" / ".config" / "OrcaSlicer";
|
||||
mgr.add_global_allowed_root(root.string());
|
||||
|
||||
SECTION("a plain file below the root is not denied by the root's components")
|
||||
{
|
||||
CHECK_FALSE(mgr.is_denied_path_keyword(root / "log" / "debug.log"));
|
||||
CHECK_FALSE(mgr.is_denied_path_keyword(root / "orca_plugins" / "plugin_data" / "key" / "state.json"));
|
||||
}
|
||||
|
||||
SECTION("a keyword below the root still denies")
|
||||
{
|
||||
CHECK(mgr.is_denied_path_keyword(root / "cert" / "ca.pem"));
|
||||
CHECK(mgr.is_denied_path_keyword(root / "plugin" / "secrets" / "token.txt"));
|
||||
CHECK(mgr.is_denied_path_keyword(root / "plugin.conf"));
|
||||
}
|
||||
|
||||
SECTION("a keyword in a path outside every root still denies")
|
||||
{
|
||||
CHECK(mgr.is_denied_path_keyword(fs::path("/elsewhere/.config/app/file.txt")));
|
||||
}
|
||||
|
||||
SECTION("inside a plugin context the log is readable and the app config stays blocked")
|
||||
{
|
||||
ScopedPluginAuditContext ctx("test_plugin", "");
|
||||
CHECK(mgr.check_open((root / "log" / "debug.log").string(), "r").allowed);
|
||||
AuditDecision decision = mgr.check_open((root / (SLIC3R_APP_KEY ".conf")).string(), "r");
|
||||
CHECK_FALSE(decision.allowed);
|
||||
CHECK(decision.reason == "denied filename");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Plugin audit is_denied_path combines the filename and keyword registries", "[audit]")
|
||||
{
|
||||
seed_denied_names();
|
||||
|
||||
Reference in New Issue
Block a user