]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
util-lib: split string parsing related calls from util.[ch] into parse-util.[ch]
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <grp.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "fd-util.h"
27 #include "mkdir.h"
28 #include "nspawn-setuid.h"
29 #include "process-util.h"
30 #include "signal-util.h"
31 #include "string-util.h"
32 #include "user-util.h"
33 #include "util.h"
34
35 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
36 int pipe_fds[2];
37 pid_t pid;
38
39 assert(database);
40 assert(key);
41 assert(rpid);
42
43 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
44 return log_error_errno(errno, "Failed to allocate pipe: %m");
45
46 pid = fork();
47 if (pid < 0)
48 return log_error_errno(errno, "Failed to fork getent child: %m");
49 else if (pid == 0) {
50 int nullfd;
51 char *empty_env = NULL;
52
53 if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0)
54 _exit(EXIT_FAILURE);
55
56 if (pipe_fds[0] > 2)
57 safe_close(pipe_fds[0]);
58 if (pipe_fds[1] > 2)
59 safe_close(pipe_fds[1]);
60
61 nullfd = open("/dev/null", O_RDWR);
62 if (nullfd < 0)
63 _exit(EXIT_FAILURE);
64
65 if (dup3(nullfd, STDIN_FILENO, 0) < 0)
66 _exit(EXIT_FAILURE);
67
68 if (dup3(nullfd, STDERR_FILENO, 0) < 0)
69 _exit(EXIT_FAILURE);
70
71 if (nullfd > 2)
72 safe_close(nullfd);
73
74 (void) reset_all_signal_handlers();
75 (void) reset_signal_mask();
76 close_all_fds(NULL, 0);
77
78 execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
79 execle("/bin/getent", "getent", database, key, NULL, &empty_env);
80 _exit(EXIT_FAILURE);
81 }
82
83 pipe_fds[1] = safe_close(pipe_fds[1]);
84
85 *rpid = pid;
86
87 return pipe_fds[0];
88 }
89
90 int change_uid_gid(const char *user, char **_home) {
91 char line[LINE_MAX], *x, *u, *g, *h;
92 const char *word, *state;
93 _cleanup_free_ uid_t *uids = NULL;
94 _cleanup_free_ char *home = NULL;
95 _cleanup_fclose_ FILE *f = NULL;
96 _cleanup_close_ int fd = -1;
97 unsigned n_uids = 0;
98 size_t sz = 0, l;
99 uid_t uid;
100 gid_t gid;
101 pid_t pid;
102 int r;
103
104 assert(_home);
105
106 if (!user || streq(user, "root") || streq(user, "0")) {
107 /* Reset everything fully to 0, just in case */
108
109 r = reset_uid_gid();
110 if (r < 0)
111 return log_error_errno(r, "Failed to become root: %m");
112
113 *_home = NULL;
114 return 0;
115 }
116
117 /* First, get user credentials */
118 fd = spawn_getent("passwd", user, &pid);
119 if (fd < 0)
120 return fd;
121
122 f = fdopen(fd, "r");
123 if (!f)
124 return log_oom();
125 fd = -1;
126
127 if (!fgets(line, sizeof(line), f)) {
128
129 if (!ferror(f)) {
130 log_error("Failed to resolve user %s.", user);
131 return -ESRCH;
132 }
133
134 log_error_errno(errno, "Failed to read from getent: %m");
135 return -errno;
136 }
137
138 truncate_nl(line);
139
140 wait_for_terminate_and_warn("getent passwd", pid, true);
141
142 x = strchr(line, ':');
143 if (!x) {
144 log_error("/etc/passwd entry has invalid user field.");
145 return -EIO;
146 }
147
148 u = strchr(x+1, ':');
149 if (!u) {
150 log_error("/etc/passwd entry has invalid password field.");
151 return -EIO;
152 }
153
154 u++;
155 g = strchr(u, ':');
156 if (!g) {
157 log_error("/etc/passwd entry has invalid UID field.");
158 return -EIO;
159 }
160
161 *g = 0;
162 g++;
163 x = strchr(g, ':');
164 if (!x) {
165 log_error("/etc/passwd entry has invalid GID field.");
166 return -EIO;
167 }
168
169 *x = 0;
170 h = strchr(x+1, ':');
171 if (!h) {
172 log_error("/etc/passwd entry has invalid GECOS field.");
173 return -EIO;
174 }
175
176 h++;
177 x = strchr(h, ':');
178 if (!x) {
179 log_error("/etc/passwd entry has invalid home directory field.");
180 return -EIO;
181 }
182
183 *x = 0;
184
185 r = parse_uid(u, &uid);
186 if (r < 0) {
187 log_error("Failed to parse UID of user.");
188 return -EIO;
189 }
190
191 r = parse_gid(g, &gid);
192 if (r < 0) {
193 log_error("Failed to parse GID of user.");
194 return -EIO;
195 }
196
197 home = strdup(h);
198 if (!home)
199 return log_oom();
200
201 /* Second, get group memberships */
202 fd = spawn_getent("initgroups", user, &pid);
203 if (fd < 0)
204 return fd;
205
206 fclose(f);
207 f = fdopen(fd, "r");
208 if (!f)
209 return log_oom();
210 fd = -1;
211
212 if (!fgets(line, sizeof(line), f)) {
213 if (!ferror(f)) {
214 log_error("Failed to resolve user %s.", user);
215 return -ESRCH;
216 }
217
218 log_error_errno(errno, "Failed to read from getent: %m");
219 return -errno;
220 }
221
222 truncate_nl(line);
223
224 wait_for_terminate_and_warn("getent initgroups", pid, true);
225
226 /* Skip over the username and subsequent separator whitespace */
227 x = line;
228 x += strcspn(x, WHITESPACE);
229 x += strspn(x, WHITESPACE);
230
231 FOREACH_WORD(word, l, x, state) {
232 char c[l+1];
233
234 memcpy(c, word, l);
235 c[l] = 0;
236
237 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
238 return log_oom();
239
240 r = parse_uid(c, &uids[n_uids++]);
241 if (r < 0) {
242 log_error("Failed to parse group data from getent.");
243 return -EIO;
244 }
245 }
246
247 r = mkdir_parents(home, 0775);
248 if (r < 0)
249 return log_error_errno(r, "Failed to make home root directory: %m");
250
251 r = mkdir_safe(home, 0755, uid, gid);
252 if (r < 0 && r != -EEXIST)
253 return log_error_errno(r, "Failed to make home directory: %m");
254
255 (void) fchown(STDIN_FILENO, uid, gid);
256 (void) fchown(STDOUT_FILENO, uid, gid);
257 (void) fchown(STDERR_FILENO, uid, gid);
258
259 if (setgroups(n_uids, uids) < 0)
260 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
261
262 if (setresgid(gid, gid, gid) < 0)
263 return log_error_errno(errno, "setregid() failed: %m");
264
265 if (setresuid(uid, uid, uid) < 0)
266 return log_error_errno(errno, "setreuid() failed: %m");
267
268 if (_home) {
269 *_home = home;
270 home = NULL;
271 }
272
273 return 0;
274 }