xref: /unit/src/nxt_clone.c (revision 2629:116cb969f351)
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, "%L %L %L", 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 %L 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\": %L, 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