]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
Merge pull request #3757 from poettering/efi-search
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2015 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <grp.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23
24 #include "alloc-util.h"
25 #include "fd-util.h"
26 #include "mkdir.h"
27 #include "nspawn-setuid.h"
28 #include "process-util.h"
29 #include "signal-util.h"
30 #include "string-util.h"
31 #include "user-util.h"
32 #include "util.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 if (!ferror(f)) {
128 log_error("Failed to resolve user %s.", user);
129 return -ESRCH;
130 }
131
132 return log_error_errno(errno, "Failed to read from getent: %m");
133 }
134
135 truncate_nl(line);
136
137 wait_for_terminate_and_warn("getent passwd", pid, true);
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 /* Second, get group memberships */
199 fd = spawn_getent("initgroups", user, &pid);
200 if (fd < 0)
201 return fd;
202
203 fclose(f);
204 f = fdopen(fd, "r");
205 if (!f)
206 return log_oom();
207 fd = -1;
208
209 if (!fgets(line, sizeof(line), f)) {
210 if (!ferror(f)) {
211 log_error("Failed to resolve user %s.", user);
212 return -ESRCH;
213 }
214
215 return log_error_errno(errno, "Failed to read from getent: %m");
216 }
217
218 truncate_nl(line);
219
220 wait_for_terminate_and_warn("getent initgroups", pid, true);
221
222 /* Skip over the username and subsequent separator whitespace */
223 x = line;
224 x += strcspn(x, WHITESPACE);
225 x += strspn(x, WHITESPACE);
226
227 FOREACH_WORD(word, l, x, state) {
228 char c[l+1];
229
230 memcpy(c, word, l);
231 c[l] = 0;
232
233 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
234 return log_oom();
235
236 r = parse_uid(c, &uids[n_uids++]);
237 if (r < 0) {
238 log_error("Failed to parse group data from getent.");
239 return -EIO;
240 }
241 }
242
243 r = mkdir_parents(home, 0775);
244 if (r < 0)
245 return log_error_errno(r, "Failed to make home root directory: %m");
246
247 r = mkdir_safe(home, 0755, uid, gid);
248 if (r < 0 && r != -EEXIST)
249 return log_error_errno(r, "Failed to make home directory: %m");
250
251 (void) fchown(STDIN_FILENO, uid, gid);
252 (void) fchown(STDOUT_FILENO, uid, gid);
253 (void) fchown(STDERR_FILENO, uid, gid);
254
255 if (setgroups(n_uids, uids) < 0)
256 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
257
258 if (setresgid(gid, gid, gid) < 0)
259 return log_error_errno(errno, "setresgid() failed: %m");
260
261 if (setresuid(uid, uid, uid) < 0)
262 return log_error_errno(errno, "setresuid() failed: %m");
263
264 if (_home) {
265 *_home = home;
266 home = NULL;
267 }
268
269 return 0;
270 }