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