]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
Merge pull request #7540 from fbuihuu/systemd-delta-tweaks
[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 "errno.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], r;
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 r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
48 if (r < 0)
49 return r;
50 if (r == 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 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 (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 /* 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 (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
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, false);
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 }