mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-07 15:51:08 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6778ecf2d7 | ||
|
|
910eeef7b4 |
@@ -115,6 +115,9 @@ static const std::unordered_map<std::string, AuditEventCategory> audit_event_cat
|
|||||||
{"subprocess.Popen", AuditEventCategory::ProcessCreate},
|
{"subprocess.Popen", AuditEventCategory::ProcessCreate},
|
||||||
{"_winapi.CreateProcess", AuditEventCategory::ProcessCreate},
|
{"_winapi.CreateProcess", AuditEventCategory::ProcessCreate},
|
||||||
{"_posixsubprocess.fork_exec", AuditEventCategory::ProcessCreate},
|
{"_posixsubprocess.fork_exec", AuditEventCategory::ProcessCreate},
|
||||||
|
|
||||||
|
// threading
|
||||||
|
{"_thread.start_new_thread", AuditEventCategory::Threading},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the category event_name belongs to, or AuditEventCategory::None when it isn't audited.
|
// Returns the category event_name belongs to, or AuditEventCategory::None when it isn't audited.
|
||||||
@@ -735,6 +738,12 @@ std::vector<std::string> audit_targets(const std::string& event_name, AuditEvent
|
|||||||
}
|
}
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
case AuditEventCategory::Threading:
|
||||||
|
// Thread creation exposes no user-supplied target. Use a fixed sentinel so the grant
|
||||||
|
// persists per plugin: the permission list matches targets by exact string, and the
|
||||||
|
// started function's repr embeds an address that changes every run.
|
||||||
|
targets.emplace_back("thread");
|
||||||
|
return targets;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -761,6 +770,7 @@ std::vector<std::string>* permission_list_for(AuditEventCategory category, Plugi
|
|||||||
case AuditEventCategory::Http: return &permissions.network_http;
|
case AuditEventCategory::Http: return &permissions.network_http;
|
||||||
case AuditEventCategory::Socket: return &permissions.network_socket;
|
case AuditEventCategory::Socket: return &permissions.network_socket;
|
||||||
case AuditEventCategory::ProcessCreate: return &permissions.process;
|
case AuditEventCategory::ProcessCreate: return &permissions.process;
|
||||||
|
case AuditEventCategory::Threading: return &permissions.threading;
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -832,11 +842,74 @@ wxString audit_message(AuditEventCategory category, const wxString& plugin_name,
|
|||||||
return wxString::Format(_L("Plugin \"%s\" is requesting to open a network connection to:\n%s"), plugin_name, target_list);
|
return wxString::Format(_L("Plugin \"%s\" is requesting to open a network connection to:\n%s"), plugin_name, target_list);
|
||||||
case AuditEventCategory::ProcessCreate:
|
case AuditEventCategory::ProcessCreate:
|
||||||
return wxString::Format(_L("Plugin \"%s\" is requesting to run the following command(s):\n%s"), plugin_name, target_list);
|
return wxString::Format(_L("Plugin \"%s\" is requesting to run the following command(s):\n%s"), plugin_name, target_list);
|
||||||
|
case AuditEventCategory::Threading:
|
||||||
|
return wxString::Format(_L("Plugin \"%s\" is requesting permission to create a thread."), plugin_name);
|
||||||
default:
|
default:
|
||||||
return wxString::Format(_L("Plugin \"%s\" is requesting permission for the Python audit event \"%s\"."), plugin_name, event_name);
|
return wxString::Format(_L("Plugin \"%s\" is requesting permission for the Python audit event \"%s\"."), plugin_name, event_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Builds and shows the modal permission prompt. Must run on the GUI thread.
|
||||||
|
bool prompt_for_targets(AuditEventCategory category, const std::string& plugin_name, const std::string& event_name,
|
||||||
|
const std::vector<std::string>& unresolved)
|
||||||
|
{
|
||||||
|
wxString target_list;
|
||||||
|
for (const auto& target : unresolved)
|
||||||
|
target_list += wxString::FromUTF8(target.c_str()) + "\n";
|
||||||
|
|
||||||
|
wxMessageDialog dialog(nullptr,
|
||||||
|
audit_message(category, wxString::FromUTF8(plugin_name.c_str()),
|
||||||
|
wxString::FromUTF8(event_name.c_str()), target_list),
|
||||||
|
_L("Plugin permission request"), wxYES_NO | wxICON_WARNING);
|
||||||
|
return dialog.ShowModal() == wxID_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Records a grant in the plugin's sidecar so it is not asked again. Reads the install state
|
||||||
|
// freshly because the async prompt outlives the caller's stack copy of it.
|
||||||
|
void persist_grant(const std::string& plugin_key, AuditEventCategory category, const std::vector<std::string>& targets)
|
||||||
|
{
|
||||||
|
PluginInstallState state;
|
||||||
|
if (!PluginManager::instance().get_install_state(plugin_key, state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<std::string>* permission_list = permission_list_for(category, state.permissions);
|
||||||
|
if (!permission_list)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const auto& target : targets)
|
||||||
|
persist_permission(plugin_key, state, *permission_list, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requests permission for an audited event, returning true when it is already granted or the user
|
||||||
|
// approves an inline prompt.
|
||||||
|
//
|
||||||
|
// An audited event can fire on a thread the UI thread may itself be blocked waiting on: the
|
||||||
|
// SlicingPipeline hook runs on the slicing worker thread (see PluginHooks.cpp), and
|
||||||
|
// BackgroundSlicingProcess::stop()/stop_internal() park the UI thread until that worker stops.
|
||||||
|
// Blocking the worker on a marshaled modal -- which is safe for the plugin-load worker that
|
||||||
|
// request_filesystem_read_permissions runs on -- would therefore deadlock the application (the
|
||||||
|
// invariant PluginHostUi.cpp documents for slicing-hook UI calls). Off the main thread the prompt
|
||||||
|
// is therefore posted asynchronously and the current event denied (fail closed, like an unanswered
|
||||||
|
// prompt); the grant is persisted once the user accepts, so a later attempt succeeds without
|
||||||
|
// re-prompting.
|
||||||
|
bool request_permission(AuditEventCategory category, const std::string& plugin_key, const std::string& plugin_name,
|
||||||
|
const std::string& event_name, const std::vector<std::string>& unresolved)
|
||||||
|
{
|
||||||
|
if (wxTheApp == nullptr || GUI::wxGetApp().is_closing())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (wxIsMainThread())
|
||||||
|
return prompt_for_targets(category, plugin_name, event_name, unresolved);
|
||||||
|
|
||||||
|
GUI::wxGetApp().CallAfter([category, plugin_key, plugin_name, event_name, unresolved]() {
|
||||||
|
if (wxTheApp == nullptr || GUI::wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
|
if (prompt_for_targets(category, plugin_name, event_name, unresolved))
|
||||||
|
persist_grant(plugin_key, category, unresolved);
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int decide_audited_event(PluginAuditManager& mgr,
|
int decide_audited_event(PluginAuditManager& mgr,
|
||||||
PluginInstallState& state,
|
PluginInstallState& state,
|
||||||
const std::string& plugin_key,
|
const std::string& plugin_key,
|
||||||
@@ -857,15 +930,7 @@ int decide_audited_event(PluginAuditManager& mgr,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString target_list;
|
if (!request_permission(category, plugin_key, plugin_name, event_name, unresolved))
|
||||||
for (const auto& target : unresolved)
|
|
||||||
target_list += wxString::FromUTF8(target.c_str()) + "\n";
|
|
||||||
|
|
||||||
wxMessageDialog dialog(nullptr,
|
|
||||||
audit_message(category, wxString::FromUTF8(plugin_name.c_str()),
|
|
||||||
wxString::FromUTF8(event_name.c_str()), target_list),
|
|
||||||
_L("Plugin permission request"), wxYES_NO | wxICON_WARNING);
|
|
||||||
if (dialog.ShowModal() != wxID_YES)
|
|
||||||
return report_denied(mgr, event_name, {false, "audit permission required"});
|
return report_denied(mgr, event_name, {false, "audit permission required"});
|
||||||
|
|
||||||
if (permission_list)
|
if (permission_list)
|
||||||
|
|||||||
@@ -807,6 +807,7 @@ bool read_install_state(const boost::filesystem::path& plugin_dir, PluginInstall
|
|||||||
read_string_list("network_http", parsed.permissions.network_http);
|
read_string_list("network_http", parsed.permissions.network_http);
|
||||||
read_string_list("network_socket", parsed.permissions.network_socket);
|
read_string_list("network_socket", parsed.permissions.network_socket);
|
||||||
read_string_list("process", parsed.permissions.process);
|
read_string_list("process", parsed.permissions.process);
|
||||||
|
read_string_list("threading", parsed.permissions.threading);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.contains("enabled") && state["enabled"].is_boolean())
|
if (state.contains("enabled") && state["enabled"].is_boolean())
|
||||||
@@ -850,6 +851,7 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
|||||||
{"network_http", state.permissions.network_http},
|
{"network_http", state.permissions.network_http},
|
||||||
{"network_socket", state.permissions.network_socket},
|
{"network_socket", state.permissions.network_socket},
|
||||||
{"process", state.permissions.process},
|
{"process", state.permissions.process},
|
||||||
|
{"threading", state.permissions.threading},
|
||||||
};
|
};
|
||||||
|
|
||||||
nlohmann::json capabilities = nlohmann::json::array();
|
nlohmann::json capabilities = nlohmann::json::array();
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ struct PluginPermissions
|
|||||||
std::vector<std::string> network_http;
|
std::vector<std::string> network_http;
|
||||||
std::vector<std::string> network_socket;
|
std::vector<std::string> network_socket;
|
||||||
std::vector<std::string> process;
|
std::vector<std::string> process;
|
||||||
|
std::vector<std::string> threading;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PluginInstallState {
|
struct PluginInstallState {
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins
|
|||||||
state.permissions.network_http = {"https://api.example.com"};
|
state.permissions.network_http = {"https://api.example.com"};
|
||||||
state.permissions.network_socket = {"192.168.45.6:443"};
|
state.permissions.network_socket = {"192.168.45.6:443"};
|
||||||
state.permissions.process = {"/usr/bin/curl"};
|
state.permissions.process = {"/usr/bin/curl"};
|
||||||
|
state.permissions.threading = {"thread"};
|
||||||
REQUIRE(write_install_state(plugin_dir, state));
|
REQUIRE(write_install_state(plugin_dir, state));
|
||||||
|
|
||||||
// Permission data is persisted in the same sidecar as the installation metadata.
|
// Permission data is persisted in the same sidecar as the installation metadata.
|
||||||
@@ -132,6 +133,7 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins
|
|||||||
CHECK(persisted.permissions.network_http == state.permissions.network_http);
|
CHECK(persisted.permissions.network_http == state.permissions.network_http);
|
||||||
CHECK(persisted.permissions.network_socket == state.permissions.network_socket);
|
CHECK(persisted.permissions.network_socket == state.permissions.network_socket);
|
||||||
CHECK(persisted.permissions.process == state.permissions.process);
|
CHECK(persisted.permissions.process == state.permissions.process);
|
||||||
|
CHECK(persisted.permissions.threading == state.permissions.threading);
|
||||||
|
|
||||||
// Reading the sidecar back onto a freshly-scanned descriptor (whose header version is still
|
// Reading the sidecar back onto a freshly-scanned descriptor (whose header version is still
|
||||||
// 1.0.0) must surface the cloud-installed 1.2.0. This is what lets update_cloud_metadata compare
|
// 1.0.0) must surface the cloud-installed 1.2.0. This is what lets update_cloud_metadata compare
|
||||||
|
|||||||
Reference in New Issue
Block a user