]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
Merge pull request #7386 from keszybz/spdx
[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 "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 if (!ferror(f)) {
129 log_error("Failed to resolve user %s.", user);
130 return -ESRCH;
131 }
132
133 return log_error_errno(errno, "Failed to read from getent: %m");
134 }
135
136 truncate_nl(line);
137
138 wait_for_terminate_and_warn("getent passwd", pid, true);
139
140 x = strchr(line, ':');
141 if (!x) {
142 log_error("/etc/passwd entry has invalid user field.");
143 return -EIO;
144 }
145
146 u = strchr(x+1, ':');
147 if (!u) {
148 log_error("/etc/passwd entry has invalid password field.");
149 return -EIO;
150 }
151
152 u++;
153 g = strchr(u, ':');
154 if (!g) {
155 log_error("/etc/passwd entry has invalid UID field.");
156 return -EIO;
157 }
158
159 *g = 0;
160 g++;
161 x = strchr(g, ':');
162 if (!x) {
163 log_error("/etc/passwd entry has invalid GID field.");
164 return -EIO;
165 }
166
167 *x = 0;
168 h = strchr(x+1, ':');
169 if (!h) {
170 log_error("/etc/passwd entry has invalid GECOS field.");
171 return -EIO;
172 }
173
174 h++;
175 x = strchr(h, ':');
176 if (!x) {
177 log_error("/etc/passwd entry has invalid home directory field.");
178 return -EIO;
179 }
180
181 *x = 0;
182
183 r = parse_uid(u, &uid);
184 if (r < 0) {
185 log_error("Failed to parse UID of user.");
186 return -EIO;
187 }
188
189 r = parse_gid(g, &gid);
190 if (r < 0) {
191 log_error("Failed to parse GID of user.");
192 return -EIO;
193 }
194
195 home = strdup(h);
196 if (!home)
197 return log_oom();
198
199 /* Second, get group memberships */
200 fd = spawn_getent("initgroups", user, &pid);
201 if (fd < 0)
202 return fd;
203
204 fclose(f);
205 f = fdopen(fd, "r");
206 if (!f)
207 return log_oom();
208 fd = -1;
209
210 if (!fgets(line, sizeof(line), f)) {
211 if (!ferror(f)) {
212 log_error("Failed to resolve user %s.", user);
213 return -ESRCH;
214 }
215
216 return log_error_errno(errno, "Failed to read from getent: %m");
217 }
218
219 truncate_nl(line);
220
221 wait_for_terminate_and_warn("getent initgroups", pid, true);
222
223 /* Skip over the username and subsequent separator whitespace */
224 x = line;
225 x += strcspn(x, WHITESPACE);
226 x += strspn(x, WHITESPACE);
227
228 FOREACH_WORD(word, l, x, state) {
229 char c[l+1];
230
231 memcpy(c, word, l);
232 c[l] = 0;
233
234 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
235 return log_oom();
236
237 r = parse_uid(c, &uids[n_uids++]);
238 if (r < 0) {
239 log_error("Failed to parse group data from getent.");
240 return -EIO;
241 }
242 }
243
244 r = mkdir_parents(home, 0775);
245 if (r < 0)
246 return log_error_errno(r, "Failed to make home root directory: %m");
247
248 r = mkdir_safe(home, 0755, uid, gid, false);
249 if (r < 0 && r != -EEXIST)
250 return log_error_errno(r, "Failed to make home directory: %m");
251
252 (void) fchown(STDIN_FILENO, uid, gid);
253 (void) fchown(STDOUT_FILENO, uid, gid);
254 (void) fchown(STDERR_FILENO, uid, gid);
255
256 if (setgroups(n_uids, uids) < 0)
257 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
258
259 if (setresgid(gid, gid, gid) < 0)
260 return log_error_errno(errno, "setresgid() failed: %m");
261
262 if (setresuid(uid, uid, uid) < 0)
263 return log_error_errno(errno, "setresuid() failed: %m");
264
265 if (_home) {
266 *_home = home;
267 home = NULL;
268 }
269
270 return 0;
271 }