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