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