Bug Summary

File:.build-ci/../src/global-ctx.c
Warning:line 233, column 11
Potential leak of memory pointed to by 'opts'

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 global-ctx.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 nvme.p -I . -I .. -I src -I ../src -I ccan -I ../ccan -I libnvme/src -I ../libnvme/src -I /usr/include/json-c -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 ../src/global-ctx.c
1// SPDX-License-Identifier: GPL-2.0-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 <fcntl.h>
10#include <stdbool.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14
15#include <libnvme-mi.h>
16#include <libnvme.h>
17
18#include <shared/string-util.h>
19
20#include "argconfig.h"
21#include "args.h"
22#include "cleanup.h"
23#include "global-ctx.h"
24#include "logging.h"
25#include "nvme-print.h"
26#include "privsep.h"
27
28static int check_arg_dev(int argc, char **argv)
29{
30 if (!nvme_args.device) {
31 errno(*__errno_location ()) = EINVAL22;
32 nvme_show_perror(argv[0]);
33 return -EINVAL22;
34 }
35 return 0;
36}
37
38static int get_transport_handle(struct libnvme_global_ctx *ctx, int argc,
39 char **argv, int flags,
40 struct libnvme_transport_handle **hdl)
41{
42 char *devname;
43 int ret;
44
45 ret = check_arg_dev(argc, argv);
46 if (ret)
47 return ret;
48
49 devname = nvme_args.device;
50
51 ret = libnvme_open(ctx, devname, flags, hdl);
52 if (ret)
53 nvme_show_err(ret, devname);
54
55 return ret;
56}
57
58void put_transport_handle(struct libnvme_transport_handle *hdl)
59{
60 libnvme_close(hdl);
61}
62
63static void setup_transport_handle(struct libnvme_global_ctx *ctx,
64 struct libnvme_transport_handle *hdl,
65 struct argconfig_commandline_options *opts)
66{
67 libnvme_transport_handle_set_submit_entry(hdl, nvme_submit_entry);
68 libnvme_transport_handle_set_submit_exit(hdl, nvme_submit_exit);
69 libnvme_transport_handle_set_decide_retry(hdl, nvme_decide_retry);
70
71#ifdef CONFIG_MI
72 if (libnvme_transport_handle_is_mi(hdl)) {
73 struct libnvme_mi_ep *ep = libnvme_transport_handle_get_mi_ep(hdl);
74 if (ep) {
75 libnvme_mi_ep_set_submit_entry(ep, nvme_mi_submit_entry);
76 libnvme_mi_ep_set_submit_exit(ep, nvme_mi_submit_exit);
77 }
78 }
79#endif
80
81 libnvme_set_dry_run(ctx, nvme_args.dry_run);
82 if (nvme_args.timeout != NVME_DEFAULT_IOCTL_TIMEOUT0)
83 libnvme_transport_handle_set_timeout(hdl, nvme_args.timeout);
84}
85
86/*
87 * Mirror of libnvme's test sysfs root. libnvme keeps its own copy, set
88 * through libnvme_set_test_sysfs_dir(); this one covers the sysfs
89 * attributes nvme-cli reads by path rather than through libnvme.
90 */
91static char *test_sysfs_dir;
92
93static int set_test_sysfs_dir(const char *path)
94{
95 char *dup = strdup(path);
96
97 if (!dup)
98 return -ENOMEM12;
99
100 free(test_sysfs_dir);
101 test_sysfs_dir = dup;
102
103 return 0;
104}
105
106int nvme_sysfs_ctrl_path(const char *ctrl_name, char **path)
107{
108 if (asprintf(path, "%s/sys/class/nvme/%s",
109 test_sysfs_dir ? test_sysfs_dir : "", ctrl_name) < 0)
110 return -ENOMEM12;
111
112 return 0;
113}
114
115enum set_option_key {
116 SET_FORCE_4K,
117 SET_MI_PROBE_ENABLED,
118 SET_TEST_BASE_DIR,
119 SET_TEST_SYSFS_DIR,
120 SET_KEYS,
121};
122
123static const char * const set_option_keys[] = {
124 [SET_FORCE_4K] = "force-4k",
125 [SET_MI_PROBE_ENABLED] = "mi-probe-enabled",
126 [SET_TEST_BASE_DIR] = "test-base-dir",
127 [SET_TEST_SYSFS_DIR] = "test-sysfs-dir",
128};
129
130struct set_option {
131 enum set_option_key key;
132 char *val;
133};
134
135/* --set-options "key=value[,key=value,...]", split in @buf. */
136struct set_options {
137 char *buf;
138 struct set_option *opts;
139 unsigned int count;
140 /* The first invalid key=value, or its unknown key. */
141 char *missing_eq;
142 char *unknown_key;
143};
144
145static bool_Bool is_true(const char *val)
146{
147 return !strcmp(val, "1") ||
148 !strcasecmp(val, "true") ||
149 !strncasecmp(val, "enable", 6);
150}
151
152/* Splits @so->buf into @so->opts up to the first invalid option. */
153static void parse_set_options(struct set_options *so)
154{
155 char *p = so->buf, *kv, *val;
156 int key;
157
158 while ((kv = shr_strsep(&p, ",")) != NULL((void*)0)) {
159 if (!*kv)
160 continue;
161
162 val = strchr(kv, '=')_Generic (0 ? (kv) : (void *) 1, const void *: (const char *)
(strchr (kv, '=')), default: strchr (kv, '='))
;
163 if (!val) {
164 so->missing_eq = kv;
165 return;
166 }
167 *val++ = '\0';
168
169 for (key = 0; key < SET_KEYS; key++)
170 if (!strcmp(kv, set_option_keys[key]))
171 break;
172 if (key == SET_KEYS) {
173 so->unknown_key = kv;
174 return;
175 }
176
177 so->opts[so->count].key = key;
178 so->opts[so->count].val = val;
179 so->count++;
180 }
181}
182
183static int apply_set_option(struct libnvme_global_ctx *ctx,
184 const struct set_option *opt)
185{
186 int ret = 0;
187
188 switch (opt->key) {
189 case SET_FORCE_4K:
190 libnvme_set_force_4k(ctx, is_true(opt->val));
191 break;
192 case SET_MI_PROBE_ENABLED:
193 libnvme_set_mi_probe_enabled(ctx, is_true(opt->val));
194 break;
195 case SET_TEST_BASE_DIR:
196 ret = libnvme_set_test_base_dir(ctx, opt->val);
197 break;
198 case SET_TEST_SYSFS_DIR:
199 ret = libnvme_set_test_sysfs_dir(ctx, opt->val);
200 if (!ret)
201 ret = set_test_sysfs_dir(opt->val);
202 break;
203 case SET_KEYS:
204 break;
205 }
206
207 if (ret)
208 nvme_show_error("--set-options: failed to set '%s=%s': %s",nvme_show_message(1, "--set-options: failed to set '%s=%s': %s"
, set_option_keys[opt->key], opt->val, libnvme_strerror
(-ret))
209 set_option_keys[opt->key], opt->val,nvme_show_message(1, "--set-options: failed to set '%s=%s': %s"
, set_option_keys[opt->key], opt->val, libnvme_strerror
(-ret))
210 libnvme_strerror(-ret))nvme_show_message(1, "--set-options: failed to set '%s=%s': %s"
, set_option_keys[opt->key], opt->val, libnvme_strerror
(-ret))
;
211
212 return ret;
213}
214
215/*
216 * nvme_apply_set_options() - apply --set-options to @ctx.
217 *
218 * Returns 0 on success, -EINVAL for unknown keys or missing '='.
219 */
220static int nvme_apply_set_options(struct libnvme_global_ctx *ctx,
221 const char *str)
222{
223 __cleanup_free__attribute__((cleanup(shr_freep))) struct set_option *opts = NULL((void*)0);
224 __cleanup_free__attribute__((cleanup(shr_freep))) char *buf = strdup(str);
225 struct set_options so = { .buf = buf };
226 unsigned int i, n = 1;
227 int ret;
228
229 for (i = 0; str[i]; i++)
11
Loop condition is false. Execution continues on line 231
230 n += str[i] == ',';
231 opts = calloc(n, sizeof(*opts));
12
Memory is allocated
232 if (!buf || !opts)
13
Assuming 'buf' is null
233 return -ENOMEM12;
14
Potential leak of memory pointed to by 'opts'
234 so.opts = opts;
235
236 parse_set_options(&so);
237
238 for (i = 0; i < so.count; i++) {
239 ret = apply_set_option(ctx, &so.opts[i]);
240 if (ret)
241 return ret;
242 }
243
244 if (so.missing_eq) {
245 nvme_show_error("--set-options: missing '=' in '%s'",nvme_show_message(1, "--set-options: missing '=' in '%s'", so
.missing_eq)
246 so.missing_eq)nvme_show_message(1, "--set-options: missing '=' in '%s'", so
.missing_eq)
;
247 return -EINVAL22;
248 }
249 if (so.unknown_key) {
250 nvme_show_error("--set-options: unknown key '%s'",nvme_show_message(1, "--set-options: unknown key '%s'", so.unknown_key
)
251 so.unknown_key)nvme_show_message(1, "--set-options: unknown key '%s'", so.unknown_key
)
;
252 return -EINVAL22;
253 }
254
255 return 0;
256}
257
258static int __nvme_create_global_ctx(struct libnvme_global_ctx **pctx)
259{
260 __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0);
261 int err;
262
263 ctx = libnvme_create_global_ctx();
264 if (!ctx)
6
Assuming 'ctx' is non-null
7
Taking false branch
265 return -ENOMEM12;
266 nvme_privsep_attach(ctx);
267
268 log_level = map_log_level(nvme_args.verbose, nvme_args.quiet);
269 libnvme_set_logging_file(ctx, stderrstderr);
270 libnvme_set_logging_level(ctx, log_level, false0, false0);
271 libnvme_set_dry_run(ctx, nvme_args.dry_run);
272
273 if (nvme_args.set_options) {
8
Assuming field 'set_options' is non-null
9
Taking true branch
274 err = nvme_apply_set_options(ctx, nvme_args.set_options);
10
Calling 'nvme_apply_set_options'
275 if (err)
276 return err;
277 }
278
279 *pctx = ctx;
280 ctx = NULL((void*)0);
281
282 return 0;
283}
284
285int nvme_create_global_ctx_hostnqn(struct libnvme_global_ctx **pctx,
286 const char *hostnqn_arg,
287 const char *hostid_arg,
288 char **hostnqn, char **hostid)
289{
290 __cleanup_nvme_global_ctx__attribute__((cleanup(cleanup_nvme_global_ctx))) struct libnvme_global_ctx *ctx = NULL((void*)0);
291 __cleanup_free__attribute__((cleanup(shr_freep))) char *hnqn = NULL((void*)0);
292 __cleanup_free__attribute__((cleanup(shr_freep))) char *hid = NULL((void*)0);
293 int err;
294
295 err = __nvme_create_global_ctx(&ctx);
5
Calling '__nvme_create_global_ctx'
296 if (err)
297 return err;
298
299 libnvme_set_ioctl_probing(ctx, !nvme_args.no_ioctl_probing);
300
301#ifdef CONFIG_FABRICS
302 err = libnvmf_host_get_ids(ctx, hostnqn_arg, hostid_arg, &hnqn, &hid);
303 if (err)
304 return err;
305
306 libnvme_set_hostnqn(ctx, hnqn);
307 libnvme_set_hostid(ctx, hid);
308#endif
309
310 if (hostnqn) {
311 *hostnqn = hnqn;
312 hnqn = NULL((void*)0);
313 }
314 if (hostid) {
315 *hostid = hid;
316 hid = NULL((void*)0);
317 }
318
319 *pctx = ctx;
320 ctx = NULL((void*)0);
321
322 return 0;
323}
324
325int nvme_create_global_ctx(struct libnvme_global_ctx **pctx)
326{
327 return nvme_create_global_ctx_hostnqn(pctx, NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0));
4
Calling 'nvme_create_global_ctx_hostnqn'
328}
329
330int parse_and_open(struct libnvme_global_ctx **ctx,
331 struct libnvme_transport_handle **hdl, int argc, char **argv,
332 const char *desc, struct argconfig_commandline_options *opts)
333{
334 struct libnvme_transport_handle *hdl_new;
335 struct libnvme_global_ctx *ctx_new;
336 int ret;
337
338 ret = parse_args(argc, argv, desc, opts);
339 if (ret)
340 return ret;
341
342 ret = nvme_create_global_ctx(&ctx_new);
343 if (ret)
344 return ret;
345
346 ret = get_transport_handle(ctx_new, argc, argv, O_RDONLY00, &hdl_new);
347 if (ret) {
348 libnvme_free_global_ctx(ctx_new);
349 return -ENXIO6;
350 }
351
352 setup_transport_handle(ctx_new, hdl_new, opts);
353
354 *ctx = ctx_new;
355 *hdl = hdl_new;
356
357 return 0;
358}
359
360int open_exclusive(struct libnvme_global_ctx **ctx,
361 struct libnvme_transport_handle **hdl, int argc, char **argv,
362 int ignore_exclusive,
363 struct argconfig_commandline_options *opts)
364{
365 struct libnvme_transport_handle *hdl_new;
366 struct libnvme_global_ctx *ctx_new;
367 int flags = O_RDONLY00;
368 int ret;
369
370 if (!ignore_exclusive)
1
Assuming 'ignore_exclusive' is not equal to 0
2
Taking false branch
371 flags |= O_EXCL0200;
372
373 ret = nvme_create_global_ctx(&ctx_new);
3
Calling 'nvme_create_global_ctx'
374 if (ret)
375 return ret;
376
377 ret = get_transport_handle(ctx_new, argc, argv, flags, &hdl_new);
378 if (ret) {
379 libnvme_free_global_ctx(ctx_new);
380 return ret;
381 }
382
383 setup_transport_handle(ctx_new, hdl_new, opts);
384
385 *ctx = ctx_new;
386 *hdl = hdl_new;
387
388 return 0;
389}