• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

drakenclimber / libcgroup / 16178979042

16 Jun 2025 02:39PM UTC coverage: 54.02% (-2.2%) from 56.219%
16178979042

push

github

drakenclimber
api.c: prevent array out-of-bounds access in cgroup_parse_rules_file

In the function src/api.c/cgroup_parse_rules_file, the condition of loop:

for (i = 0; lst->tail->controllers[i]; i++)
        cgroup_dbg(" %s", lst->tail->controllers[i]);

allows accessing lst->tail->controllers[MAX_MNT_ELEMENTS] if
lst->tail->controllers is full and lacks a terminating NULL.

Add explicit bounds checking (i < MAX_MNT_ELEMENTS) while maintaining
the NULL check. This ensures that there will never be reading past the
array boundaries regardless of its content.

Found by Linux Verification Center (linuxtesting.org) with SVACE.

Signed-off-by: Mikhail Dmitrichenko <m.dmitrichenko222@gmail.com>
Acked-by: Kamalesh Babulal <kamalesh.babulal@oracle.com>
Signed-off-by: Tom Hromatka <tom.hromatka@oracle.com>

1 of 1 new or added line in 1 file covered. (100.0%)

272 existing lines in 14 files now uncovered.

5435 of 10061 relevant lines covered (54.02%)

170.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.71
/src/systemd.c
1
/* SPDX-License-Identifier: LGPL-2.1-only */
2
/**
3
 * Copyright (c) 2022 Oracle and/or its affiliates.
4
 * Author: Tom Hromatka <tom.hromatka@oracle.com>
5
 * Author: Silvia Chapa <silvia.chapa@oracle.com>
6
 */
7

8
#include <libcgroup-internal.h>
9

10
#ifdef WITH_SYSTEMD
11
#include <systemd/sd-bus.h>
12
#include <libcgroup.h>
13
#include <unistd.h>
14
#include <assert.h>
15
#include <stdlib.h>
16
#include <libgen.h>
17
#include <errno.h>
18

19
#define USEC_PER_SEC 1000000
20

21
static const char * const modes[] = {
22
        "fail",                        /* CGROUP_SYSTEMD_MODE_FAIL */
23
        "replace",                /* CGROUP_SYSTEMD_MODE_REPLACE */
24
        "isolate",                /* CGROUP_SYSTEMD_MODE_ISOLATE */
25
        "ignore-dependencies",        /* CGROUP_SYSTEMD_MODE_IGNORE_DEPS */
26
        "ignore-requirements",        /* CGROUP_SYSTEMD_MODE_IGNORE_REQS */
27
};
28
static_assert(ARRAY_SIZE(modes) == CGROUP_SYSTEMD_MODE_CNT,
29
              "modes[] array must be same length as CGROUP_SYSTEMD_MODE_CNT");
30

31
/*
32
 * controller_name table borrowed from:
33
 * https://github.com/systemd/systemd/blob/main/src/basic/cgroup-util.c#L2260
34
 */
35
static const char * const controller_name[] = {
36
        /* subset of controllers (cgroup v1/v2), systemd supports */
37
        "cpu",
38
        "cpuacct",
39
        "cpuset",
40
        "io",
41
        "blkio",
42
        "memory",
43
        "devices",
44
        "pids",
45

46
        /* systemd pseudo BPF based controllers (cgroup v2 ) */
47
        "bpf-firewall",
48
        "bpf-devices",
49
        "bpf-foreign",
50
        "bpf-socket-bind",
51
        "bpf-restrict-network-interfaces",
52
};
53

54
static const char * const sender = "org.freedesktop.systemd1";
55
static const char * const path = "/org/freedesktop/systemd1";
56
static const char * const interface = "org.freedesktop.systemd1.Manager";
57

58
int cgroup_set_default_scope_opts(struct cgroup_systemd_scope_opts * const opts)
34✔
59
{
60
        if (!opts)
34✔
UNCOV
61
                return ECGINVAL;
×
62

63
        opts->delegated = 1;
34✔
64
        opts->mode = CGROUP_SYSTEMD_MODE_FAIL;
34✔
65
        opts->pid = -1;
34✔
66

67
        return 0;
34✔
68
}
69

