Bug Summary

File:.build-ci/../shared/tests/test-sandbox-util.c
Warning:line 224, column 2
The 1st argument to 'dup2' is -1 but should be >= 0

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -O3 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name test-sandbox-util.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model static -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -fcoverage-compilation-dir=/__w/nvme-cli/nvme-cli/.build-ci -resource-dir /usr/bin/../lib/clang/22 -include /__w/nvme-cli/nvme-cli/.build-ci/nvme-config.h -I shared/tests/test-sandbox-util.p -I shared/tests -I ../shared/tests -I ccan -I ../ccan -I . -I .. -D _FILE_OFFSET_BITS=64 -D _GNU_SOURCE -U NDEBUG -internal-isystem /usr/bin/../lib/clang/22/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/16/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu11 -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-opt-analyze-headers -analyzer-output=html -faddrsig -fdwarf2-cfi-asm -o /__w/nvme-cli/nvme-cli/.build-ci/scan-results/2026-10-24-125112-639-1 -x c ../shared/tests/test-sandbox-util.c
1// SPDX-License-Identifier: LGPL-2.1-or-later
2/*
3 * This file is part of nvme-cli.
4 * Copyright (c) 2026 SUSE Software Solutions
5 *
6 * Authors: Daniel Wagner <dwagner@suse.de>
7 */
8#include <errno(*__errno_location ()).h>
9#include <stdint.h>
10#include <fcntl.h>
11#include <stdbool.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <time.h>
16#include <unistd.h>
17
18#include <sys/ioctl.h>
19#include <sys/resource.h>
20#ifdef __linux__1
21#include <sys/syscall.h>
22#endif
23
24#include <ccan/array_size/array_size.h>
25
26#include <shared/sandbox-util.h>
27
28static bool_Bool check_bool(const char *name, bool_Bool got)
29{
30 printf(" - %s [%s]\n", name, got ? "PASS" : "FAIL");
31 return got;
32}
33
34/* What the functions write, handed back as the result. */
35static struct {
36 pid_t pid;
37 int err;
38 char text[16];
39} out;
40static int outside;
41
42/*
43 * Runs @fn with shr_sandbox_begin(), the child hands back @out.
44 *
45 * Return: what shr_sandbox_begin() returned, or -EPROTO for a result of
46 * an unexpected size.
47 */
48static int run(int (*fn)(void *arg), void *arg)
49{
50 struct shr_sandbox_output res;
51 int ret;
52
53 memset(&out, 0, sizeof(out));
54 ret = shr_sandbox_begin(&res);
55 switch (ret) {
56 case SHR_SANDBOX_OFF:
57 fn(arg);
58 break;
59 case SHR_SANDBOX_CHILD:
60 fn(arg);
61 shr_sandbox_end(&out, sizeof(out));
62 case SHR_SANDBOX_DONE:
63 if (res.len == sizeof(out))
64 memcpy(&out, res.buf, sizeof(out));
65 else
66 ret = -EPROTO71;
67 free(res.buf);
68 break;
69 }
70
71 return ret;
72}
73
74static int fn_result(void *arg)
75{
76 out.pid = getpid();
77 memcpy(out.text, "decoded", sizeof("decoded"));
78 outside = 1;
79 return 0;
80}
81
82/* The child is sandboxed already, so the code runs in it. */
83static int fn_nested(void *arg)
84{
85 struct shr_sandbox_output res;
86
87 out.err = shr_sandbox_begin(&res);
88 return 0;
89}
90
91/* A child may hand back nothing. */
92static bool_Bool run_empty(void)
93{
94 struct shr_sandbox_output res;
95 int ret = shr_sandbox_begin(&res);
96
97 if (ret == SHR_SANDBOX_CHILD)
98 shr_sandbox_end(NULL((void*)0), 0);
99 free(res.buf);
100
101 return ret == SHR_SANDBOX_DONE && !res.len;
102}
103
104static int fn_malloc(void *arg)
105{
106 size_t len = 4 << 20;
107 char *p = malloc(len);
108
109 if (!p)
110 return -ENOMEM12;
111 memset(p, 0x5a, len);
112 p = realloc(p, 2 * len);
113 if (!p)
114 return -ENOMEM12;
115 out.text[0] = p[len - 1];
116 free(p);
117 return 0;
118}
119
120/* Opening a file is not allowed. */
121static int fn_open(void *arg)
122{
123 int fd = open("/dev/null", O_RDONLY00);
124
125 if (fd < 0)
126 out.err = errno(*__errno_location ());
127 else
128 close(fd);
129 return 0;
130}
131
132/* Descriptors other than stdio are closed in the sandbox. */
133static int fn_write_fd(void *arg)
134{
135 if (write(*(int *)arg, "x", 1) < 0)
136 out.err = errno(*__errno_location ());
137 return 0;
138}
139
140/* stdio asks whether a stream is a terminal. */
141static int fn_isatty(void *arg)
142{
143 struct winsize ws;
144
145 out.err = isatty(STDIN_FILENO0) &&
146 !ioctl(STDIN_FILENO0, TIOCGWINSZ0x5413, &ws) ? 0 : ENOTTY25;
147 return 0;
148}
149
150#ifdef __linux__1
151/* FIONREAD works on a pipe, so only the filter can make it fail. */
152static int fn_ioctl(void *arg)
153{
154 int n;
155
156 if (ioctl(STDIN_FILENO0, FIONREAD0x541B, &n))
157 out.err = errno(*__errno_location ());
158 return 0;
159}
160
161/* Signal 0 only checks whether a signal could be sent. */
162static int fn_tgkill(void *arg)
163{
164 pid_t pid = *(pid_t *)arg;
165
166 if (syscall(SYS_tgkill234, pid, pid, 0))
167 out.err = errno(*__errno_location ());
168 return 0;
169}
170#endif
171
172/*
173 * A child taken over could write data of its choice to the result pipe;
174 * the caller must notice data beyond the result.
175 */
176static int fn_extra(void *arg)
177{
178 uint64_t len = sizeof(out);
179
180 if (write(3, &len, sizeof(len)) < 0 ||
181 write(3, &out, sizeof(out)) < 0 ||
182 write(3, "x", 1) < 0)
183 _exit(1);
184 _exit(0);
185}
186
187/* ... and a length beyond what the caller takes. */
188static int fn_huge(void *arg)
189{
190 uint64_t len = (uint64_t)1 << 40;
191
192 if (write(3, &len, sizeof(len)) < 0)
193 out.err = errno(*__errno_location ());
194 _exit(0);
195}
196
197/* The child cannot read the time zone, the caller loads it. */
198static int fn_localtime(void *arg)
199{
200 time_t t = 0;
201 struct tm tm;
202
203 strftime(out.text, sizeof(out.text), "%Z %z", localtime_r(&t, &tm));
204 return 0;
205}
206
207static int fn_abort(void *arg)
208{
209 abort();
210}
211
212static int fn_exit(void *arg)
213{
214 _exit(0);
215}
216
217/* Runs @fn with @fd as stdin. */
218static int run_stdin(int (*fn)(void *arg), int fd)
219{
220 int saved = dup(STDIN_FILENO0), ret;
9
Assuming that 'dup' fails
10
'saved' initialized here
221
222 dup2(fd, STDIN_FILENO0);
223 ret = run(fn, NULL((void*)0));
224 dup2(saved, STDIN_FILENO0);
11
The 1st argument to 'dup2' is -1 but should be >= 0
225 close(saved);
226
227 return ret;
228}
229
230/* A descriptor above a lowered limit, which a loop up to the limit misses */
231static bool_Bool run_high_fd(int fd)
232{
233 struct rlimit saved, low;
234 int high = 200;
235 bool_Bool ret;
236
237 if (getrlimit(RLIMIT_NOFILERLIMIT_NOFILE, &saved) || saved.rlim_cur <= high ||
238 dup2(fd, high) != high) {
239 printf(" - no descriptor above the limit [SKIP]\n");
240 return true1;
241 }
242 low = saved;
243 low.rlim_cur = 64;
244 ret = !setrlimit(RLIMIT_NOFILERLIMIT_NOFILE, &low);
245 ret &= check_bool("descriptors above the limit are closed",
246 run(fn_write_fd, &high) == SHR_SANDBOX_DONE &&
247 out.err == EBADF9);
248 setrlimit(RLIMIT_NOFILERLIMIT_NOFILE, &saved);
249 close(high);
250
251 return ret;
252}
253
254int main(void)
255{
256 char zone[sizeof(out.text)];
257 pid_t self = getpid();
258 bool_Bool pass = true1;
259 time_t t = 0;
260 struct tm tm;
261 int fds[2], pty, ret;
262
263 printf("shr_sandbox_begin:\n");
264
265 pass &= check_bool("disabled: runs in process",
266 run(fn_result, NULL((void*)0)) == SHR_SANDBOX_OFF &&
267 out.pid == self && outside == 1);
268
269 shr_sandbox_enable(true1);
270 outside = 0;
271 ret = run(fn_result, NULL((void*)0));
272 if (ret
0.1
'ret' is not equal to SHR_SANDBOX_OFF
== SHR_SANDBOX_OFF) {
1
Taking false branch
273 printf(" - no sandbox on this system [SKIP]\n");
274 return pass ? 77 : EXIT_FAILURE1;
275 }
276 pass &= check_bool("the code runs in a child",
277 ret
1.1
'ret' is not equal to SHR_SANDBOX_DONE
== SHR_SANDBOX_DONE && out.pid != self);
278 pass &= check_bool("the result is returned",
279 !strcmp(out.text, "decoded"));
2
Assuming the condition is false
280 pass &= check_bool("other memory is not", outside == 0);
3
Assuming 'outside' is not equal to 0
281 pass &= check_bool("in the sandbox, the code runs in the child",
282 run(fn_nested, NULL((void*)0)) == SHR_SANDBOX_DONE &&
283 out.err == SHR_SANDBOX_OFF);
284 pass &= check_bool("an empty result", run_empty());
285 pass &= check_bool("memory can be allocated",
286 run(fn_malloc, NULL((void*)0)) == SHR_SANDBOX_DONE &&
287 out.text[0] == 0x5a);
288 /* the time zone of the system, which only tells something if not UTC */
289 run(fn_localtime, NULL((void*)0));
290 strftime(zone, sizeof(zone), "%Z %z", localtime_r(&t, &tm));
291 if (strcmp(zone, "UTC +0000") && strcmp(zone, "GMT +0000"))
4
Assuming the condition is false
292 pass &= check_bool("the time zone of the caller is used",
293 !strcmp(out.text, zone));
294 else
295 printf(" - the time zone is UTC [SKIP]\n");
296 pass &= check_bool("files cannot be opened",
297 run(fn_open, NULL((void*)0)) == SHR_SANDBOX_DONE && out.err);
298
299 if (!pipe(fds)) {
5
Taking false branch
300 pass &= check_bool("other descriptors are closed",
301 run(fn_write_fd, &fds[1]) ==
302 SHR_SANDBOX_DONE && out.err == EBADF9);
303 pass &= run_high_fd(fds[1]);
304#ifdef __linux__1
305 pass &= check_bool("other ioctls fail with ENOTTY",
306 run_stdin(fn_ioctl, fds[0]) ==
307 SHR_SANDBOX_DONE && out.err == ENOTTY25);
308#endif
309 close(fds[0]);
310 close(fds[1]);
311 }
312
313 pty = posix_openpt(O_RDWR02 | O_NOCTTY0400);
314 if (pty >= 0) {
6
Assuming 'pty' is >= 0
7
Taking true branch
315 pass &= check_bool("terminal queries work",
316 run_stdin(fn_isatty, pty) ==
8
Calling 'run_stdin'
317 SHR_SANDBOX_DONE && !out.err);
318 close(pty);
319 } else {
320 printf(" - no pty for the terminal queries [SKIP]\n");
321 }
322
323#ifdef __linux__1
324 pass &= check_bool("other processes cannot be signaled",
325 run(fn_tgkill, &self) == SHR_SANDBOX_DONE &&
326 out.err == EPERM1);
327#endif
328 pass &= check_bool("data after the result fails",
329 run(fn_extra, NULL((void*)0)) == -EPROTO71);
330 pass &= check_bool("a result too large fails",
331 run(fn_huge, NULL((void*)0)) == -EPROTO71);
332 pass &= check_bool("abort() in the child fails",
333 run(fn_abort, NULL((void*)0)) == -EPROTO71);
334 pass &= check_bool("exit without a result fails",
335 run(fn_exit, NULL((void*)0)) == -EPROTO71);
336
337 return pass ? EXIT_SUCCESS0 : EXIT_FAILURE1;
338}