master
c 210 lines 5.93 KB
Raw
1 #include <unistd.h>
2 #include <errno.h>
3 #include <sys/types.h>
4 #include <grp.h>
5 #include <pwd.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #include "config.h"
11
12 #ifdef HAVE_CAPABILITY
13 #include <sys/capability.h>
14 #endif
15
16 #define FALLBACK_USER "nobody"
17
18 void show_help() {
19 fprintf(stdout, "\n");
20 fprintf(stdout, "nd-run\n");
21 fprintf(stdout, "\n");
22 fprintf(stdout, "Copyright 2025 Netdata Inc.\n");
23 fprintf(stdout, "\n");
24 fprintf(stdout, "A helper to run a command as an unprivileged user without any extra privileges\n");
25 fprintf(stdout, "\n");
26 fprintf(stdout, "Defaults to running the command as '%s', but will fall back to '%s' if '%s' is not found on the system.\n", NETDATA_USER, FALLBACK_USER, NETDATA_USER);
27 fprintf(stdout, "\n");
28 fprintf(stdout, "If it's not possible to switch users, the command will run as the current user instead.\n");
29 #ifdef HAVE_CAPABILITY
30 fprintf(stdout, "\n");
31 fprintf(stdout, "Regardless of whether it switched users, all capabilities will be dropped.\n");
32 #endif
33 }
34
35 static void fatal(const char *msg) {
36 perror(msg);
37 exit(EXIT_FAILURE);
38 }
39
40 #ifdef HAVE_CAPABILITY
41 static void clear_caps() {
42 // Clear out all capabilities
43 //
44 // This does not require any special privileges since it is reducing
45 // the process’s privileges.
46 cap_t caps = cap_init();
47
48 if (caps == NULL) fatal("cap_init");
49
50 if (cap_clear(caps) == -1) {
51 cap_free(caps);
52 fatal("cap_clear");
53 }
54
55 if (cap_set_proc(caps) == -1) {
56 cap_free(caps);
57 fatal("cap_set_proc");
58 }
59
60 cap_free(caps);
61 }
62 #endif
63
64 static void set_env_var(const char *name, const char *value) {
65 // Set an environment variable if the specified value is not a NULL pointer.
66 char buf[64];
67
68 if (value == NULL) {
69 return;
70 }
71
72 if (setenv(name, value, 1) != 0) {
73 snprintf(buf, 64, "setenv %s", name);
74 perror(buf);
75 }
76 }
77
78 static void clean_environment(struct passwd *pw) {
79 // Explicitly scrub the environment, only passing on a few things
80 // we know are needed to make things work correctly.
81
82 // First, save copies of the environment variables we want to keep.
83 // We must copy them before clearing the environment, as getenv()
84 // returns pointers into the environment block which will be invalidated.
85 char *saved_path = NULL;
86 char *saved_tz = NULL;
87 char *saved_tzdir = NULL;
88 char *saved_tmpdir = NULL;
89 char *saved_pwd = NULL;
90
91 const char *tmp;
92 if ((tmp = getenv("PATH")) != NULL) {
93 saved_path = strdup(tmp);
94 if (!saved_path) fatal("strdup PATH");
95 }
96 if ((tmp = getenv("TZ")) != NULL) {
97 saved_tz = strdup(tmp);
98 if (!saved_tz) fatal("strdup TZ");
99 }
100 if ((tmp = getenv("TZDIR")) != NULL) {
101 saved_tzdir = strdup(tmp);
102 if (!saved_tzdir) fatal("strdup TZDIR");
103 }
104 if ((tmp = getenv("TMPDIR")) != NULL) {
105 saved_tmpdir = strdup(tmp);
106 if (!saved_tmpdir) fatal("strdup TMPDIR");
107 }
108 if ((tmp = getenv("PWD")) != NULL) {
109 saved_pwd = strdup(tmp);
110 if (!saved_pwd) fatal("strdup PWD");
111 }
112
113 // Now clear the environment
114 #ifdef HAVE_CLEARENV
115 clearenv();
116 #else
117 extern char **environ;
118 environ = NULL;
119 #endif
120
121 // Set the new environment with our saved values
122 set_env_var("USER", pw->pw_name);
123 set_env_var("LOGNAME", pw->pw_name);
124 set_env_var("HOME", pw->pw_dir);
125 set_env_var("SHELL", "/bin/sh"); // Ignore user default shell
126 set_env_var("LC_ALL", "C"); // Force C locale
127 set_env_var("PATH", saved_path);
128 set_env_var("PWD", saved_pwd);
129 set_env_var("TZ", saved_tz);
130 set_env_var("TZDIR", saved_tzdir);
131 set_env_var("TMPDIR", (saved_tmpdir == NULL) ? "/tmp" : saved_tmpdir); // Use a sane default for TMPDIR if it wasn't set.
132
133 // Free the saved copies
134 free(saved_path);
135 free(saved_tz);
136 free(saved_tzdir);
137 free(saved_tmpdir);
138 free(saved_pwd);
139 }
140
141 int main(int argc, char *argv[]) {
142 if (argc < 2) {
143 show_help();
144 return EXIT_FAILURE;
145 }
146
147 uid_t euid = geteuid();
148
149 struct passwd *pw = getpwnam(NETDATA_USER);
150 if (!pw) {
151 pw = getpwnam(FALLBACK_USER);
152 if (!pw) {
153 fprintf(stderr, "Fallback user '%s' not found either\n", FALLBACK_USER);
154 return EXIT_FAILURE;
155 }
156 }
157
158 if (euid != pw->pw_uid) {
159 // Set supplementary groups for this user (must be done before dropping privs)
160 if (initgroups(pw->pw_name, pw->pw_gid) != 0) {
161 if (euid == 0) {
162 if (setgroups(0, NULL) != 0) {
163 fatal("setgroups");
164 }
165 } else if (errno != EPERM) {
166 fatal("initgroups");
167 }
168 }
169
170 // Drop GID then UID. Prefer setres* when available to also drop saved IDs.
171 // Linux/BSD generally provide setresgid/setresuid; macOS does not.
172 #ifdef HAVE_SETRESGID
173 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
174 if (euid == 0 || errno != EPERM) {
175 fatal("setresgid");
176 }
177 }
178 #else
179 if (setgid(pw->pw_gid) != 0) {
180 if (euid == 0 || errno != EPERM) {
181 fatal("setgid");
182 }
183 }
184 #endif
185
186 #ifdef HAVE_SETRESUID
187 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
188 if (euid == 0 || errno != EPERM) {
189 fatal("setresuid");
190 }
191 }
192 #else
193 if (setuid(pw->pw_uid) != 0) {
194 if (euid == 0 || errno != EPERM) {
195 fatal("setuid");
196 }
197 }
198 #endif
199 }
200
201 #ifdef HAVE_CAPABILITY
202 clear_caps();
203 #endif
204
205 clean_environment(pw);
206
207 // Exec the requested command (replaces the current process on success)
208 execvp(argv[1], &argv[1]);
209 fatal("execvp"); // Only reached on error
210 }