70
/*
71
 * Returns time elapsed in usec
72
 *
73
 * Inspired-by: https://github.com/cockpit-project/cockpit/blob/main/src/tls/socket-io.c#L39
74
 */
75
static int64_t elapsed_time(const struct timespec * const start, const struct timespec * const end)
2,303✔
76
{
77
        int64_t elapsed = (end->tv_sec - start->tv_sec) * 1000000 +
2,303✔
78
                          (end->tv_nsec - start->tv_nsec) / 1000;
2,303✔
79

80
        assert(elapsed >= 0);
2,303✔
81

82
        return elapsed;
2,303✔
83
}
84

85
static int job_removed_callback(sd_bus_message *message, void *user_data, sd_bus_error *error)
19✔
86
{
87
        const char *result, *msg_path, *scope_name;
88
        const char **job_path = user_data;
19✔
89
        int ret;
90

91
        ret = sd_bus_message_read(message, "uoss", NULL, &msg_path, &scope_name, &result);
19✔
92
        if (ret < 0) {
19✔
UNCOV
93
                cgroup_err("callback message read failed: %d\n", errno);
×
UNCOV
94
                return 0;
×
95
        }
96

97
        if (*job_path == NULL || strcmp(msg_path, *job_path) != 0) {
19✔
98
                cgroup_dbg("Received a systemd signal, but it was not our message\n");
2✔
99
                return 0;
2✔
100
        }
101

102
        cgroup_dbg("Received JobRemoved signal for scope %s.  Result: %s\n", scope_name, result);
17✔
103

104
        /*
105
         * Use the job_path pointer as a way to inform the original thread that the job has
106
         * completed.
107
         */
108
        *job_path = NULL;
17✔
109
        return 0;
17✔
110
}
111

112
static int validate_scope_slice_name(const char * const scope_name, const char * const slice_name)
29✔
113
{
114
        char *_scope_name = NULL;
29✔
115
        int i, len, ret = 0;
29✔
116

117
        /* get the scope name without the suffix, min length is <n>.scope */
118
        len = strlen(scope_name) - 6;
29✔
119
        if (len < 1)
29✔
120
                cgroup_warn("Invalid scope name %s\n", scope_name);
2✔
121
        else if (strcmp(&scope_name[len], ".scope") != 0)
27✔
122
                cgroup_warn("scope doesn't have expected suffix\n");
2✔
123

124
        /* get the slice name without the suffix, min length is <n>.slice */
125
        len = strlen(slice_name) - 6;
29✔
126
        if (len < 1)
29✔
127
                cgroup_warn("Invalid slice name %s\n", slice_name);
2✔
128
        else if (strcmp(&slice_name[len], ".slice") != 0)
27✔
129
                cgroup_warn("slice doesn't have expected suffix\n");
2✔
130

131
        /*
132
         * systemd will silently prefix a '_' to the scope name and
133
         * create, and delegate it under the slice. If it matches with any
134
         * of the controller in the controller_name[]. i.e.,
135
         * oracle.slice/cpuset.scope, will be created as
136
         * oracle.slice/_cpuset.scope
137
         *
138
         * Disallow such scope names, that will cause confusion to the
139
         * users, where they might try to operate scope cgroup, but
140
         * the delegated scope is _scope cgroup.
141
         */
142
        _scope_name = strdup(scope_name);
29✔
143
        if (_scope_name == NULL) {
29✔
UNCOV
144
                cgroup_err("Failed to allocate memory for _scope_name\n");
×
UNCOV
145
                last_errno = errno;
×
UNCOV
146
                ret = 1;
×
UNCOV
147
                goto out;
×
148
        }
149

150
        len = strlen(_scope_name) - 6;        /* strlen(".scope") + '\0' */
29✔
151
        _scope_name[len] = '\0';
29✔
152

153
        for (i = 0; i < ARRAY_SIZE(controller_name); i++) {
380✔
154
                if (strcmp(_scope_name, controller_name[i]))
353✔
155
                        continue;
351✔
156

157
                cgroup_err("Invalid scope name, using controller name %s\n", controller_name[i]);
2✔
158
                ret = 1;
2✔
159
                break;
2✔
160
        }
161

162
        free(_scope_name);
29✔
163
out:
29✔
164
        return ret;
29✔
165
}
166

