From: Michael Tremer Date: Tue, 24 Feb 2026 11:37:17 +0000 (+0000) Subject: daemon: Drop privileges after start X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7bde54836af6e79792cb21e84a2aeb291ca289b1;p=telemetry.git daemon: Drop privileges after start Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 00ba6ae..0f9466c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -166,6 +166,8 @@ dist_telemetryd_SOURCES = \ src/daemon/metrics.h \ src/daemon/parse.c \ src/daemon/parse.h \ + src/daemon/priv.c \ + src/daemon/priv.h \ src/daemon/proc.c \ src/daemon/proc.h \ src/daemon/proto.c \ diff --git a/src/daemon/main.c b/src/daemon/main.c index 16129e1..70270da 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -27,6 +27,7 @@ #include "ctx.h" #include "daemon.h" #include "logging.h" +#include "priv.h" #include "string.h" const char* argp_program_version = PACKAGE_VERSION; @@ -98,6 +99,11 @@ int main(int argc, char* argv[]) { if (r) goto ERROR; + // Drop privileges + r = td_priv_drop_privileges(ctx, user); + if (r < 0) + goto ERROR; + // Create a daemon r = td_daemon_create(&daemon, ctx, sources); if (r < 0) { diff --git a/src/daemon/priv.c b/src/daemon/priv.c new file mode 100644 index 0000000..f033b4e --- /dev/null +++ b/src/daemon/priv.c @@ -0,0 +1,214 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2026 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "ctx.h" +#include "priv.h" + +static int td_priv_cap_acquire_setpcap(td_ctx* ctx) { + cap_flag_value_t value; + int r; + + // Fetch current capabilities + cap_t caps = cap_get_proc(); + + // Check if CAP_SETPCAP is already enabled + r = cap_get_flag(caps, CAP_SETPCAP, CAP_EFFECTIVE, &value); + if (r) { + ERROR(ctx, "The kernel does not seem to know CAP_SETPCAP: %m\n"); + goto ERROR; + } + + // It CAP_SETPCAP isn't set, we will try to set it + if (value != CAP_SET) { + const cap_value_t cap = CAP_SETPCAP; + + r = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET); + if (r) { + ERROR(ctx, "Could not set CAP_SETPCAP: %m\n"); + goto ERROR; + } + + // Store capabilities + r = cap_set_proc(caps); + if (r) { + ERROR(ctx, "Could not acquire effective CAP_SETPCAP capability: %m\n"); + goto ERROR; + } + } + +ERROR: + if (caps) + cap_free(caps); + + return r; +} + +static cap_flag_value_t td_priv_keep_cap(const cap_value_t cap, const cap_value_t* keep_caps) { + for (const cap_value_t* c = keep_caps; *c; c++) { + if (cap == *c) + return CAP_SET; + } + + return CAP_CLEAR; +} + +static int td_priv_drop_capabilities(td_ctx* ctx) { + int r; + + const cap_value_t keep_caps[] = { + CAP_DAC_OVERRIDE, + CAP_NET_ADMIN, + CAP_NET_RAW, + CAP_SYS_ADMIN, + 0, + }; + + // Acquire CAP_SETPCAP + r = td_priv_cap_acquire_setpcap(ctx); + if (r) + return r; + + // Fetch the current set of capabilities + cap_t caps = cap_get_proc(); + + // Drop all capabilities that we do not need + for (cap_value_t cap = 1; cap <= CAP_LAST_CAP; cap++) { + // Skip any capabilities we would like to skip + cap_flag_value_t flag = td_priv_keep_cap(cap, keep_caps); + + // Drop the capability from the bounding set + if (flag == CAP_CLEAR) { + r = prctl(PR_CAPBSET_DROP, cap); + if (r) { + ERROR(ctx, "Could not drop capability from the bounding set: %m\n"); + goto ERROR; + } + } + + r = cap_set_flag(caps, CAP_INHERITABLE, 1, &cap, CAP_CLEAR); + if (r) { + ERROR(ctx, "Could not set capability %d: %m\n", (int)cap); + goto ERROR; + } + + r = cap_set_flag(caps, CAP_PERMITTED, 1, &cap, flag); + if (r) { + ERROR(ctx, "Could not set capability %d: %m\n", (int)cap); + goto ERROR; + } + + r = cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, flag); + if (r) { + ERROR(ctx, "Could not set capability %d: %m\n", (int)cap); + goto ERROR; + } + } + + // Restore capabilities + r = cap_set_proc(caps); + if (r) { + ERROR(ctx, "Could not restore capabilities: %m\n"); + goto ERROR; + } + +ERROR: + if (caps) + cap_free(caps); + + return r; +} + +int td_priv_drop_privileges(td_ctx* ctx, const char* user) { + struct passwd* passwd = NULL; + int r; + + // Fetch the current user + uid_t current_uid = getuid(); + + // If we have not been launched by root, we will assume that we have already + // been launched with a minimal set of privileges. + if (current_uid > 0) + return 0; + + // Log action + DEBUG(ctx, "Dropping privileges...\n"); + + // Fetch information about the desired user + passwd = getpwnam(user); + if (!passwd) { + ERROR(ctx, "Could not find user %s: %m\n", user); + return -errno; + } + + uid_t uid = passwd->pw_uid; + gid_t gid = passwd->pw_gid; + + DEBUG(ctx, "Changing to user '%s', uid = %ld, gid = %ld\n", user, uid, gid); + + // Change group + r = setresgid(gid, gid, gid); + if (r) { + ERROR(ctx, "Could not change group to %d: %m\n", gid); + return -errno; + } + + // Set any supplementary groups + r = setgroups(0, NULL); + if (r) { + ERROR(ctx, "Could not set supplementary groups: %m\n"); + return -errno; + } + + // Do not drop any capabilities when we change to the new user + r = prctl(PR_SET_KEEPCAPS, 1); + if (r) { + ERROR(ctx, "Could not set PR_SET_KEEPCAPS: %m\n"); + return -errno; + } + + // Change to the new user + r = setresuid(uid, uid, uid); + if (r) { + ERROR(ctx, "Could not change user to %d: %m\n", uid); + return -errno; + } + + // Reset PR_SET_KEEPCAPS + r = prctl(PR_SET_KEEPCAPS, 0); + if (r) { + ERROR(ctx, "Could not set PR_SET_KEEPCAPS: %m\n"); + return -errno; + } + + // Drop capabilities + r = td_priv_drop_capabilities(ctx); + if (r < 0) + return r; + + return 0; +} diff --git a/src/daemon/priv.h b/src/daemon/priv.h new file mode 100644 index 0000000..609fc76 --- /dev/null +++ b/src/daemon/priv.h @@ -0,0 +1,28 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2026 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#ifndef TELEMETRY_PRIV_H +#define TELEMETRY_PRIV_H + +#include "ctx.h" + +int td_priv_drop_privileges(td_ctx* ctx, const char* user); + +#endif /* TELEMETRY_PRIV_H */