]> git.ipfire.org Git - telemetry.git/commitdiff
daemon: Further limit capabilities of commands
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 24 Feb 2026 16:27:49 +0000 (16:27 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 24 Feb 2026 16:27:49 +0000 (16:27 +0000)
Commands must have their capabilities set if they require any. Otherwise
they will be running as an unprivileged user without any capabilities.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/daemon/command.c
src/daemon/command.h
src/daemon/source.c
src/daemon/source.h
src/daemon/sources/hostapd.c
src/daemon/sources/legacy-gateway-latency4.c
src/daemon/sources/suricata.c
src/daemon/sources/unbound.c

index 9c01c81f19bc775d98a7f485b069612820b71218..372b93f81a0911dd2164a3b4938beacedb04c363 100644 (file)
@@ -26,6 +26,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <syscall.h>
+#include <sys/capability.h>
 #include <sys/prctl.h>
 #include <unistd.h>
 
@@ -39,6 +40,8 @@
 // By default, commands are being killed after 30 seconds
 #define DEFAULT_TIMEOUT        SEC_TO_USEC(30)
 
+#define MAX_CAPS 8
+
 typedef struct td_command_output {
        // Pipes
        int pipes[2];
@@ -60,6 +63,10 @@ struct td_command {
        // Timeout
        uint64_t timeout;
 
+       // Capabilities
+       cap_value_t caps[MAX_CAPS];
+       unsigned int num_caps;
+
        // pidfd
        int pidfd;
 
@@ -226,6 +233,35 @@ void td_command_on_success(td_command* self,
        self->callbacks.on_success_data = data;
 }
 
+// Capabilities
+static int td_command_require_cap(td_command* self, const cap_value_t cap) {
+       // Check if we have space
+       if (self->num_caps >= MAX_CAPS)
+               return -ENOSPC;
+
+       // Stop the capability
+       self->caps[self->num_caps++] = cap;
+
+       return 0;
+}
+
+int td_command_require_caps(td_command* self, const cap_value_t* caps) {
+       int r;
+
+       // Check input
+       if (!caps)
+               return -EINVAL;
+
+       // Apply all caps
+       for (const cap_value_t* cap = caps; *cap; cap++) {
+               r = td_command_require_cap(self, *cap);
+               if (r < 0)
+                       return r;
+       }
+
+       return 0;
+}
+
 static int td_command_write(td_command* self,
                int fd, unsigned int events, td_command_output* output) {
        ssize_t bytes_read = 0;
@@ -426,6 +462,89 @@ static int td_command_timeout(sd_event_source* source, uint64_t usec, void* data
        return 0;
 }
 
+static int td_command_drop_caps(td_command* self) {
+       cap_t caps = NULL;
+       int r;
+
+       // Don't do anything if no caps have been required
+       if (!self->num_caps)
+               return 0;
+
+       // Create a new set of capabilities
+       caps = cap_init();
+       if (!caps) {
+               ERROR(self->ctx, "Failed to create a new set of capabilities: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Set permitted caps
+       r = cap_set_flag(caps, CAP_PERMITTED, self->num_caps, self->caps, CAP_SET);
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to set permitted caps: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Set effective caps
+       r = cap_set_flag(caps, CAP_EFFECTIVE, self->num_caps, self->caps, CAP_SET);
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to set effective caps: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Set inheritable caps
+       r = cap_set_flag(caps, CAP_INHERITABLE, self->num_caps, self->caps, CAP_SET);
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to set inheritable caps: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+       // Set the caps
+       r = cap_set_proc(caps);
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to set caps: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
+ERROR:
+       if (caps)
+               cap_free(caps);
+
+       return r;
+}
+
+static int td_command_set_caps(td_command* self) {
+       int r;
+
+       // Drop all capabilities
+       r = td_command_drop_caps(self);
+       if (r < 0)
+               return r;
+
+       // Clear ambient caps
+       r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
+       if (r < 0) {
+               ERROR(self->ctx, "Failed to clear ambient caps: %m\n");
+               return -errno;
+       }
+
+       // Now, re-raise ambient caps,
+       // so that the executed command will have the correct capabilities
+       for (unsigned int i = 0; i < self->num_caps; i++) {
+               r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, self->caps[i], 0, 0);
+               if (r < 0) {
+                       ERROR(self->ctx, "Failed to raise ambient cap %d: %m\n", self->caps[i]);
+                       return -errno;
+               }
+       }
+
+       return 0;
+}
+
 static int td_command_parent(td_command* self) {
        int fd = -EBADF;
        int r;
@@ -521,7 +640,11 @@ static int td_command_child(td_command* self, const char** argv) {
        td_command_close_pipe(self->stdout.pipes);
        td_command_close_pipe(self->stderr.pipes);
 
-       // Execute the command
+       // Set capabilities
+       r = td_command_set_caps(self);
+       ERROR(self->ctx, "CAPS %d\n", r);
+       if (r < 0)
+               return r;
 
        // Execute the command
        r = execvp(argv[0], (char**)argv);
index 30730a4ae22e8be3ea189c541bfbf23d5d8b4533..054ebb090148df4116e20df3f324fe34526fdad3 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef TELEMETRY_COMMAND_H
 #define TELEMETRY_COMMAND_H
 
+#include <sys/capability.h>
+
 typedef struct td_command td_command;
 
 #include "ctx.h"
@@ -44,6 +46,9 @@ void td_command_set_timeout(td_command* self, uint64_t timeout);
 void td_command_on_success(td_command* self,
        td_command_success_callback callback, void* data);
 
+// Capabilities
+int td_command_require_caps(td_command* self, const cap_value_t* caps);
+
 int td_command_execute(td_command* self, const char** argv);
 
 #endif /* TELEMETRY_COMMAND_H */
index 678ca5b2879ad3ccd6983046a9f0a653dbd46d6b..6206be4c9f4f2d58c49e46d5a432855837008e29 100644 (file)
@@ -573,7 +573,7 @@ int td_source_create_command(td_source* self, td_command** command) {
        return td_command_create(command, self->ctx, self->daemon);
 }
 
-int td_source_run_command(td_source* self, const char** argv,
+int td_source_run_command(td_source* self, const cap_value_t* caps, const char** argv,
                td_command_success_callback callback, void* data) {
        td_command* command = NULL;
        int r;
@@ -583,6 +583,13 @@ int td_source_run_command(td_source* self, const char** argv,
        if (r < 0)
                goto ERROR;
 
+       // Set capabilities
+       if (caps) {
+               r = td_command_require_caps(command, caps);
+               if (r < 0)
+                       goto ERROR;
+       }
+
        // Set the callback
        td_command_on_success(command, callback, data);
 
index 9df02fcb06b6b37513e595ce7778db9a7d5e84b7..5f9d35278f587281df888f5a920fd3b3545ddf3d 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef TELEMETRY_SOURCE_H
 #define TELEMETRY_SOURCE_H
 
+#include <sys/capability.h>
+
 #include <libudev.h>
 
 #ifdef HAVE_LIBNL3
@@ -103,7 +105,7 @@ const td_rrd_ds* td_source_get_data_sources(td_source* self);
 int td_source_create_metrics(td_source* self, td_metrics** metrics, const char* object);
 
 int td_source_create_command(td_source* self, td_command** command);
-int td_source_run_command(td_source* self, const char** argv,
+int td_source_run_command(td_source* self, const cap_value_t* caps, const char** argv,
        td_command_success_callback callback, void* data);
 
 int td_source_parse_metrics(td_source* self, const char* object,
index 5cad5cf14bf4d66aefa4f0ba66dc37caebaf7bd2..cd5c78d302c56058a772b474f30af66567d9eaca 100644 (file)
@@ -217,7 +217,7 @@ static int hostapd_heartbeat(td_ctx* ctx, td_source* source) {
                "hostapd_cli", "all_sta", NULL,
        };
 
-       return td_source_run_command(source, argv, hostapd_on_success, source);
+       return td_source_run_command(source, NULL, argv, hostapd_on_success, source);
 }
 
 const td_source_impl hostapd_source = {
index 72321b9aa97357c7e1457fdd0965b4a3fe7d4bd6..ce40fdc754b5a92aa1aeb16dcbee95bc49d911e4 100644 (file)
 #include "../time.h"
 #include "legacy-gateway-latency4.h"
 
+static const cap_value_t caps[] = {
+       CAP_NET_ADMIN,
+       CAP_NET_RAW,
+       0,
+};
+
 static int fetch_default_gateway(td_ctx* ctx, td_source* source,
                char* address, size_t length, unsigned int* type) {
        struct nl_cache* routes = NULL;
@@ -173,7 +179,7 @@ static int do_arping(td_ctx* ctx, td_source* source, const char* address) {
                NULL,
        };
 
-       return td_source_run_command(source, argv, legacy_gateway_latency_on_success, source);
+       return td_source_run_command(source, caps, argv, legacy_gateway_latency_on_success, source);
 }
 
 static int do_ping(td_ctx* ctx, td_source* source, const char* address) {
@@ -195,7 +201,7 @@ static int do_ping(td_ctx* ctx, td_source* source, const char* address) {
                NULL,
        };
 
-       return td_source_run_command(source, argv, legacy_gateway_latency_on_success, source);
+       return td_source_run_command(source, caps, argv, legacy_gateway_latency_on_success, source);
 }
 
 static int legacy_gateway_latency4_heartbeat(td_ctx* ctx, td_source* source) {
index 93a3980cb7f06045bafeaa7b44da5690111ac5b3..95ccdb8548d8520e99686825444669fe5fb5ac11 100644 (file)
@@ -177,7 +177,7 @@ static int suricata_heartbeat(td_ctx* ctx, td_source* source) {
                "suricatasc", "--command=dump-counters", NULL,
        };
 
-       return td_source_run_command(source, argv, suricata_on_success, source);
+       return td_source_run_command(source, NULL, argv, suricata_on_success, source);
 }
 
 const td_source_impl suricata_source = {
index a78af54f219398770780c430960f1d5d8fdca177..f89861cea33d81b7943519014ec38f1b5f19b741 100644 (file)
@@ -119,7 +119,7 @@ static int unbound_heartbeat(td_ctx* ctx, td_source* source) {
                "unbound-control", "stats_noreset", NULL,
        };
 
-       return td_source_run_command(source, argv, unbound_on_success, source);
+       return td_source_run_command(source, NULL, argv, unbound_on_success, source);
 }
 
 const td_source_impl unbound_source = {