]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
process-util: add new FORK_DEATHSIG_SIGKILL flag, rename FORK_DEATHSIG → FORK_DEATHSI...
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <sys/types.h>
5 #include <unistd.h>
6
7 #include "alloc-util.h"
8 #include "constants.h"
9 #include "errno.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "mkdir.h"
13 #include "nspawn-setuid.h"
14 #include "process-util.h"
15 #include "signal-util.h"
16 #include "string-util.h"
17 #include "strv.h"
18 #include "user-util.h"
19
20 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
21 int pipe_fds[2], r;
22 pid_t pid;
23
24 assert(database);
25 assert(key);
26 assert(rpid);
27
28 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
29 return log_error_errno(errno, "Failed to allocate pipe: %m");
30
31 r = safe_fork_full("(getent)",
32 (int[]) { -EBADF, pipe_fds[1], -EBADF }, NULL, 0,
33 FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE,
34 &pid);
35 if (r < 0) {
36 safe_close_pair(pipe_fds);
37 return r;
38 }
39 if (r == 0) {
40 execle("/usr/bin/getent", "getent", database, key, NULL, &(char*[1]){});
41 execle("/bin/getent", "getent", database, key, NULL, &(char*[1]){});
42 _exit(EXIT_FAILURE);
43 }
44
45 pipe_fds[1] = safe_close(pipe_fds[1]);
46
47 *rpid = pid;
48
49 return pipe_fds[0];
50 }
51
52 int change_uid_gid_raw(
53 uid_t uid,
54 gid_t gid,
55 const gid_t *supplementary_gids,
56 size_t n_supplementary_gids,
57 bool chown_stdio) {
58
59 if (!uid_is_valid(uid))
60 uid = 0;
61 if (!gid_is_valid(gid))
62 gid = 0;
63
64 if (chown_stdio) {
65 (void) fchown(STDIN_FILENO, uid, gid);
66 (void) fchown(STDOUT_FILENO, uid, gid);
67 (void) fchown(STDERR_FILENO, uid, gid);
68 }
69
70 if (setgroups(n_supplementary_gids, supplementary_gids) < 0)
71 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
72
73 if (setresgid(gid, gid, gid) < 0)
74 return log_error_errno(errno, "setresgid() failed: %m");
75
76 if (setresuid(uid, uid, uid) < 0)
77 return log_error_errno(errno, "setresuid() failed: %m");
78
79 return 0;
80 }
81
82 int change_uid_gid(const char *user, bool chown_stdio, char **ret_home) {
83 char *x, *u, *g, *h;
84 _cleanup_free_ gid_t *gids = NULL;
85 _cleanup_free_ char *home = NULL, *line = NULL;
86 _cleanup_fclose_ FILE *f = NULL;
87 _cleanup_close_ int fd = -EBADF;
88 unsigned n_gids = 0;
89 uid_t uid;
90 gid_t gid;
91 pid_t pid;
92 int r;
93
94 assert(ret_home);
95
96 if (!user || STR_IN_SET(user, "root", "0")) {
97 /* Reset everything fully to 0, just in case */
98
99 r = reset_uid_gid();
100 if (r < 0)
101 return log_error_errno(r, "Failed to become root: %m");
102
103 *ret_home = NULL;
104 return 0;
105 }
106
107 /* First, get user credentials */
108 fd = spawn_getent("passwd", user, &pid);
109 if (fd < 0)
110 return fd;
111
112 f = take_fdopen(&fd, "r");
113 if (!f)
114 return log_oom();
115
116 r = read_line(f, LONG_LINE_MAX, &line);
117 if (r == 0)
118 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
119 "Failed to resolve user %s.", user);
120 if (r < 0)
121 return log_error_errno(r, "Failed to read from getent: %m");
122
123 (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
124
125 x = strchr(line, ':');
126 if (!x)
127 return log_error_errno(SYNTHETIC_ERRNO(EIO),
128 "/etc/passwd entry has invalid user field.");
129
130 u = strchr(x+1, ':');
131 if (!u)
132 return log_error_errno(SYNTHETIC_ERRNO(EIO),
133 "/etc/passwd entry has invalid password field.");
134
135 u++;
136 g = strchr(u, ':');
137 if (!g)
138 return log_error_errno(SYNTHETIC_ERRNO(EIO),
139 "/etc/passwd entry has invalid UID field.");
140
141 *g = 0;
142 g++;
143 x = strchr(g, ':');
144 if (!x)
145 return log_error_errno(SYNTHETIC_ERRNO(EIO),
146 "/etc/passwd entry has invalid GID field.");
147
148 *x = 0;
149 h = strchr(x+1, ':');
150 if (!h)
151 return log_error_errno(SYNTHETIC_ERRNO(EIO),
152 "/etc/passwd entry has invalid GECOS field.");
153
154 h++;
155 x = strchr(h, ':');
156 if (!x)
157 return log_error_errno(SYNTHETIC_ERRNO(EIO),
158 "/etc/passwd entry has invalid home directory field.");
159
160 *x = 0;
161
162 r = parse_uid(u, &uid);
163 if (r < 0)
164 return log_error_errno(SYNTHETIC_ERRNO(EIO),
165 "Failed to parse UID of user.");
166
167 r = parse_gid(g, &gid);
168 if (r < 0)
169 return log_error_errno(SYNTHETIC_ERRNO(EIO),
170 "Failed to parse GID of user.");
171
172 home = strdup(h);
173 if (!home)
174 return log_oom();
175
176 f = safe_fclose(f);
177 line = mfree(line);
178
179 /* Second, get group memberships */
180 fd = spawn_getent("initgroups", user, &pid);
181 if (fd < 0)
182 return fd;
183
184 f = take_fdopen(&fd, "r");
185 if (!f)
186 return log_oom();
187
188 r = read_line(f, LONG_LINE_MAX, &line);
189 if (r == 0)
190 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
191 "Failed to resolve user %s.", user);
192 if (r < 0)
193 return log_error_errno(r, "Failed to read from getent: %m");
194
195 (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
196
197 /* Skip over the username and subsequent separator whitespace */
198 x = line;
199 x += strcspn(x, WHITESPACE);
200 x += strspn(x, WHITESPACE);
201
202 for (const char *p = x;;) {
203 _cleanup_free_ char *word = NULL;
204
205 r = extract_first_word(&p, &word, NULL, 0);
206 if (r < 0)
207 return log_error_errno(r, "Failed to parse group data from getent: %m");
208 if (r == 0)
209 break;
210
211 if (!GREEDY_REALLOC(gids, n_gids+1))
212 return log_oom();
213
214 r = parse_gid(word, &gids[n_gids++]);
215 if (r < 0)
216 return log_error_errno(r, "Failed to parse group data from getent: %m");
217 }
218
219 r = mkdir_parents(home, 0775);
220 if (r < 0)
221 return log_error_errno(r, "Failed to make home root directory: %m");
222
223 r = mkdir_safe(home, 0755, uid, gid, 0);
224 if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR))
225 return log_error_errno(r, "Failed to make home directory: %m");
226
227 r = change_uid_gid_raw(uid, gid, gids, n_gids, chown_stdio);
228 if (r < 0)
229 return r;
230
231 if (ret_home)
232 *ret_home = TAKE_PTR(home);
233
234 return 0;
235 }