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