]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
tree-wide: drop 'This file is part of systemd' blurb
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright 2015 Lennart Poettering
4 ***/
5
6 #include <grp.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9
10 #include "alloc-util.h"
11 #include "def.h"
12 #include "errno.h"
13 #include "fd-util.h"
14 #include "fileio.h"
15 #include "mkdir.h"
16 #include "nspawn-setuid.h"
17 #include "process-util.h"
18 #include "signal-util.h"
19 #include "string-util.h"
20 #include "strv.h"
21 #include "user-util.h"
22 #include "util.h"
23
24 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
25 int pipe_fds[2], r;
26 pid_t pid;
27
28 assert(database);
29 assert(key);
30 assert(rpid);
31
32 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
33 return log_error_errno(errno, "Failed to allocate pipe: %m");
34
35 r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
36 if (r < 0) {
37 safe_close_pair(pipe_fds);
38 return r;
39 }
40 if (r == 0) {
41 char *empty_env = NULL;
42
43 safe_close(pipe_fds[0]);
44
45 if (rearrange_stdio(-1, pipe_fds[1], -1) < 0)
46 _exit(EXIT_FAILURE);
47
48 close_all_fds(NULL, 0);
49
50 execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
51 execle("/bin/getent", "getent", database, key, NULL, &empty_env);
52 _exit(EXIT_FAILURE);
53 }
54
55 pipe_fds[1] = safe_close(pipe_fds[1]);
56
57 *rpid = pid;
58
59 return pipe_fds[0];
60 }
61
62 int change_uid_gid(const char *user, char **_home) {
63 char *x, *u, *g, *h;
64 const char *word, *state;
65 _cleanup_free_ uid_t *uids = NULL;
66 _cleanup_free_ char *home = NULL, *line = NULL;
67 _cleanup_fclose_ FILE *f = NULL;
68 _cleanup_close_ int fd = -1;
69 unsigned n_uids = 0;
70 size_t sz = 0, l;
71 uid_t uid;
72 gid_t gid;
73 pid_t pid;
74 int r;
75
76 assert(_home);
77
78 if (!user || STR_IN_SET(user, "root", "0")) {
79 /* Reset everything fully to 0, just in case */
80
81 r = reset_uid_gid();
82 if (r < 0)
83 return log_error_errno(r, "Failed to become root: %m");
84
85 *_home = NULL;
86 return 0;
87 }
88
89 /* First, get user credentials */
90 fd = spawn_getent("passwd", user, &pid);
91 if (fd < 0)
92 return fd;
93
94 f = fdopen(fd, "re");
95 if (!f)
96 return log_oom();
97 fd = -1;
98
99 r = read_line(f, LONG_LINE_MAX, &line);
100 if (r == 0) {
101 log_error("Failed to resolve user %s.", user);
102 return -ESRCH;
103 }
104 if (r < 0)
105 return log_error_errno(r, "Failed to read from getent: %m");
106
107 (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
108
109 x = strchr(line, ':');
110 if (!x) {
111 log_error("/etc/passwd entry has invalid user field.");
112 return -EIO;
113 }
114
115 u = strchr(x+1, ':');
116 if (!u) {
117 log_error("/etc/passwd entry has invalid password field.");
118 return -EIO;
119 }
120
121 u++;
122 g = strchr(u, ':');
123 if (!g) {
124 log_error("/etc/passwd entry has invalid UID field.");
125 return -EIO;
126 }
127
128 *g = 0;
129 g++;
130 x = strchr(g, ':');
131 if (!x) {
132 log_error("/etc/passwd entry has invalid GID field.");
133 return -EIO;
134 }
135
136 *x = 0;
137 h = strchr(x+1, ':');
138 if (!h) {
139 log_error("/etc/passwd entry has invalid GECOS field.");
140 return -EIO;
141 }
142
143 h++;
144 x = strchr(h, ':');
145 if (!x) {
146 log_error("/etc/passwd entry has invalid home directory field.");
147 return -EIO;
148 }
149
150 *x = 0;
151
152 r = parse_uid(u, &uid);
153 if (r < 0) {
154 log_error("Failed to parse UID of user.");
155 return -EIO;
156 }
157
158 r = parse_gid(g, &gid);
159 if (r < 0) {
160 log_error("Failed to parse GID of user.");
161 return -EIO;
162 }
163
164 home = strdup(h);
165 if (!home)
166 return log_oom();
167
168 f = safe_fclose(f);
169 line = mfree(line);
170
171 /* Second, get group memberships */
172 fd = spawn_getent("initgroups", user, &pid);
173 if (fd < 0)
174 return fd;
175
176 f = fdopen(fd, "re");
177 if (!f)
178 return log_oom();
179 fd = -1;
180
181 r = read_line(f, LONG_LINE_MAX, &line);
182 if (r == 0) {
183 log_error("Failed to resolve user %s.", user);
184 return -ESRCH;
185 }
186 if (r < 0)
187 return log_error_errno(r, "Failed to read from getent: %m");
188
189 (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
190
191 /* Skip over the username and subsequent separator whitespace */
192 x = line;
193 x += strcspn(x, WHITESPACE);
194 x += strspn(x, WHITESPACE);
195
196 FOREACH_WORD(word, l, x, state) {
197 char c[l+1];
198
199 memcpy(c, word, l);
200 c[l] = 0;
201
202 if (!GREEDY_REALLOC(uids, sz, n_uids+1))
203 return log_oom();
204
205 r = parse_uid(c, &uids[n_uids++]);
206 if (r < 0)
207 return log_error_errno(r, "Failed to parse group data from getent: %m");
208 }
209
210 r = mkdir_parents(home, 0775);
211 if (r < 0)
212 return log_error_errno(r, "Failed to make home root directory: %m");
213
214 r = mkdir_safe(home, 0755, uid, gid, 0);
215 if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR))
216 return log_error_errno(r, "Failed to make home directory: %m");
217
218 (void) fchown(STDIN_FILENO, uid, gid);
219 (void) fchown(STDOUT_FILENO, uid, gid);
220 (void) fchown(STDERR_FILENO, uid, gid);
221
222 if (setgroups(n_uids, uids) < 0)
223 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
224
225 if (setresgid(gid, gid, gid) < 0)
226 return log_error_errno(errno, "setresgid() failed: %m");
227
228 if (setresuid(uid, uid, uid) < 0)
229 return log_error_errno(errno, "setresuid() failed: %m");
230
231 if (_home)
232 *_home = TAKE_PTR(home);
233
234 return 0;
235 }