From 7c8dc3e817b8334bc36392fb007984966dd733ea Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Tue, 21 Jul 2026 13:14:03 +1200 Subject: [PATCH 1/5] Mark ML controller non-dumpable before accepting commands Same-UID peers in the shared ES/ml-cpp pod could write /proc//mem and overwrite access@GOT so a spawn's access(path, X_OK) became dlopen(path, RTLD_LAZY) (elastic/security#12621). Call prctl(PR_SET_DUMPABLE, 0) after logging is up and before opening command pipes; verify with PR_GET_DUMPABLE and fail closed if the boundary cannot be established. No-op on non-Linux platforms where /proc//mem is not the concern. Co-authored-by: Cursor --- bin/controller/Main.cc | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bin/controller/Main.cc b/bin/controller/Main.cc index 61e43288c2..14096758c0 100644 --- a/bin/controller/Main.cc +++ b/bin/controller/Main.cc @@ -63,6 +63,36 @@ #include #include +#ifdef Linux +#include +#endif + +namespace { + +//! Prevent same-UID peers (e.g. a compromised pytorch_inference / JVM Attach +//! agent in the shared pod) from writing this process's memory via +//! /proc//mem. That path was used to overwrite access@GOT and turn a +//! controller spawn into dlopen (elastic/security#12621). Fail closed: if we +//! cannot establish the boundary, refuse to accept commands. +bool makeProcessNonDumpable() { +#ifdef Linux + if (::prctl(PR_SET_DUMPABLE, 0) != 0) { + LOG_ERROR(<< "prctl PR_SET_DUMPABLE(0) failed: " << std::strerror(errno)); + return false; + } + // Confirm the attribute stuck — a silent no-op would leave the process exposed. + if (::prctl(PR_GET_DUMPABLE) != 0) { + LOG_ERROR(<< "process remains dumpable after PR_SET_DUMPABLE(0)"); + return false; + } + return true; +#else + // /proc//mem same-UID writes are a Linux concern; no equivalent gate here. + return true; +#endif +} +} + int main(int argc, char** argv) { const std::string& defaultNamedPipePath{ml::core::CNamedPipeFactory::defaultPath()}; const std::string& progName{ml::core::CProgName::progName()}; @@ -122,6 +152,13 @@ int main(int argc, char** argv) { // statically links its own version library. LOG_INFO(<< ml::ver::CBuildInfo::fullInfo()); + // Harden against same-UID /proc//mem writes before accepting commands. + if (makeProcessNonDumpable() == false) { + LOG_FATAL(<< "Could not mark ML controller non-dumpable"); + cancellerThread.stop(); + return EXIT_FAILURE; + } + // Unlike other programs we DON'T reduce the process priority here, because // the controller is critical to the overall system. Also its resource // requirements should always be very low. From 88ade68d3bd13cdb99d4e5a9b118eecd50c7fb8f Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Tue, 21 Jul 2026 13:14:40 +1200 Subject: [PATCH 2/5] Add changelog for #3081 Co-authored-by: Cursor --- docs/changelog/3081.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/3081.yaml diff --git a/docs/changelog/3081.yaml b/docs/changelog/3081.yaml new file mode 100644 index 0000000000..06926697d9 --- /dev/null +++ b/docs/changelog/3081.yaml @@ -0,0 +1,5 @@ +area: Machine Learning +issues: [] +pr: 3081 +summary: Mark ML controller non-dumpable before accepting commands +type: bug From 3de96c9598fc7e7649ed6fb1364e74a90541ad63 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 24 Jul 2026 10:41:18 +1200 Subject: [PATCH 3/5] [ML] Improve PR_GET_DUMPABLE diagnostic in controller non-dumpable guard Distinguish a PR_GET_DUMPABLE syscall failure (-1) from the process still being dumpable, and log the actual value, per review feedback on #3081. Co-authored-by: Cursor --- bin/controller/Main.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bin/controller/Main.cc b/bin/controller/Main.cc index 14096758c0..e7bf7ecc74 100644 --- a/bin/controller/Main.cc +++ b/bin/controller/Main.cc @@ -81,8 +81,14 @@ bool makeProcessNonDumpable() { return false; } // Confirm the attribute stuck — a silent no-op would leave the process exposed. - if (::prctl(PR_GET_DUMPABLE) != 0) { - LOG_ERROR(<< "process remains dumpable after PR_SET_DUMPABLE(0)"); + const int dumpable{::prctl(PR_GET_DUMPABLE)}; + if (dumpable == -1) { + LOG_ERROR(<< "prctl PR_GET_DUMPABLE failed: " << std::strerror(errno)); + return false; + } + if (dumpable != 0) { + LOG_ERROR(<< "process remains dumpable (" << dumpable + << ") after PR_SET_DUMPABLE(0)"); return false; } return true; From 9b47f5eee027a327d3673472f3a605dbc29a1276 Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 24 Jul 2026 11:02:24 +1200 Subject: [PATCH 4/5] [ML] Remove public-to-private security-tracker references Co-authored-by: Cursor --- bin/controller/Main.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/controller/Main.cc b/bin/controller/Main.cc index e7bf7ecc74..1386b893a6 100644 --- a/bin/controller/Main.cc +++ b/bin/controller/Main.cc @@ -71,9 +71,9 @@ namespace { //! Prevent same-UID peers (e.g. a compromised pytorch_inference / JVM Attach //! agent in the shared pod) from writing this process's memory via -//! /proc//mem. That path was used to overwrite access@GOT and turn a -//! controller spawn into dlopen (elastic/security#12621). Fail closed: if we -//! cannot establish the boundary, refuse to accept commands. +//! /proc//mem. That path could be used to overwrite access@GOT and turn a +//! controller spawn into dlopen (privately reported security finding). Fail +//! closed: if we cannot establish the boundary, refuse to accept commands. bool makeProcessNonDumpable() { #ifdef Linux if (::prctl(PR_SET_DUMPABLE, 0) != 0) { From 388d6a9a79a73255ccf53401b357d0bfd47fb3ff Mon Sep 17 00:00:00 2001 From: Ed Savage Date: Fri, 24 Jul 2026 11:08:01 +1200 Subject: [PATCH 5/5] [ML] Fix clang-format in controller Main.cc Co-authored-by: Cursor --- bin/controller/Main.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/controller/Main.cc b/bin/controller/Main.cc index 1386b893a6..9a863f2429 100644 --- a/bin/controller/Main.cc +++ b/bin/controller/Main.cc @@ -87,8 +87,7 @@ bool makeProcessNonDumpable() { return false; } if (dumpable != 0) { - LOG_ERROR(<< "process remains dumpable (" << dumpable - << ") after PR_SET_DUMPABLE(0)"); + LOG_ERROR(<< "process remains dumpable (" << dumpable << ") after PR_SET_DUMPABLE(0)"); return false; } return true;