]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
nspawn: port some code to use read_line()
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <grp.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24
25 #include "alloc-util.h"
26 #include "def.h"
27 #include "errno.h"
28 #include "fd-util.h"
29 #include "fileio.h"
30 #include "mkdir.h"
31 #include "nspawn-setuid.h"
32 #include "process-util.h"
33 #include "signal-util.h"
34 #include "string-util.h"
35 #include "user-util.h"
36 #include "util.h"
37
38 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
39 int pipe_fds[2], r;
40 pid_t pid;
41
42 assert(database);
43 assert(key);
44 assert(rpid);
45
46 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
47 return log_error_errno(errno, "Failed to allocate pipe: %m");
48
49 r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
50 if (r < 0)
51 return r;
52 if (r == 0) {
53 int nullfd;
54 char *empty_env = NULL;
55
56 if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0)
57 _exit(EXIT_FAILURE);
58
59 if (pipe_fds[0] > 2)
60 safe_close(pipe_fds[0]);
61 if (pipe_fds[1] > 2)
62 safe_close(pipe_fds[1]);
63
64 nullfd = open("/dev/null", O_RDWR);
65 if (nullfd < 0)
66 _exit(EXIT_FAILURE);
67
68 if (dup3(nullfd, STDIN_FILENO, 0) < 0)
69 _exit(EXIT_FAILURE);
70
71 if (dup3(nullfd, STDERR_FILENO, 0) < 0)
72 _exit(EXIT_FAILURE);
73
74 if (nullfd > 2)
75 safe_close(nullfd);
76
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 *x, *u, *g, *h;
93 const char *word, *state;
94 _cleanup_free_ uid_t *uids = NULL;
95 _cleanup_free_ char *home = NULL, *line = 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, "re");
124 if (!f)
125 return log_oom();
126 fd = -1;
127
128 r = read_line(f, LONG_LINE_MAX, &line);
129 if (r == 0) {
130 log_error("Failed to resolve user %s.", user);
131 return -ESRCH;
132 }
133 if (r < 0)
134 return log_error_errno(r, "Failed to read from getent: %m");
135
136 (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
137
138 x = strchr(line, ':');
139 if (!x) {
140 log_error("/etc/passwd entry has invalid user field.");
141 return -EIO;
142 }
143
144 u = strchr(x+1, ':');
145 if (!u) {
146 log_error("/etc/passwd entry has invalid password field.");
147 return -EIO;
148 }
149
150 u++;
151 g = strchr(u, ':');
152 if (!g) {
153 log_error("/etc/passwd entry has invalid UID field.");
154 return -EIO;
155 }
156
157 *g = 0;
158 g++;
159 x = strchr(g, ':');
160 if (!x) {
161 log_error("/etc/passwd entry has invalid GID field.");
162 return -EIO;
163 }
164
165 *x = 0;
166 h = strchr(x+1, ':');
167 if (!h) {
168 log_error("/etc/passwd entry has invalid GECOS field.");
169 return -EIO;
170 }
171
172 h++;
173 x = strchr(h, ':');
174 if (!x) {
175 log_error("/etc/passwd entry has invalid home directory field.");
176 return -EIO;
177 }
178
179 *x = 0;
180
181 r = parse_uid(u, &uid);
182 if (r < 0) {
183 log_error("Failed to parse UID of user.");
184 return -EIO;
185 }
186
187 r = parse_gid(g, &gid);
188 if (r < 0) {
189 log_error("Failed to parse GID of user.");
190 return -EIO;
191 }
192
193 home = strdup(h);
194 if (!home)
195 return log_oom();
196
197 f = safe_fclose(f);
198 line = mfree(line);
199
200 /* Second, get group memberships */
201 fd = spawn_getent("initgroups", user, &pid);
202 if (fd < 0)
203 return fd;
204
205 f = fdopen(fd, "re");
206 if (!f)
207 return log_oom();
208 fd = -1;
209
210 r = read_line(f, LONG_LINE_MAX, &line);
211 if (r == 0) {
212 log_error("Failed to resolve user %s.", user);
213 return -ESRCH;
214 }
215 if (r < 0)
216 return log_error_errno(r, "Failed to read from getent: %m");
217
218 (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
219
220 /* Skip over the username and subsequent separator whitespace */
221 x = line;
222 x += strcspn(x, WHITESPACE);
223 x += strspn(x, WHITESPACE);
224
225 FOREACH_WORD(word, l, x, state) {
226 char c[l+1];
227
228 memcpy(c, word, l);
229 c[l] = 0;
230
231 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
232 return log_oom();
233
234 r = parse_uid(c, &uids[n_uids++]);
235 if (r < 0) {
236 log_error("Failed to parse group data from getent.");
237 return -EIO;
238 }
239 }
240
241 r = mkdir_parents(home, 0775);
242 if (r < 0)
243 return log_error_errno(r, "Failed to make home root directory: %m");
244
245 r = mkdir_safe(home, 0755, uid, gid, false);
246 if (r < 0 && r != -EEXIST)
247 return log_error_errno(r, "Failed to make home directory: %m");
248
249 (void) fchown(STDIN_FILENO, uid, gid);
250 (void) fchown(STDOUT_FILENO, uid, gid);
251 (void) fchown(STDERR_FILENO, uid, gid);
252
253 if (setgroups(n_uids, uids) < 0)
254 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
255
256 if (setresgid(gid, gid, gid) < 0)
257 return log_error_errno(errno, "setresgid() failed: %m");
258
259 if (setresuid(uid, uid, uid) < 0)
260 return log_error_errno(errno, "setresuid() failed: %m");
261
262 if (_home) {
263 *_home = home;
264 home = NULL;
265 }
266
267 return 0;
268 }