]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
nspawn: split out --uid= logic into nspawn-setuid.[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 <sys/types.h>
23 #include <unistd.h>
24 #include <grp.h>
25
26 #include "util.h"
27 #include "signal-util.h"
28 #include "mkdir.h"
29 #include "process-util.h"
30
31 #include "nspawn.h"
32 #include "nspawn-setuid.h"
33
34 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
35 int pipe_fds[2];
36 pid_t pid;
37
38 assert(database);
39 assert(key);
40 assert(rpid);
41
42 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
43 return log_error_errno(errno, "Failed to allocate pipe: %m");
44
45 pid = fork();
46 if (pid < 0)
47 return log_error_errno(errno, "Failed to fork getent child: %m");
48 else if (pid == 0) {
49 int nullfd;
50 char *empty_env = NULL;
51
52 if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0)
53 _exit(EXIT_FAILURE);
54
55 if (pipe_fds[0] > 2)
56 safe_close(pipe_fds[0]);
57 if (pipe_fds[1] > 2)
58 safe_close(pipe_fds[1]);
59
60 nullfd = open("/dev/null", O_RDWR);
61 if (nullfd < 0)
62 _exit(EXIT_FAILURE);
63
64 if (dup3(nullfd, STDIN_FILENO, 0) < 0)
65 _exit(EXIT_FAILURE);
66
67 if (dup3(nullfd, STDERR_FILENO, 0) < 0)
68 _exit(EXIT_FAILURE);
69
70 if (nullfd > 2)
71 safe_close(nullfd);
72
73 (void) reset_all_signal_handlers();
74 (void) reset_signal_mask();
75 close_all_fds(NULL, 0);
76
77 execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
78 execle("/bin/getent", "getent", database, key, NULL, &empty_env);
79 _exit(EXIT_FAILURE);
80 }
81
82 pipe_fds[1] = safe_close(pipe_fds[1]);
83
84 *rpid = pid;
85
86 return pipe_fds[0];
87 }
88
89 int change_uid_gid(const char *user, char **_home) {
90 char line[LINE_MAX], *x, *u, *g, *h;
91 const char *word, *state;
92 _cleanup_free_ uid_t *uids = NULL;
93 _cleanup_free_ char *home = NULL;
94 _cleanup_fclose_ FILE *f = NULL;
95 _cleanup_close_ int fd = -1;
96 unsigned n_uids = 0;
97 size_t sz = 0, l;
98 uid_t uid;
99 gid_t gid;
100 pid_t pid;
101 int r;
102
103 assert(_home);
104
105 if (!user || streq(user, "root") || streq(user, "0")) {
106 /* Reset everything fully to 0, just in case */
107
108 r = reset_uid_gid();
109 if (r < 0)
110 return log_error_errno(r, "Failed to become root: %m");
111
112 *_home = NULL;
113 return 0;
114 }
115
116 /* First, get user credentials */
117 fd = spawn_getent("passwd", user, &pid);
118 if (fd < 0)
119 return fd;
120
121 f = fdopen(fd, "r");
122 if (!f)
123 return log_oom();
124 fd = -1;
125
126 if (!fgets(line, sizeof(line), f)) {
127
128 if (!ferror(f)) {
129 log_error("Failed to resolve user %s.", user);
130 return -ESRCH;
131 }
132
133 log_error_errno(errno, "Failed to read from getent: %m");
134 return -errno;
135 }
136
137 truncate_nl(line);
138
139 wait_for_terminate_and_warn("getent passwd", pid, true);
140
141 x = strchr(line, ':');
142 if (!x) {
143 log_error("/etc/passwd entry has invalid user field.");
144 return -EIO;
145 }
146
147 u = strchr(x+1, ':');
148 if (!u) {
149 log_error("/etc/passwd entry has invalid password field.");
150 return -EIO;
151 }
152
153 u++;
154 g = strchr(u, ':');
155 if (!g) {
156 log_error("/etc/passwd entry has invalid UID field.");
157 return -EIO;
158 }
159
160 *g = 0;
161 g++;
162 x = strchr(g, ':');
163 if (!x) {
164 log_error("/etc/passwd entry has invalid GID field.");
165 return -EIO;
166 }
167
168 *x = 0;
169 h = strchr(x+1, ':');
170 if (!h) {
171 log_error("/etc/passwd entry has invalid GECOS field.");
172 return -EIO;
173 }
174
175 h++;
176 x = strchr(h, ':');
177 if (!x) {
178 log_error("/etc/passwd entry has invalid home directory field.");
179 return -EIO;
180 }
181
182 *x = 0;
183
184 r = parse_uid(u, &uid);
185 if (r < 0) {
186 log_error("Failed to parse UID of user.");
187 return -EIO;
188 }
189
190 r = parse_gid(g, &gid);
191 if (r < 0) {
192 log_error("Failed to parse GID of user.");
193 return -EIO;
194 }
195
196 home = strdup(h);
197 if (!home)
198 return log_oom();
199
200 /* Second, get group memberships */
201 fd = spawn_getent("initgroups", user, &pid);
202 if (fd < 0)
203 return fd;
204
205 fclose(f);
206 f = fdopen(fd, "r");
207 if (!f)
208 return log_oom();
209 fd = -1;
210
211 if (!fgets(line, sizeof(line), f)) {
212 if (!ferror(f)) {
213 log_error("Failed to resolve user %s.", user);
214 return -ESRCH;
215 }
216
217 log_error_errno(errno, "Failed to read from getent: %m");
218 return -errno;
219 }
220
221 truncate_nl(line);
222
223 wait_for_terminate_and_warn("getent initgroups", pid, true);
224
225 /* Skip over the username and subsequent separator whitespace */
226 x = line;
227 x += strcspn(x, WHITESPACE);
228 x += strspn(x, WHITESPACE);
229
230 FOREACH_WORD(word, l, x, state) {
231 char c[l+1];
232
233 memcpy(c, word, l);
234 c[l] = 0;
235
236 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
237 return log_oom();
238
239 r = parse_uid(c, &uids[n_uids++]);
240 if (r < 0) {
241 log_error("Failed to parse group data from getent.");
242 return -EIO;
243 }
244 }
245
246 r = mkdir_parents(home, 0775);
247 if (r < 0)
248 return log_error_errno(r, "Failed to make home root directory: %m");
249
250 r = mkdir_safe(home, 0755, uid, gid);
251 if (r < 0 && r != -EEXIST)
252 return log_error_errno(r, "Failed to make home directory: %m");
253
254 (void) fchown(STDIN_FILENO, uid, gid);
255 (void) fchown(STDOUT_FILENO, uid, gid);
256 (void) fchown(STDERR_FILENO, uid, gid);
257
258 if (setgroups(n_uids, uids) < 0)
259 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
260
261 if (setresgid(gid, gid, gid) < 0)
262 return log_error_errno(errno, "setregid() failed: %m");
263
264 if (setresuid(uid, uid, uid) < 0)
265 return log_error_errno(errno, "setreuid() failed: %m");
266
267 if (_home) {
268 *_home = home;
269 home = NULL;
270 }
271
272 return 0;
273 }