]> git.proxmox.com Git - systemd.git/blob - src/nspawn/nspawn-setuid.c
New upstream version 249~rc1
[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 "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 uid_t uid;
100 gid_t gid;
101 pid_t pid;
102 int r;
103
104 assert(ret_home);
105
106 if (!user || STR_IN_SET(user, "root", "0")) {
107 /* Reset everything fully to 0, just in case */
108
109 r = reset_uid_gid();
110 if (r < 0)
111 return log_error_errno(r, "Failed to become root: %m");
112
113 *ret_home = NULL;
114 return 0;
115 }
116
117 /* First, get user credentials */
118 fd = spawn_getent("passwd", user, &pid);
119 if (fd < 0)
120 return fd;
121
122 f = take_fdopen(&fd, "r");
123 if (!f)
124 return log_oom();
125
126 r = read_line(f, LONG_LINE_MAX, &line);
127 if (r == 0)
128 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
129 "Failed to resolve user %s.", user);
130 if (r < 0)
131 return log_error_errno(r, "Failed to read from getent: %m");
132
133 (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
134
135 x = strchr(line, ':');
136 if (!x)
137 return log_error_errno(SYNTHETIC_ERRNO(EIO),
138 "/etc/passwd entry has invalid user field.");
139
140 u = strchr(x+1, ':');
141 if (!u)
142 return log_error_errno(SYNTHETIC_ERRNO(EIO),
143 "/etc/passwd entry has invalid password field.");
144
145 u++;
146 g = strchr(u, ':');
147 if (!g)
148 return log_error_errno(SYNTHETIC_ERRNO(EIO),
149 "/etc/passwd entry has invalid UID field.");
150
151 *g = 0;
152 g++;
153 x = strchr(g, ':');
154 if (!x)
155 return log_error_errno(SYNTHETIC_ERRNO(EIO),
156 "/etc/passwd entry has invalid GID field.");
157
158 *x = 0;
159 h = strchr(x+1, ':');
160 if (!h)
161 return log_error_errno(SYNTHETIC_ERRNO(EIO),
162 "/etc/passwd entry has invalid GECOS field.");
163
164 h++;
165 x = strchr(h, ':');
166 if (!x)
167 return log_error_errno(SYNTHETIC_ERRNO(EIO),
168 "/etc/passwd entry has invalid home directory field.");
169
170 *x = 0;
171
172 r = parse_uid(u, &uid);
173 if (r < 0)
174 return log_error_errno(SYNTHETIC_ERRNO(EIO),
175 "Failed to parse UID of user.");
176
177 r = parse_gid(g, &gid);
178 if (r < 0)
179 return log_error_errno(SYNTHETIC_ERRNO(EIO),
180 "Failed to parse GID of user.");
181
182 home = strdup(h);
183 if (!home)
184 return log_oom();
185
186 f = safe_fclose(f);
187 line = mfree(line);
188
189 /* Second, get group memberships */
190 fd = spawn_getent("initgroups", user, &pid);
191 if (fd < 0)
192 return fd;
193
194 f = take_fdopen(&fd, "r");
195 if (!f)
196 return log_oom();
197
198 r = read_line(f, LONG_LINE_MAX, &line);
199 if (r == 0)
200 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
201 "Failed to resolve user %s.", user);
202 if (r < 0)
203 return log_error_errno(r, "Failed to read from getent: %m");
204
205 (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
206
207 /* Skip over the username and subsequent separator whitespace */
208 x = line;
209 x += strcspn(x, WHITESPACE);
210 x += strspn(x, WHITESPACE);
211
212 for (const char *p = x;;) {
213 _cleanup_free_ char *word = NULL;
214
215 r = extract_first_word(&p, &word, NULL, 0);
216 if (r < 0)
217 return log_error_errno(r, "Failed to parse group data from getent: %m");
218 if (r == 0)
219 break;
220
221 if (!GREEDY_REALLOC(gids, n_gids+1))
222 return log_oom();
223
224 r = parse_gid(word, &gids[n_gids++]);
225 if (r < 0)
226 return log_error_errno(r, "Failed to parse group data from getent: %m");
227 }
228
229 r = mkdir_parents(home, 0775);
230 if (r < 0)
231 return log_error_errno(r, "Failed to make home root directory: %m");
232
233 r = mkdir_safe(home, 0755, uid, gid, 0);
234 if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR))
235 return log_error_errno(r, "Failed to make home directory: %m");
236
237 r = change_uid_gid_raw(uid, gid, gids, n_gids, chown_stdio);
238 if (r < 0)
239 return r;
240
241 if (ret_home)
242 *ret_home = TAKE_PTR(home);
243
244 return 0;
245 }