167
int cgroup_create_scope(const char * const scope_name, const char * const slice_name,
29✔
168
                        const struct cgroup_systemd_scope_opts * const opts)
169
{
170
        sd_bus_message *msg = NULL, *reply = NULL;
29✔
171
        int ret = 0, sdret = 0, cgret = ECGFAIL;
29✔
172
        sd_bus_error error = SD_BUS_ERROR_NULL;
29✔
173
        const char *job_path = NULL;
29✔
174
        struct timespec start, now;
175
        sd_bus *bus = NULL;
29✔
176
        pid_t child_pid;
177

178
        if (!scope_name || !slice_name || !opts)
29✔
UNCOV
179
                return ECGINVAL;
×
180

181
        if (validate_scope_slice_name(scope_name, slice_name))
29✔
182
                return ECGINVAL;
2✔
183

184
        if (opts->mode >= CGROUP_SYSTEMD_MODE_CNT) {
27✔
UNCOV
185
                cgroup_err("invalid systemd mode: %d\n", opts->mode);
×
UNCOV
186
                return ECGINVAL;
×
187
        }
188

189
        if (opts->mode == CGROUP_SYSTEMD_MODE_ISOLATE ||
27✔
190
            opts->mode == CGROUP_SYSTEMD_MODE_IGNORE_DEPS ||
27✔
191
            opts->mode == CGROUP_SYSTEMD_MODE_IGNORE_REQS) {
27✔
UNCOV
192
                cgroup_err("unsupported systemd mode: %d\n", opts->mode);
×
UNCOV
193
                return ECGINVAL;
×
194
        }
195

196
        if (opts->pid < 0) {
27✔
197
                child_pid = fork();
14✔
198
                if (child_pid < 0) {
28✔
UNCOV
199
                        last_errno = errno;
×
UNCOV
200
                        cgroup_err("fork failed: %d\n", errno);
×
UNCOV
201
                        return ECGOTHER;
×
202
                }
203

204
                if (child_pid == 0) {
28✔
205
                        static char * const args[] = {"libcgroup_systemd_idle_thread", NULL};
206

207
                        /*
208
                         * Have the child sleep forever.  Systemd will delete the scope if
209
                         * there isn't a running process in it.
210
                         */
211
                        execvp("libcgroup_systemd_idle_thread", args);
14✔
212

213
                        /* The child process should never get here */
214
                        last_errno = errno;
14✔
215
                        cgroup_err("failed to create system idle thread.\n");
14✔
UNCOV
216
                        return ECGOTHER;
×
217
                }
218

219
                cgroup_dbg("created libcgroup_system_idle thread pid %d\n", child_pid);
14✔
220
        } else {
221
                child_pid = opts->pid;
13✔
222
        }
223
        cgroup_dbg("pid %d will be placed in scope %s\n", child_pid, scope_name);
27✔
224

225
        sdret = sd_bus_default_system(&bus);
27✔
226
        if (sdret < 0) {
27✔
UNCOV
227
                cgroup_err("failed to open the system bus: %d\n", errno);
×
UNCOV
228
                goto out;
×
229
        }
230

231
        sdret = sd_bus_match_signal(bus, NULL, sender, path, interface,
27✔
232
                                    "JobRemoved", job_removed_callback, &job_path);
233
        if (sdret < 0) {
27✔
UNCOV
234
                cgroup_err("failed to install match callback: %d\n", errno);
×
UNCOV
235
                goto out;
×
236
        }
237

238
        sdret = sd_bus_message_new_method_call(bus, &msg, sender, path, interface,
27✔
239
                                               "StartTransientUnit");
240
        if (sdret < 0) {
27✔
UNCOV
241
                cgroup_err("failed to create the systemd msg: %d\n", errno);
×
UNCOV
242
                goto out;
×
243
        }
244

245
        sdret = sd_bus_message_append(msg, "ss", scope_name, modes[opts->mode]);
27✔
246
        if (sdret < 0) {
27✔
UNCOV
247
                cgroup_err("failed to append the scope name: %d\n", errno);
×
UNCOV
248
                goto out;
×
249
        }
250

251
        sdret = sd_bus_message_open_container(msg, 'a', "(sv)");
27✔
252
        if (sdret < 0) {
27✔
UNCOV
253
                cgroup_err("failed to open container: %d\n", errno);
×
UNCOV
254
                goto out;
×
255
        }
256

257
        sdret = sd_bus_message_append(msg, "(sv)", "Description", "s",
27✔
258
                                      "scope created by libcgroup");
259
        if (sdret < 0) {
27✔
UNCOV
260
                cgroup_err("failed to append the description: %d\n", errno);
×
UNCOV
261
                goto out;
×
262
        }
263

264
        sdret = sd_bus_message_append(msg, "(sv)", "PIDs", "au", 1, child_pid);
27✔
265
        if (sdret < 0) {
27✔
UNCOV
266
                cgroup_err("failed to append the PID: %d\n", errno);
×
UNCOV
267
                goto out;
×
268
        }
269

270
        sdret = sd_bus_message_append(msg, "(sv)", "Slice", "s", slice_name);
27✔
271
        if (sdret < 0) {
27✔
UNCOV
272
                cgroup_err("failed to append the slice: %d\n", errno);
×
UNCOV
273
                goto out;
×
274
        }
275

276
        if (opts->delegated == 1) {
27✔
277
                sdret = sd_bus_message_append(msg, "(sv)", "Delegate", "b", 1);
27✔
278
                if (sdret < 0) {
27✔
UNCOV
279
                        cgroup_err("failed to append delegate: %d\n", errno);
×
UNCOV
280
                        goto out;
×
281
                }
282
        }
283

284
        sdret = sd_bus_message_close_container(msg);
27✔
285
        if (sdret < 0) {
27✔
UNCOV
286
                cgroup_err("failed to close the container: %d\n", errno);
×
UNCOV
287
                goto out;
×
288
        }
289

290
        sdret = sd_bus_message_append(msg, "a(sa(sv))", 0);
27✔
291
        if (sdret < 0) {
27✔
UNCOV
292
                cgroup_err("failed to append aux structure: %d\n", errno);
×
UNCOV
293
                goto out;
×
294
        }
295

296
        sdret = sd_bus_call(bus, msg, 0, &error, &reply);
27✔
297
        if (sdret < 0) {
27✔
298
                cgroup_err("sd_bus_call() failed: %d\n",
10✔
299
                           sd_bus_message_get_errno(msg));
300
                cgroup_err("error message: %s\n", error.message);
10✔
301
                goto out;
10✔
302
        }
303

304
        /* Receive the job_path from systemd */
305
        sdret = sd_bus_message_read(reply, "o", &job_path);
17✔
306
        if (sdret < 0) {
17✔
UNCOV
307
                cgroup_err("failed to read reply: %d\n", errno);
×
UNCOV
308
                goto out;
×
309
        }
310

311
        cgroup_dbg("job_path = %s\n", job_path);
17✔
312

313
        ret = clock_gettime(CLOCK_MONOTONIC, &start);
17✔
314
        if (ret < 0) {
17✔
UNCOV
315
                last_errno = errno;
×
UNCOV
316
                cgroup_err("Failed to get time: %d\n", errno);
×
UNCOV
317
                cgret = ECGOTHER;
×
UNCOV
318
                goto out;
×
319
        }
320

321
        /* The callback will null out the job_path pointer on completion */
322
        while (job_path) {
2,320✔
323
                sdret = sd_bus_process(bus, NULL);
2,303✔
324
                if (sdret < 0) {
2,303✔
UNCOV
325
                        cgroup_err("failed to process the sd bus: %d\n", errno);
×
UNCOV
326
                        goto out;
×
327
                }
328

329
                if (sdret == 0) {
2,303✔
330
                        /*
331
                         * Per the sd_bus_wait() man page, call this function after sd_bus_process
332
                         * returns zero. The wait time (usec) was somewhat arbitrarily chosen
333
                         */
334
                        sdret = sd_bus_wait(bus, 10);
2,267✔
335
                        if (sdret < 0) {
2,267✔
UNCOV
336
                                cgroup_err("failed to wait for sd bus: %d\n", errno);
×
UNCOV
337
                                goto out;
×
338
                        }
339
                }
340

341
                ret = clock_gettime(CLOCK_MONOTONIC, &now);
2,303✔
342
                if (ret < 0) {
2,303✔
UNCOV
343
                        last_errno = errno;
×
UNCOV
344
                        cgroup_err("Failed to get time: %d\n", errno);
×
UNCOV
345
                        cgret = ECGOTHER;
×
UNCOV
346
                        goto out;
×
347
                }
348

349
                if (elapsed_time(&start, &now) > USEC_PER_SEC) {
2,303✔
UNCOV
350
                        cgroup_err("The create scope command timed out\n");
×
UNCOV
351
                        goto out;
×
352
                }
353
        }
354

355
        cgret = 0;
17✔
356

357
out:
27✔
358
        if (cgret && opts->pid < 0)
27✔
359
                kill(child_pid, SIGTERM);
8✔
360

361
        sd_bus_error_free(&error);
27✔
362
        sd_bus_message_unref(msg);
27✔
363
        sd_bus_message_unref(reply);
27✔
364
        sd_bus_unref(bus);
27✔
365

366
        return cgret;
27✔
367
}
368

