Compare commits

..
4 Commits
Author SHA1 Message Date
Hanif Koh 5b92b996b2 Stage GMP 6.3.0 for the Flatpak Build
The Flatpak sandbox has no network, so the manifest pre-downloads every
dependency tarball into external-packages/. Point the GMP entry at the
6.3.0 .tar.xz the deps recipe now expects.
2026-10-20 17:59:39 +08:00
Hanif Koh 44ecb9f8c1 Use the OrcaSlicer_deps Mirror as the GMP Fallback Download
SoftFever published gmp-6.3.0.tar.xz in SoftFever/OrcaSlicer_deps, the
same mirror 6.2.1 came from. Both sources serve the .tar.xz release file,
byte-identical (one SHA-256): the GNU server first, the mirror as fallback.
2026-10-20 17:59:39 +08:00
Hanif Koh 41a7d72b1c Add gmplib.org as a Fallback Download for GMP
Same two-source pattern as MPFR: the GNU mirror first, the project's own
server second, one hash for both.
2026-10-20 17:59:39 +08:00
Hanif Koh 13b8395d21 Update GMP to 6.3.0 to Avoid the x18 Register on Apple Silicon
GMP 6.2.1's arm64 assembly keeps loop counters in x18, which Apple reserves and
macOS zeroes on every return to user space (syscalls, page faults, preemption).
When that happens inside one of those loops the counter underflows and the
routine walks the stack until the guard page: EXC_BAD_ACCESS / SIGBUS inside
__gmpn_* whenever CGAL falls back to exact arithmetic (texture import boundary
smoothing, text emboss cuts, mesh repair, mesh boolean).

6.3.0 moves those counters to ordinary temporaries (gmp-6.2 changeset
f4ff6ff711ed); no x18 use remains under mpn/arm64. The GCC 15 patch applies
unchanged apart from a line offset. The tarball comes from the GNU mirror, like
MPFR. Windows is unaffected: MSVC copies the prebuilt libgmp-10 and the arm64
job stages MSYS2's GMP.

Built with the deps recipe flags and ran make check on Linux x86_64 (clang 18,
200/200) and macOS arm64 (Apple clang, 199/199).
2026-10-20 17:59:39 +08:00
3 changed files with 7 additions and 106 deletions

No files matched your search

