Coverage Report

Created: 2025-10-10 06:52

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