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