From 7e55a251d6ef8e9676572fb55df47f19ae4b2d09 Mon Sep 17 00:00:00 2001
From: Ed Wildgoose <ed+git@wildgooses.com>
Date: Fri, 26 Jul 2024 17:12:41 +0100
Subject: [PATCH 1/5] Propogate capabilities to child process

---
 c_src/exec_impl.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 51 insertions(+), 1 deletion(-)

diff --git a/c_src/exec_impl.cpp b/c_src/exec_impl.cpp
index 2f1b199..7ee8e84 100644
--- a/c_src/exec_impl.cpp
+++ b/c_src/exec_impl.cpp
@@ -364,6 +364,55 @@ static int getpty(int& fdmp, ei::StringBuffer<128>& err) {
     return 0;
 }
 
+//------------------------------------------------------------------------------
+void propagate_caps(ei::StringBuffer<128>& err)
+{
+#ifdef HAVE_CAP
+    cap_t caps;
+    cap_value_t cap_list[CAP_LAST_CAP + 1];
+    int num_caps=0;
+
+    // Get the current process capabilities
+    caps = cap_get_proc();
+    if (caps == NULL) {
+        err.write("error %d on cap_get_proc: %s\n", errno, strerror(errno));
+    }
+
+    // Get the bounding set of capabilities
+    cap_flag_value_t cap_value;
+    for (cap_value_t i = 0; i <= CAP_LAST_CAP; ++i) {
+        if (cap_get_flag(caps, i, CAP_PERMITTED, &cap_value) == -1) {
+            err.write("error %d on cap_get_flag: %s\n", errno, strerror(errno));
+        }
+        if (cap_value == CAP_SET) {
+            cap_list[num_caps++] = i;
+        }
+    }
+
+    // Set inheritable capabilities to all permitted capabilities
+    if (cap_set_flag(caps, CAP_INHERITABLE, num_caps, cap_list, CAP_SET) == -1) {
+        err.write("error %d on cap_set_flag: %s\n", errno, strerror(errno));
+    }
+
+    // Apply inheritable capabilities
+    if (cap_set_proc(caps) == -1) {
+        err.write("error %d on cap_set_proc: %s\n", errno, strerror(errno));
+    }
+
+    // Set ambient capabilities
+    for (size_t i = 0; i < num_caps; i++) {
+        if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap_list[i], 0, 0) == -1) {
+            err.write("error %d on PR_CAP_AMBIENT_RAISE: %s\n", errno, strerror(errno));
+        }
+    }
+
+    // Free the capability state
+    if (cap_free(caps) == -1) {
+        err.write("error %d on cap_free: %s\n", errno, strerror(errno));
+    }
+#endif
+}
+
 //------------------------------------------------------------------------------
 pid_t start_child(CmdOptions& op, std::string& error)
 {
@@ -489,7 +538,6 @@ pid_t start_child(CmdOptions& op, std::string& error)
     } else if (pid == 0) {
         // I am the child
         int r;
-        
         if (op.pty()) {
             int fds;
             char pts_name[256];
@@ -647,6 +695,8 @@ pid_t start_child(CmdOptions& op, std::string& error)
         }
         */
 
+        propagate_caps(err);
+
         const char* executable = op.executable().empty()
             ? argv[0] : op.executable().c_str();
 

From 29ed3bd8acccc7e4a2282fe963f84af2c4ea12d5 Mon Sep 17 00:00:00 2001
From: Serge Aleynikov <saleyn@gmail.com>
Date: Sun, 1 Sep 2024 15:12:37 -0400
Subject: [PATCH 2/5] Add error handling

---
 c_src/exec_impl.cpp | 56 ++++++++++++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/c_src/exec_impl.cpp b/c_src/exec_impl.cpp
index 7ee8e84..f8a4410 100644
--- a/c_src/exec_impl.cpp
+++ b/c_src/exec_impl.cpp
@@ -364,53 +364,64 @@ static int getpty(int& fdmp, ei::StringBuffer<128>& err) {
     return 0;
 }
 
+struct Caps {
+    Caps() : m_caps(cap_get_proc()) {}
+    ~Caps() { if (m_caps) cap_free(m_caps); }
+
+    bool         valid()        const { return !!m_caps;           }
+    cap_t        value()              { return m_caps;             }
+    void         add(cap_value_t cap) { m_cap_list.push_back(cap); }
+    cap_value_t* list()               { return m_cap_list.data();  }
+    size_t       size()         const { return m_cap_list.size();  }
+private:
+    cap_t m_caps;
+    std::vector<cap_value_t> m_cap_list;
+};
+
 //------------------------------------------------------------------------------
-void propagate_caps(ei::StringBuffer<128>& err)
+bool propagate_caps(ei::StringBuffer<128>& err)
 {
 #ifdef HAVE_CAP
-    cap_t caps;
-    cap_value_t cap_list[CAP_LAST_CAP + 1];
-    int num_caps=0;
+    Caps caps;
 
     // Get the current process capabilities
-    caps = cap_get_proc();
-    if (caps == NULL) {
+    if (!caps.valid()) {
         err.write("error %d on cap_get_proc: %s\n", errno, strerror(errno));
+        return false;
     }
 
     // Get the bounding set of capabilities
     cap_flag_value_t cap_value;
     for (cap_value_t i = 0; i <= CAP_LAST_CAP; ++i) {
-        if (cap_get_flag(caps, i, CAP_PERMITTED, &cap_value) == -1) {
+        if (cap_get_flag(caps.value(), i, CAP_PERMITTED, &cap_value) < 0) {
             err.write("error %d on cap_get_flag: %s\n", errno, strerror(errno));
+            return false;
         }
-        if (cap_value == CAP_SET) {
-            cap_list[num_caps++] = i;
-        }
+        if (cap_value == CAP_SET)
+            cap_list.add(i);
     }
 
     // Set inheritable capabilities to all permitted capabilities
-    if (cap_set_flag(caps, CAP_INHERITABLE, num_caps, cap_list, CAP_SET) == -1) {
+    if (cap_set_flag(caps.value(), CAP_INHERITABLE, caps.size(), caps.list(), CAP_SET) < 0) {
         err.write("error %d on cap_set_flag: %s\n", errno, strerror(errno));
+        return false;
     }
 
     // Apply inheritable capabilities
-    if (cap_set_proc(caps) == -1) {
+    if (cap_set_proc(caps.value()) < 0) {
         err.write("error %d on cap_set_proc: %s\n", errno, strerror(errno));
+        return false;
     }
 
     // Set ambient capabilities
-    for (size_t i = 0; i < num_caps; i++) {
-        if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap_list[i], 0, 0) == -1) {
-            err.write("error %d on PR_CAP_AMBIENT_RAISE: %s\n", errno, strerror(errno));
+    for (size_t i = 0; i < caps.size(); i++) {
+        if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, caps.list()[i], 0, 0) < 0) {
+            err.write("error %d on PR_CAP_AMBIENT_RAISE[%d]: %s\n", errno, i, strerror(errno));
+            return false;
         }
     }
-
-    // Free the capability state
-    if (cap_free(caps) == -1) {
-        err.write("error %d on cap_free: %s\n", errno, strerror(errno));
-    }
 #endif
+    return true;
 }
 
 //------------------------------------------------------------------------------
@@ -695,7 +706,10 @@ pid_t start_child(CmdOptions& op, std::string& error)
         }
         */
 
-        propagate_caps(err);
+        if (!propagate_caps(err)) {
+	    perror(err.c_str());
+	    exit(EXIT_FAILURE);
+	}
 
         const char* executable = op.executable().empty()
             ? argv[0] : op.executable().c_str();

From 3a46e2313a5e359b645783b6afbfc110c52cfef7 Mon Sep 17 00:00:00 2001
From: Serge Aleynikov <saleyn@gmail.com>
Date: Sun, 1 Sep 2024 15:19:29 -0400
Subject: [PATCH 3/5] Fix compilation

---
 c_src/exec_impl.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/c_src/exec_impl.cpp b/c_src/exec_impl.cpp
index f8a4410..b8687c9 100644
--- a/c_src/exec_impl.cpp
+++ b/c_src/exec_impl.cpp
@@ -364,6 +364,7 @@ static int getpty(int& fdmp, ei::StringBuffer<128>& err) {
     return 0;
 }
 
+#ifdef HAVE_CAP
 struct Caps {
     Caps() : m_caps(cap_get_proc()) {}
     ~Caps() { if (m_caps) cap_free(m_caps); }
@@ -377,6 +378,7 @@ struct Caps {
     cap_t m_caps;
     std::vector<cap_value_t> m_cap_list;
 };
+#endif
 
 //------------------------------------------------------------------------------
 bool propagate_caps(ei::StringBuffer<128>& err)

From 4bf68405361b2b36b30e7af66de7c0b95a19bed5 Mon Sep 17 00:00:00 2001
From: Serge Aleynikov <saleyn@gmail.com>
Date: Sun, 1 Sep 2024 15:56:14 -0400
Subject: [PATCH 4/5] Add names of capabilities

---
 c_src/exec_impl.cpp | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/c_src/exec_impl.cpp b/c_src/exec_impl.cpp
index b8687c9..96e062c 100644
--- a/c_src/exec_impl.cpp
+++ b/c_src/exec_impl.cpp
@@ -365,6 +365,47 @@ static int getpty(int& fdmp, ei::StringBuffer<128>& err) {
 }
 
 #ifdef HAVE_CAP
+// Placeholder for atom declaration
+// sed -n 's/^#define \(CAP_.*\) .*/\1/p' /usr/include/linux/capability.h | tr A-Z a-z | sed 's/cap_//'
+//
+const char *cap_name[CAP_LAST_CAP+1] = {
+    "chown",
+    "dac_override",
+    "dac_read_search",
+    "fowner",
+    "fsetid",
+    "kill",
+    "setgid",
+    "setuid",
+    "setpcap",
+    "linux_immutable",
+    "net_bind_service",
+    "net_broadcast",
+    "net_admin",
+    "net_raw",
+    "ipc_lock",
+    "ipc_owner",
+    "sys_module",
+    "sys_rawio",
+    "sys_chroot",
+    "sys_ptrace",
+    "sys_pacct",
+    "sys_admin",
+    "sys_boot",
+    "sys_nice",
+    "sys_resource",
+    "sys_time",
+    "sys_tty_config",
+    "mknod",
+    "lease",
+    "audit_write",
+    "audit_control",
+    "setfcap",
+    "mac_override",
+    "mac_admin",
+    "syslog"
+};
+
 struct Caps {
     Caps() : m_caps(cap_get_proc()) {}
     ~Caps() { if (m_caps) cap_free(m_caps); }

From 7188da8d1b7685c35275f44f365289b9378dcbb9 Mon Sep 17 00:00:00 2001
From: Serge Aleynikov <saleyn@gmail.com>
Date: Sun, 1 Sep 2024 21:52:00 -0400
Subject: [PATCH 5/5] Update capability names

---
 c_src/exec_impl.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/c_src/exec_impl.cpp b/c_src/exec_impl.cpp
index 96e062c..0f2977b 100644
--- a/c_src/exec_impl.cpp
+++ b/c_src/exec_impl.cpp
@@ -365,7 +365,7 @@ static int getpty(int& fdmp, ei::StringBuffer<128>& err) {
 }
 
 #ifdef HAVE_CAP
-// Placeholder for atom declaration
+// Placeholder for decoding capability names passed in the "run child" command
 // sed -n 's/^#define \(CAP_.*\) .*/\1/p' /usr/include/linux/capability.h | tr A-Z a-z | sed 's/cap_//'
 //
 const char *cap_name[CAP_LAST_CAP+1] = {
@@ -403,7 +403,9 @@ const char *cap_name[CAP_LAST_CAP+1] = {
     "setfcap",
     "mac_override",
     "mac_admin",
-    "syslog"
+    "syslog",
+    "wake_alarm",
+    "block_suspend",
 };
 
 struct Caps {