+6 -53
View File
@@ -353,18 +353,16 @@ void PluginAuditManager::add_denied_filename(const std::string& filename)
std::vector<std::string> PluginAuditManager::default_denied_filenames()
{
// AppConfig::config_path() picks .conf vs .ini on the USE_JSON_CONFIG ifdef and the app key
// on its mode, and is a non-static member we cannot call without an instance. Denying all
// on its mode, and is a non-static member we cannot call without an instance. Denying all
// four names is cheaper and more robust than replicating that; the two unused names cost one
// string comparison each. Keep the plugin install-state sidecar here too: it controls which
// packages load on startup and must remain host-managed. Single-sourced so install_hook() and
// the tests seed from the same list and cannot drift apart.
// string comparison each. Single-sourced here so install_hook() and the tests seed from the
// same list and cannot drift apart.
return {
SLIC3R_APP_KEY ".conf",
GCODEVIEWER_APP_KEY ".conf",
SLIC3R_APP_KEY ".ini",
GCODEVIEWER_APP_KEY ".ini",
secret_constants::USER_SECRET_FILENAME,
".install_state.json",
};
}
@@ -500,12 +498,6 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
candidate = absolute_candidate;
}
// Importing Python modules reads their source and may inspect package directories. These
// reads are normal interpreter work, not plugin data access; keep the exemption read-only
// and exclude third-party packages from the stdlib roots.
if (!is_write && is_stdlib_path(candidate))
return {true, ""};
// A root that doesn't allow writes only matches a read-shaped request; a write/create/
// delete-shaped one falls through to "outside allowed root" for that root even though the
// path is physically inside it.
@@ -1023,22 +1015,6 @@ void persist_grant(const std::string& plugin_key, AuditEventCategory category, b
static PluginAuditManager::PermissionPrompt s_permission_prompt_for_testing;
static const char* audit_category_name(AuditEventCategory category)
{
switch (category) {
case AuditEventCategory::FsRead: return "FsRead";
case AuditEventCategory::FsReadWrite: return "FsReadWrite";
case AuditEventCategory::FsCreate: return "FsCreate";
case AuditEventCategory::FsDelete: return "FsDelete";
case AuditEventCategory::Http: return "Http";
case AuditEventCategory::Socket: return "Socket";
case AuditEventCategory::ProcessCreate: return "ProcessCreate";
case AuditEventCategory::ProcessReplace: return "ProcessReplace";
case AuditEventCategory::Threading: return "Threading";
default: return "None";
}
}
// Asks the user to grant an audited event, returning true when they approve an inline prompt.
// `keys` are what a Yes saves: the unresolved targets, or the event-grant key when the event has
// no target (by_event), which the prompt does not show as a target.
@@ -1056,16 +1032,6 @@ bool request_permission(AuditEventCategory category, const std::string& plugin_k
const std::string& event_name, bool by_event, const std::vector<std::string>& keys)
{
const std::vector<std::string> shown = by_event ? std::vector<std::string>{} : keys;
std::string targets_text = by_event ? "<none>" : "";
for (const auto& target : shown) {
if (!targets_text.empty())
targets_text += ", ";
targets_text += target;
}
BOOST_LOG_TRIVIAL(info) << "[AUDIT PROMPT] plugin=" << plugin_key << " event=" << event_name
<< " category=" << audit_category_name(category) << " targets=[" << targets_text << "]"
<< (by_event && !keys.empty() ? " grant=" + keys.front() : std::string{});
if (s_permission_prompt_for_testing)
return s_permission_prompt_for_testing(event_name, shown);
@@ -1232,18 +1198,6 @@ bool PluginAuditManager::is_stdlib_file(const std::string& filename)
return it->second;
}
bool PluginAuditManager::is_stdlib_path(const boost::filesystem::path& path)
{
if (path.empty())
return false;
const auto inside_any = [&path](const std::vector<boost::filesystem::path>& dirs) {
return std::any_of(dirs.begin(), dirs.end(),
[&path](const boost::filesystem::path& dir) { return is_inside_allowed_root(path, dir); });
};
return inside_any(PluginAuditDetail::s_stdlib_dirs) && !inside_any(PluginAuditDetail::s_site_package_dirs);
}
void PluginAuditManager::release_frames(std::vector<PyObject*>& frames)
{
if (frames.empty())
@@ -1312,7 +1266,7 @@ int PluginAuditManager::audit_hook(const char* event, PyObject* args, void* user
void PluginAuditManager::install_hook()
{
// Identifies stdlib frames for operation roots and stdlib paths for read-only access.
// Tells the stdlib's own frames apart, for the operation-root frames (see record_root_frame).
PluginAuditDetail::collect_stdlib_dirs();
// data_dir() is the primary globally-allowed root during plugin execution: read+write. It
@@ -1329,9 +1283,8 @@ void PluginAuditManager::install_hook()
// "cert" denied-path keyword seeded below.
add_global_allowed_root(resources_dir(), /*allow_write=*/false);
// The user's app config and cloud credentials live directly inside data_dir(), and plugin
// install state lives beneath it, so the root just granted would otherwise expose them to
// plugins. Deny them by name.
// The user's app config and cloud credentials live directly inside data_dir(), so the
// root just granted would otherwise expose them to any plugin. Deny them by name.
//
// Seeded here rather than by each secret's owner because install_hook() runs during lazy
// interpreter init and therefore provably precedes any plugin bytecode, whereas
-5
View File
@@ -173,11 +173,6 @@ private:
static int audit_hook(const char* event, PyObject* args, void* user_data);
// True for paths inside the interpreter's stdlib, excluding site-packages. Unlike
// is_stdlib_file(), this query is not cached because filesystem paths may resolve through
// symlinks that can change.
static bool is_stdlib_path(const boost::filesystem::path& path);
// Drops the references in `frames`, keeping the caller's error and denial state intact.
void release_frames(std::vector<PyObject*>& frames);
+1 -48
View File
@@ -69,7 +69,7 @@ void seed_denied_keywords()
} // namespace
TEST_CASE("Plugin audit denies host-managed filenames anywhere", "[audit]")
TEST_CASE("Plugin audit denies app config and token filenames anywhere", "[audit]")
{
seed_denied_names();
const PluginAuditManager& mgr = PluginAuditManager::instance();
@@ -81,7 +81,6 @@ TEST_CASE("Plugin audit denies host-managed filenames anywhere", "[audit]")
CHECK(mgr.is_denied_filename(fs::path(SLIC3R_APP_KEY ".ini")));
CHECK(mgr.is_denied_filename(fs::path(GCODEVIEWER_APP_KEY ".ini")));
CHECK(mgr.is_denied_filename(fs::path(secret_constants::USER_SECRET_FILENAME)));
CHECK(mgr.is_denied_filename(fs::path(".install_state.json")));
}
SECTION("companions holding the same secrets are denied by the prefix rule")
@@ -137,7 +136,6 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
const fs::path conf = fs::path(data_dir()) / (SLIC3R_APP_KEY ".conf");
const fs::path token = fs::path(data_dir()) / secret_constants::USER_SECRET_FILENAME;
const fs::path install_state = fs::path(data_dir()) / "plugins" / "example" / ".install_state.json";
SECTION("a non-denied file inside the allowed root is writable (root really grants writes)")
{
@@ -178,17 +176,6 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
CHECK_FALSE(decision.allowed);
}
SECTION("plugin install state is blocked for reads and writes despite the allowed root")
{
AuditDecision read_decision = mgr.check_open(install_state.string(), "r");
CHECK_FALSE(read_decision.allowed);
CHECK(read_decision.reason == "denied filename");
AuditDecision write_decision = mgr.check_open(install_state.string(), "w");
CHECK_FALSE(write_decision.allowed);
CHECK(write_decision.reason == "denied filename");
}
SECTION("a traversal path resolving to the config is blocked")
{
const fs::path traversal = fs::path(data_dir()) / "plugins" / ".." / (SLIC3R_APP_KEY ".conf");
@@ -1132,37 +1119,3 @@ TEST_CASE("Stdlib code is recognized through a symlinked Python home", "[audit][
CHECK_FALSE(PluginAuditManager::is_stdlib_file((harness.outside.path() / "plugin.py").string()));
CHECK_FALSE(PluginAuditManager::is_stdlib_file("<string>"));
}
TEST_CASE("A plugin may read the CPython stdlib without a prompt, but not site-packages or for writing", "[audit][Python]")
{
AuditHarness harness;
if (!harness.initialized)
SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error());
harness.add_plugin(PLUGIN);
py::gil_scoped_acquire gil;
PythonSide python(harness);
const py::dict paths = py::module_::import("sysconfig").attr("get_paths")();
const fs::path stdlib = py::str(paths["stdlib"]).cast<std::string>();
const fs::path purelib = py::str(paths["purelib"]).cast<std::string>();
// os.py ships with every CPython installation. A site-packages path is third-party code even
// when the directory is empty: the prompt must come before the missing file is noticed.
const fs::path stdlib_file = stdlib / "os.py";
REQUIRE(fs::exists(stdlib_file));
python.globals["STDLIB_FILE"] = stdlib_file.string();
python.globals["SITE_PACKAGE_FILE"] = (purelib / "regression_probe.py").string();
python.run_as(PLUGIN, R"PY(
read_outcome = attempt(lambda: open(STDLIB_FILE).read())
write_outcome = attempt(lambda: open(STDLIB_FILE, "r+").close())
site_packages_outcome = attempt(lambda: open(SITE_PACKAGE_FILE).close())
)PY");
CHECK(python.str("read_outcome") == "ok");
CHECK(python.str("write_outcome") == "denied");
CHECK(python.str("site_packages_outcome") == "denied");
// Only the write and the site-packages read are asked about; the stdlib read is implicit.
const std::string write_prompt = "open: " + python.str("STDLIB_FILE") + "\n";
const std::string site_packages_prompt = "open: " + python.str("SITE_PACKAGE_FILE") + "\n";
CHECK(describe(harness.prompts) == write_prompt + site_packages_prompt);
}