]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nspawn/nspawn-setuid.c
Merge pull request #25791 from keszybz/ukify-check-inputs
[thirdparty/systemd.git] / src / nspawn / nspawn-setuid.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <sys/types.h>
5 #include <unistd.h>
6
7 #include "alloc-util.h"
8 #include "constants.h"
9 #include "errno.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "mkdir.h"
13 #include "nspawn-setuid.h"
14 #include "process-util.h"
15 #include "rlimit-util.h"
16 #include "signal-util.h"
17 #include "string-util.h"
18 #include "strv.h"
19 #include "user-util.h"
20
21 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
22 int pipe_fds[2], r;
23 pid_t pid;
24
25 assert(database);
26 assert(key);
27 assert(rpid);
28
29 if (pipe2(pipe_fds, O_CLOEXEC) < 0)
30 return log_error_errno(errno, "Failed to allocate pipe: %m");
31
32 r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
33 if (r < 0) {
34 safe_close_pair(pipe_fds);
35 return r;
36 }
37 if (r == 0) {
38 char *empty_env = NULL;
39
40 pipe_fds[0] = safe_close(pipe_fds[0]);
41
42 if (rearrange_stdio(-EBADF, TAKE_FD(pipe_fds[1]), -EBADF) < 0)
43 _exit(EXIT_FAILURE);
44
45 (void) close_all_fds(NULL, 0);
46
47 (void) rlimit_nofile_safe();
48
49 execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env);
50 execle("/bin/getent", "getent", database, key, NULL, &empty_env);
51 _exit(EXIT_FAILURE);
52 }
53
54 pipe_fds[1] = safe_close(pipe_fds[1]);
55
56 *rpid = pid;
57
58 return pipe_fds[0];
59 }
60
61 int change_uid_gid_raw(
62 uid_t uid,
63 gid_t gid,
64 const gid_t *supplementary_gids,
65 size_t n_supplementary_gids,
66 bool chown_stdio) {
67
68 if (!uid_is_valid(uid))
69 uid = 0;
70 if (!gid_is_valid(gid))
71 gid = 0;
72
73 if (chown_stdio) {
74 (void) fchown(STDIN_FILENO, uid, gid);
75 (void) fchown(STDOUT_FILENO, uid, gid);
76 (void) fchown(STDERR_FILENO, uid, gid);
77 }
78
79 if (setgroups(n_supplementary_gids, supplementary_gids) < 0)
80 return log_error_errno(errno, "Failed to set auxiliary groups: %m");
81
82 if (setresgid(gid, gid, gid) < 0)
83 return log_error_errno(errno, "setresgid() failed: %m");
84
85 if (setresuid(uid, uid, uid) < 0)
86 return log_error_errno(errno, "setresuid() failed: %m");
87
88 return 0;
89 }
90
91 int change_uid_gid(const char *user, bool chown_stdio, char **ret_home) {
92 char *x, *u, *g, *h;
93 _cleanup_free_ gid_t *gids = NULL;
94 _cleanup_free_ char *home = NULL, *line = NULL;
95 _cleanup_fclose_ FILE *f = NULL;
96 _cleanup_close_ int fd = -EBADF;
97 unsigned n_gids = 0;
98 uid_t uid;
99 gid_t gid;
100 pid_t pid;
101 int r;
102
103 assert(ret_home);
104
105 if (!user || STR_IN_SET(user, "root", "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 *ret_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 = take_fdopen(&fd, "r");
122 if (!f)
123 return log_oom();
124
125 r = read_line(f, LONG_LINE_MAX, &line);
126 if (r == 0)
127 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
128 "Failed to resolve user %s.", user);
129 if (r < 0)
130 return log_error_errno(r, "Failed to read from getent: %m");
131
132 (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
133
134 x = strchr(line, ':');
135 if (!x)
136 return log_error_errno(SYNTHETIC_ERRNO(EIO),
137 "/etc/passwd entry has invalid user field.");
138
139 u = strchr(x+1, ':');
140 if (!u)
141 return log_error_errno(SYNTHETIC_ERRNO(EIO),
142 "/etc/passwd entry has invalid password field.");
143
144 u++;
145 g = strchr(u, ':');
146 if (!g)
147 return log_error_errno(SYNTHETIC_ERRNO(EIO),
148 "/etc/passwd entry has invalid UID field.");
149
150 *g = 0;
151 g++;
152 x = strchr(g, ':');
153 if (!x)
154 return log_error_errno(SYNTHETIC_ERRNO(EIO),
155 "/etc/passwd entry has invalid GID field.");
156
157 *x = 0;
158 h = strchr(x+1, ':');
159 if (!h)
160 return log_error_errno(SYNTHETIC_ERRNO(EIO),
161 "/etc/passwd entry has invalid GECOS field.");
162
163 h++;
164 x = strchr(h, ':');
165 if (!x)
166 return log_error_errno(SYNTHETIC_ERRNO(EIO),
167 "/etc/passwd entry has invalid home directory field.");
168
169 *x = 0;
170
171 r = parse_uid(u, &uid);
172 if (r < 0)
173 return log_error_errno(SYNTHETIC_ERRNO(EIO),
174 "Failed to parse UID of user.");
175
176 r = parse_gid(g, &gid);
177 if (r < 0)
178 return log_error_errno(SYNTHETIC_ERRNO(EIO),
179 "Failed to parse GID of user.");
180
181 home = strdup(h);
182 if (!home)
183 return log_oom();
184
185 f = safe_fclose(f);
186 line = mfree(line);
187
188 /* Second, get group memberships */
189 fd = spawn_getent("initgroups", user, &pid);
190 if (fd < 0)
191 return fd;
192
193 f = take_fdopen(&fd, "r");
194 if (!f)
195 return log_oom();
196
197 r = read_line(f, LONG_LINE_MAX, &line);
198 if (r == 0)
199 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
200 "Failed to resolve user %s.", user);
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 for (const char *p = x;;) {
212 _cleanup_free_ char *word = NULL;
213
214 r = extract_first_word(&p, &word, NULL, 0);
215 if (r < 0)
216 return log_error_errno(r, "Failed to parse group data from getent: %m");
217 if (r == 0)
218 break;
219
220 if (!GREEDY_REALLOC(gids, n_gids+1))
221 return log_oom();
222
223 r = parse_gid(word, &gids[n_gids++]);
224 if (r < 0)
225 return log_error_errno(r, "Failed to parse group data from getent: %m");
226 }
227
228 r = mkdir_parents(home, 0775);
229 if (r < 0)
230 return log_error_errno(r, "Failed to make home root directory: %m");
231
232 r = mkdir_safe(home, 0755, uid, gid, 0);
233 if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR))
234 return log_error_errno(r, "Failed to make home directory: %m");
235
236 r = change_uid_gid_raw(uid, gid, gids, n_gids, chown_stdio);
237 if (r < 0)
238 return r;
239
240 if (ret_home)
241 *ret_home = TAKE_PTR(home);
242
243 return 0;
244 }