1 /*
2 * Copyright (C) Igor Sysoev
3 * Copyright (C) NGINX, Inc.
4 */
5
6 #include <nxt_main.h>
7 #include <sys/types.h>
8 #include <nxt_conf.h>
9 #include <nxt_clone.h>
10
11
12 #if (NXT_HAVE_CLONE_NEWUSER)
13
14 nxt_int_t nxt_clone_credential_setgroups(nxt_task_t *task, pid_t child_pid,
15 const char *str);
16 nxt_int_t nxt_clone_credential_map_set(nxt_task_t *task, const char* mapfile,
17 pid_t pid, nxt_int_t default_container, nxt_int_t default_host,
18 nxt_clone_credential_map_t *map);
19 nxt_int_t nxt_clone_credential_map_write(nxt_task_t *task, const char *mapfile,
20 pid_t pid, u_char *mapinfo);
21
22
23 nxt_int_t
nxt_clone_credential_setgroups(nxt_task_t * task,pid_t child_pid,const char * str)24 nxt_clone_credential_setgroups(nxt_task_t *task, pid_t child_pid,
25 const char *str)
26 {
27 int fd, n;
28 u_char *p, *end;
29 u_char path[PATH_MAX];
30
31 end = path + PATH_MAX;
32 p = nxt_sprintf(path, end, "/proc/%d/setgroups", child_pid);
33 *p = '\0';
34
35 if (nxt_slow_path(p == end)) {
36 nxt_alert(task, "error write past the buffer: %s", path);
37 return NXT_ERROR;
38 }
39
40 fd = open((char *)path, O_RDWR);
41
42 if (fd == -1) {
43 /*
44 * If the /proc/pid/setgroups doesn't exists, we are
45 * safe to set uid/gid maps. But if the error is anything
46 * other than ENOENT, then we should abort and let user know.
47 */
48
49 if (errno != ENOENT) {
50 nxt_alert(task, "open(%s): %E", path, nxt_errno);
51 return NXT_ERROR;
52 }
53
54 return NXT_OK;
55 }
56
57 n = write(fd, str, strlen(str));
58 close(fd);
59
60 if (nxt_slow_path(n == -1)) {
61 nxt_alert(task, "write(%s): %E", path, nxt_errno);
62 return NXT_ERROR;
63 }
64
65 return NXT_OK;
66 }
67
68
69 nxt_int_t
nxt_clone_credential_map_write(nxt_task_t * task,const char * mapfile,pid_t pid,u_char * mapinfo)70 nxt_clone_credential_map_write(nxt_task_t *task, const char *mapfile,
71 pid_t pid, u_char *mapinfo)
72 {
73 int len, mapfd;
74 u_char *p, *end;
75 ssize_t n;
76 u_char buf[256];
77
78 end = buf + sizeof(buf);
79
80 p = nxt_sprintf(buf, end, "/proc/%d/%s", pid, mapfile);
81 if (nxt_slow_path(p == end)) {
82 nxt_alert(task, "writing past the buffer");
83 return NXT_ERROR;
84 }
85
86 *p = '\0';
87
88 mapfd = open((char*)buf, O_RDWR);
89 if (nxt_slow_path(mapfd == -1)) {
90 nxt_alert(task, "failed to open proc map (%s) %E", buf, nxt_errno);
91 return NXT_ERROR;
92 }
93
94 len = nxt_strlen(mapinfo);
95
96 n = write(mapfd, (char *)mapinfo, len);
97 if (nxt_slow_path(n != len)) {
98
99 if (n == -1 && nxt_errno == EINVAL) {
100 nxt_alert(task, "failed to write %s: Check kernel maximum " \
101 "allowed lines %E", buf, nxt_errno);
102
103 } else {
104 nxt_alert(task, "failed to write proc map (%s) %E", buf,
105 nxt_errno);
106 }
107
108 close(mapfd);
109
110 return NXT_ERROR;
111 }
112
113 close(mapfd);
114
115 return NXT_OK;
116 }
117
118
119 nxt_int_t
nxt_clone_credential_map_set(nxt_task_t * task,const char * mapfile,pid_t pid,nxt_int_t default_container,nxt_int_t default_host,nxt_clone_credential_map_t * map)120 nxt_clone_credential_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
121 nxt_int_t default_container, nxt_int_t default_host,
122 nxt_clone_credential_map_t *map)
123 {
124 u_char *p, *end, *mapinfo;
125 nxt_int_t ret, len;
126 nxt_uint_t i;
127
128 /*
129 * uid_map one-entry size:
130 * alloc space for 3 numbers (32bit) plus 2 spaces and \n.
131 */
132 len = sizeof(u_char) * (10 + 10 + 10 + 2 + 1);
133
134 if (map->size > 0) {
135 len = len * map->size + 1;
136
137 mapinfo = nxt_malloc(len);
138 if (nxt_slow_path(mapinfo == NULL)) {
139 return NXT_ERROR;
140 }
141
142 p = mapinfo;
143 end = mapinfo + len;
144
145 for (i = 0; i < map->size; i++) {
146 p = nxt_sprintf(p, end, "%d %d %d", map->map[i].container,
147 map->map[i].host, map->map[i].size);
148
149 if (nxt_slow_path(p == end)) {
150 nxt_alert(task, "write past the mapinfo buffer");
151 nxt_free(mapinfo);
152 return NXT_ERROR;
153 }
154
155 if (i+1 < map->size) {
156 *p++ = '\n';
157
158 } else {
159 *p = '\0';
160 }
161 }
162
163 } else {
164 mapinfo = nxt_malloc(len);
165 if (nxt_slow_path(mapinfo == NULL)) {
166 return NXT_ERROR;
167 }
168
169 end = mapinfo + len;
170 p = nxt_sprintf(mapinfo, end, "%d %d 1",
171 default_container, default_host);
172 *p = '\0';
173
174 if (nxt_slow_path(p == end)) {
175 nxt_alert(task, "write past mapinfo buffer");
176 nxt_free(mapinfo);
177 return NXT_ERROR;
178 }
179 }
180
181 ret = nxt_clone_credential_map_write(task, mapfile, pid, mapinfo);
182
183 nxt_free(mapinfo);
184
185 return ret;
186 }
187
188
189 nxt_int_t
nxt_clone_credential_map(nxt_task_t * task,pid_t pid,nxt_credential_t * app_creds,nxt_clone_t * clone)190 nxt_clone_credential_map(nxt_task_t *task, pid_t pid,
191 nxt_credential_t *app_creds, nxt_clone_t *clone)
192 {
193 nxt_int_t ret;
194 nxt_int_t default_host_uid;
195 nxt_int_t default_host_gid;
196 const char *rule;
197 nxt_runtime_t *rt;
198
199 rt = task->thread->runtime;
200
201 if (rt->capabilities.setid) {
202 rule = "allow";
203
204 /*
205 * By default we don't map a privileged user
206 */
207 default_host_uid = app_creds->uid;
208 default_host_gid = app_creds->base_gid;
209 } else {
210 rule = "deny";
211
212 default_host_uid = nxt_euid;
213 default_host_gid = nxt_egid;
214 }
215
216 ret = nxt_clone_credential_map_set(task, "uid_map", pid, app_creds->uid,
217 default_host_uid,
218 &clone->uidmap);
219
220 if (nxt_slow_path(ret != NXT_OK)) {
221 return NXT_ERROR;
222 }
223
224 ret = nxt_clone_credential_setgroups(task, pid, rule);
225 if (nxt_slow_path(ret != NXT_OK)) {
226 nxt_alert(task, "failed to write /proc/%d/setgroups", pid);
227 return NXT_ERROR;
228 }
229
230 ret = nxt_clone_credential_map_set(task, "gid_map", pid, app_creds->base_gid,
231 default_host_gid,
232 &clone->gidmap);
233
234 if (nxt_slow_path(ret != NXT_OK)) {
235 return NXT_ERROR;
236 }
237
238 return NXT_OK;
239 }
240
241
242 nxt_int_t
nxt_clone_vldt_credential_uidmap(nxt_task_t * task,nxt_clone_credential_map_t * map,nxt_credential_t * creds)243 nxt_clone_vldt_credential_uidmap(nxt_task_t *task,
244 nxt_clone_credential_map_t *map, nxt_credential_t *creds)
245 {
246 nxt_int_t id;
247 nxt_uint_t i;
248 nxt_runtime_t *rt;
249 nxt_clone_map_entry_t m;
250
251 if (map->size == 0) {
252 return NXT_OK;
253 }
254
255 rt = task->thread->runtime;
256
257 if (!rt->capabilities.setid) {
258 if (nxt_slow_path(map->size > 1)) {
259 nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has %d entries "
260 "but unprivileged unit has a maximum of 1 map.",
261 map->size);
262
263 return NXT_ERROR;
264 }
265
266 id = map->map[0].host;
267
268 if (nxt_slow_path((nxt_uid_t) id != nxt_euid)) {
269 nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has an entry for "
270 "host uid %d but unprivileged unit can only map itself "
271 "(uid %d) into child namespaces.", id, nxt_euid);
272
273 return NXT_ERROR;
274 }
275
276 return NXT_OK;
277 }
278
279 for (i = 0; i < map->size; i++) {
280 m = map->map[i];
281
282 if (creds->uid >= (nxt_uid_t) m.container
283 && creds->uid < (nxt_uid_t) (m.container + m.size))
284 {
285 return NXT_OK;
286 }
287 }
288
289 nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has no \"container\" "
290 "entry for user \"%s\" (uid %d)", creds->user, creds->uid);
291
292 return NXT_ERROR;
293 }
294
295
296 nxt_int_t
nxt_clone_vldt_credential_gidmap(nxt_task_t * task,nxt_clone_credential_map_t * map,nxt_credential_t * creds)297 nxt_clone_vldt_credential_gidmap(nxt_task_t *task,
298 nxt_clone_credential_map_t *map, nxt_credential_t *creds)
299 {
300 nxt_uint_t base_ok, gid_ok, gids_ok;
301 nxt_uint_t i, j;
302 nxt_runtime_t *rt;
303 nxt_clone_map_entry_t m;
304
305 rt = task->thread->runtime;
306
307 if (!rt->capabilities.setid) {
308 if (creds->ngroups > 0
309 && !(creds->ngroups == 1 && creds->gids[0] == creds->base_gid)) {
310 nxt_log(task, NXT_LOG_NOTICE,
311 "unprivileged unit disallow supplementary groups for "
312 "new namespace (user \"%s\" has %d group%s).",
313 creds->user, creds->ngroups,
314 creds->ngroups > 1 ? "s" : "");
315
316 return NXT_ERROR;
317 }
318
319 if (map->size == 0) {
320 return NXT_OK;
321 }
322
323 if (nxt_slow_path(map->size > 1)) {
324 nxt_log(task, NXT_LOG_NOTICE, "\"gidmap\" field has %d entries "
325 "but unprivileged unit has a maximum of 1 map.",
326 map->size);
327
328 return NXT_ERROR;
329 }
330
331 m = map->map[0];
332
333 if (nxt_slow_path((nxt_gid_t) m.host != nxt_egid)) {
334 nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has an entry for "
335 "host gid %d but unprivileged unit can only map itself "
336 "(gid %d) into child namespaces.", m.host, nxt_egid);
337
338 return NXT_ERROR;
339 }
340
341 if (nxt_slow_path(m.size > 1)) {
342 nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has an entry with "
343 "\"size\": %d, but for unprivileged unit it must be 1.",
344 m.size);
345
346 return NXT_ERROR;
347 }
348
349 if (nxt_slow_path((nxt_gid_t) m.container != creds->base_gid)) {
350 nxt_log(task, NXT_LOG_ERR,
351 "\"gidmap\" field has no \"container\" entry for gid %d.",
352 creds->base_gid);
353
354 return NXT_ERROR;
355 }
356
357 return NXT_OK;
358 }
359
360 if (map->size == 0) {
361 if (creds->ngroups > 0
362 && !(creds->ngroups == 1 && creds->gids[0] == creds->base_gid))
363 {
364 nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has no entries "
365 "but user \"%s\" has %d suplementary group%s.",
366 creds->user, creds->ngroups,
367 creds->ngroups > 1 ? "s" : "");
368
369 return NXT_ERROR;
370 }
371
372 return NXT_OK;
373 }
374
375 base_ok = 0;
376 gids_ok = 0;
377
378 for (i = 0; i < creds->ngroups; i++) {
379 gid_ok = 0;
380
381 for (j = 0; j < map->size; j++) {
382 m = map->map[j];
383
384 if (!base_ok && creds->base_gid >= (nxt_gid_t) m.container
385 && creds->base_gid < (nxt_gid_t) (m.container+m.size))
386 {
387 base_ok = 1;
388 }
389
390 if (creds->gids[i] >= (nxt_gid_t) m.container
391 && creds->gids[i] < (nxt_gid_t) (m.container+m.size))
392 {
393 gid_ok = 1;
394 break;
395 }
396 }
397
398 if (nxt_fast_path(gid_ok)) {
399 gids_ok++;
400 }
401 }
402
403 if (!base_ok) {
404 for (i = 0; i < map->size; i++) {
405 m = map->map[i];
406
407 if (creds->base_gid >= (nxt_gid_t) m.container
408 && creds->base_gid < (nxt_gid_t) (m.container+m.size))
409 {
410 base_ok = 1;
411 break;
412 }
413 }
414 }
415
416 if (nxt_slow_path(!base_ok)) {
417 nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has no \"container\" "
418 "entry for gid %d.", creds->base_gid);
419
420 return NXT_ERROR;
421 }
422
423 if (nxt_slow_path(gids_ok < creds->ngroups)) {
424 nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has missing "
425 "suplementary gid mappings (found %d out of %d).", gids_ok,
426 creds->ngroups);
427
428 return NXT_ERROR;
429 }
430
431 return NXT_OK;
432 }
433
434 #endif
435