369
int cgroup_create_scope2(struct cgroup *cgroup, int ignore_ownership,
21✔
370
                         const struct cgroup_systemd_scope_opts * const opts)
371
{
372
        char *copy1 = NULL, *copy2 = NULL, *slash, *slice_name, *scope_name;
21✔
373
        int ret = 0;
21✔
374

375
        if (!cgroup)
21✔
UNCOV
376
                return ECGROUPNOTALLOWED;
×
377

378
        slash = strstr(cgroup->name, "/");
21✔
379
        if (!slash) {
21✔
380
                cgroup_err("cgroup name does not contain a slash: %s\n", cgroup->name);
1✔
381
                return ECGINVAL;
1✔
382
        }
383

384
        slash = strstr(slash + 1, "/");
20✔
385
        if (slash) {
20✔
386
                cgroup_err("cgroup name contains more than one slash: %s\n", cgroup->name);
1✔
387
                return ECGINVAL;
1✔
388
        }
389

390
        copy1 = strdup(cgroup->name);
19✔
391
        if (!copy1) {
19✔
UNCOV
392
                last_errno = errno;
×
UNCOV
393
                ret = ECGOTHER;
×
UNCOV
394
                goto err;
×
395
        }
396

397
        scope_name = basename(copy1);
19✔
398

399
        copy2 = strdup(cgroup->name);
19✔
400
        if (!copy2) {
19✔
UNCOV
401
                last_errno = errno;
×
UNCOV
402
                ret = ECGOTHER;
×
UNCOV
403
                goto err;
×
404
        }
405

406
        slice_name = dirname(copy2);
19✔
407

408
        ret = cgroup_create_scope(scope_name, slice_name, opts);
19✔
409
        if (ret)
19✔
410
                goto err;
11✔
411

412
        /*
413
         * Utilize cgroup_create_cgroup() to assign the requested owner/group and permissions.
414
         * cgroup_create_cgroup() can gracefully handle EEXIST if the cgroup already exists, so
415
         * we can reuse its ownership logic without penalty.
416
         */
417
        ret = cgroup_create_cgroup(cgroup, ignore_ownership);
8✔
418

419
err:
19✔
420
        if (copy1)
19✔
421
                free(copy1);
19✔
422
        if (copy2)
19✔
423
                free(copy2);
19✔
424

425
        return ret;
19✔
426
}
427

428
bool cgroup_is_systemd_enabled(void)
17✔
429
{
430
        return true;
17✔
431
}
432
#else
433
int cgroup_set_default_scope_opts(struct cgroup_systemd_scope_opts * const opts)
×
434
{
435
        cgroup_err("Systemd support not compiled\n");
×
436
        return 1;
×
437
}
438

439
int cgroup_create_scope(const char * const scope_name, const char * const slice_name,
×
440
                        const struct cgroup_systemd_scope_opts * const opts)
441
{
442
        cgroup_err("Systemd support not compiled\n");
×
443
        return 1;
×
444
}
445

446
int cgroup_create_scope2(struct cgroup *cgroup, int ignore_ownership,
×
447
                         const struct cgroup_systemd_scope_opts * const opts)
448
{
449
        cgroup_err("Systemd support not compiled\n");
×
450
        return 1;
×
451
}
452

453
bool cgroup_is_systemd_enabled(void)
×
454
{
455
        return false;
×
456
}
457
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc