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