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