Bug Summary

File:.build-ci/../keysd/tests/test-kmip-client.c
Warning:line 195, column 13
Potential memory leak

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-kmip-client.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 keysd/tests/test-keysd-kmip-client.p -I keysd/tests -I ../keysd/tests -I ../keysd/src -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 ../keysd/tests/test-kmip-client.c
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * This file is part of nvme-cli.
4 * Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
5 *
6 * Authors: Martin Belanger <martin.belanger@dell.com>
7 */
8
9#include <errno(*__errno_location ()).h>
10#include <stdbool.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15
16#include <systemd/sd-event.h>
17#include <systemd/sd-json.h>
18#include <systemd/sd-varlink.h>
19
20#include <shared/io-util.h>
21
22#include "config.h"
23#include "import.h"
24#include "kmip-client.h"
25#include "kmip-varlink.h"
26
27#define MAX_CALLS16 16
28
29/*
30 * A fake nvme-keysd-kmip. It answers GetKey after a short delay, so that
31 * calls overlap. The key of "name" is "PSK-name". It answers from server
32 * 1 when the KMS has two servers.
33 *
34 * "fail-once" fails the first call with KmsFailed.
35 */
36struct fake_call {
37 char kms[32];
38 char name[32];
39 uint64_t server;
40};
41
42static struct fake {
43 sd_event *event;
44 struct fake_call calls[MAX_CALLS16];
45 int nr_calls;
46 int in_flight;
47 int max_in_flight;
48 int in_flight_kms[2]; // for KMS "a" and "b"
49 int max_in_flight_kms;
50} fake;
51
52// Imports, done by the client.
53static struct {
54 int nr;
55 int expected; // stop the loop after this many imports
56 int fail_left; // imports to fail before one succeeds
57 bool_Bool bad_key;
58} imports;
59
60static struct kmip_client *client;
61static sd_varlink_server *server;
62
63static bool_Bool check(bool_Bool cond, const char *what)
64{
65 printf(" - %s [%s]\n", what, cond ? "PASS" : "FAIL");
66 return cond;
67}
68
69struct pending_reply {
70 sd_varlink *link;
71 struct fake_call *call;
72};
73
74static int kms_index(const char *kms)
75{
76 return strcmp(kms, "a") ? 1 : 0;
77}
78
79static int send_reply(sd_event_source *s, uint64_t usec, void *userdata)
80{
81 struct pending_reply *pr = userdata;
82 struct fake_call *call = pr->call;
83 char key[48];
84
85 fake.in_flight--;
86 fake.in_flight_kms[kms_index(call->kms)]--;
87
88 if (!strcmp(call->name, "fail-once") &&
89 call == &fake.calls[0])
90 sd_varlink_error(pr->link, KMIP_VARLINK_KMS_FAILED"io.nvme.keysd.Kmip" ".KmsFailed", NULL((void*)0));
91 else {
92 snprintf(key, sizeof(key), "PSK-%s", call->name);
93 sd_varlink_replybo(pr->link,sd_varlink_replyb((pr->link), _SD_JSON_BUILD_OBJECT_BEGIN,
_SD_JSON_BUILD_PAIR, (const char*) { "key" }, _SD_JSON_BUILD_STRING
, (const char*) { key }, _SD_JSON_BUILD_PAIR, (const char*) {
"server" }, _SD_JSON_BUILD_UNSIGNED, (uint64_t) { kms_index(
call->kms) }, _SD_JSON_BUILD_OBJECT_END)
94 SD_JSON_BUILD_PAIR_STRING("key", key),sd_varlink_replyb((pr->link), _SD_JSON_BUILD_OBJECT_BEGIN,
_SD_JSON_BUILD_PAIR, (const char*) { "key" }, _SD_JSON_BUILD_STRING
, (const char*) { key }, _SD_JSON_BUILD_PAIR, (const char*) {
"server" }, _SD_JSON_BUILD_UNSIGNED, (uint64_t) { kms_index(
call->kms) }, _SD_JSON_BUILD_OBJECT_END)
95 SD_JSON_BUILD_PAIR_UNSIGNED("server",sd_varlink_replyb((pr->link), _SD_JSON_BUILD_OBJECT_BEGIN,
_SD_JSON_BUILD_PAIR, (const char*) { "key" }, _SD_JSON_BUILD_STRING
, (const char*) { key }, _SD_JSON_BUILD_PAIR, (const char*) {
"server" }, _SD_JSON_BUILD_UNSIGNED, (uint64_t) { kms_index(
call->kms) }, _SD_JSON_BUILD_OBJECT_END)
96 kms_index(call->kms)))sd_varlink_replyb((pr->link), _SD_JSON_BUILD_OBJECT_BEGIN,
_SD_JSON_BUILD_PAIR, (const char*) { "key" }, _SD_JSON_BUILD_STRING
, (const char*) { key }, _SD_JSON_BUILD_PAIR, (const char*) {
"server" }, _SD_JSON_BUILD_UNSIGNED, (uint64_t) { kms_index(
call->kms) }, _SD_JSON_BUILD_OBJECT_END)
;
97 }
98
99 sd_varlink_unref(pr->link);
100 free(pr);
101 sd_event_source_disable_unref(s);
102
103 return 0;
104}
105
106static int fake_get_key(sd_varlink *link, sd_json_variant *params,
107 sd_varlink_method_flags_t flags, void *userdata)
108{
109 struct pending_reply *pr;
110 struct fake_call *call;
111 sd_json_variant *v;
112 uint64_t now;
113 int i;
114
115 if (fake.nr_calls == MAX_CALLS16)
116 return sd_varlink_error(link, KMIP_VARLINK_KMS_FAILED"io.nvme.keysd.Kmip" ".KmsFailed", NULL((void*)0));
117 call = &fake.calls[fake.nr_calls++];
118
119 v = sd_json_variant_by_key(params, "kms");
120 snprintf(call->kms, sizeof(call->kms), "%s",
121 v ? sd_json_variant_string(v) : "");
122 v = sd_json_variant_by_key(params, "name");
123 snprintf(call->name, sizeof(call->name), "%s",
124 v ? sd_json_variant_string(v) : "");
125 v = sd_json_variant_by_key(params, "server");
126 call->server = v ? sd_json_variant_unsigned(v) : 99;
127
128 fake.in_flight++;
129 if (fake.in_flight > fake.max_in_flight)
130 fake.max_in_flight = fake.in_flight;
131 i = kms_index(call->kms);
132 fake.in_flight_kms[i]++;
133 if (fake.in_flight_kms[i] > fake.max_in_flight_kms)
134 fake.max_in_flight_kms = fake.in_flight_kms[i];
135
136 pr = calloc(1, sizeof(*pr));
137 if (!pr)
138 return -ENOMEM12;
139 pr->link = sd_varlink_ref(link);
140 pr->call = call;
141
142 sd_event_now(fake.event, CLOCK_MONOTONIC1, &now);
143
144 return sd_event_add_time(fake.event, NULL((void*)0), CLOCK_MONOTONIC1,
145 now + 20000, 0, send_reply, pr);
146}
147
148static int test_import(const struct kmip_job *job, const char *encoded,
149 void *arg)
150{
151 char key[48];
152
153 snprintf(key, sizeof(key), "PSK-%s", job->name);
154 if (strcmp(encoded, key))
155 imports.bad_key = true1;
156
157 if (imports.fail_left) {
158 imports.fail_left--;
159 return -EIO5;
160 }
161 imports.nr++;
162
163 if (imports.nr == imports.expected)
164 sd_event_exit(fake.event, 0);
165
166 return 0;
167}
168
169/* Load nvme-keysd.conf with KMS "a" (one server) and "b" (two servers). */
170static struct keysd_config *load_cfg(void)
171{
172 static const char text[] =
173 "[kmip:a]\nserver = 192.0.2.1\n"
174 "[kmip:b]\nserver = 192.0.2.2\nserver = 192.0.2.3\n";
175 char path[] = "/tmp/nvme-keysd-client-XXXXXX";
176 struct keysd_config *cfg;
177 int fd;
178
179 fd = mkstemp(path);
180 if (fd < 0 || shr_write_all(fd, text, sizeof(text) - 1)) {
181 fprintf(stderrstderr, "cannot write the config\n");
182 exit(EXIT_FAILURE1);
183 }
184 close(fd);
185 cfg = config_load(path);
186 unlink(path);
187
188 return cfg;
189}
190
191static void set_job(struct kmip_job *job, const struct keysd_config *cfg,
192 const char *kms, const char *name)
193{
194 memset(job, 0, sizeof(*job));
195 job->kms = config_find_kmip(cfg, kms);
5
Potential memory leak
196 job->source = strdup("test.conf");
197 job->subsysnqn = strdup("nqn.test");
198 job->name = strdup(name);
2
Memory is allocated
199}
200
201static void free_jobs(struct kmip_job *jobs, size_t n)
202{
203 size_t i;
204
205 for (i = 0; i < n; i++) {
206 free(jobs[i].source);
207 free(jobs[i].subsysnqn);
208 free(jobs[i].name);
209 }
210}
211
212/*
213 * Run the client on @jobs against the fake helper, until every key is
214 * imported or @limit_usec passes.
215 */
216static void run(const char *address, const struct kmip_job *jobs, size_t n,
217 uint64_t limit_usec)
218{
219 const struct kmip_client_cfg cfg = {
220 .address = address,
221 .retry_usec = 10000,
222 .retry_max_usec = 40000,
223 .import = test_import,
224 };
225 sd_event_source *limit;
226 uint64_t now;
227
228 memset(&fake.calls, 0, sizeof(fake.calls));
229 fake.nr_calls = 0;
230 fake.max_in_flight = 0;
231 fake.max_in_flight_kms = 0;
232 imports.nr = 0;
233 imports.expected = n;
234 imports.bad_key = false0;
235
236 // A loop cannot run again after it ends: use a new one.
237 if (sd_event_new(&fake.event) < 0 ||
238 sd_varlink_server_attach_event(server, fake.event, 0) < 0) {
239 fprintf(stderrstderr, "event loop setup failed\n");
240 exit(EXIT_FAILURE1);
241 }
242
243 // A NULL callback ends the loop.
244 sd_event_now(fake.event, CLOCK_MONOTONIC1, &now);
245 if (sd_event_add_time(fake.event, &limit, CLOCK_MONOTONIC1,
246 now + limit_usec, 0, NULL((void*)0), NULL((void*)0)) < 0) {
247 fprintf(stderrstderr, "sd_event_add_time failed\n");
248 exit(EXIT_FAILURE1);
249 }
250
251 if (kmip_client_new(fake.event, &cfg, jobs, n, &client) < 0) {
252 fprintf(stderrstderr, "kmip_client_new failed\n");
253 exit(EXIT_FAILURE1);
254 }
255 sd_event_loop(fake.event);
256 kmip_client_free(client);
257 client = NULL((void*)0);
258 sd_event_source_disable_unref(limit);
259 sd_varlink_server_detach_event(server);
260 fake.event = sd_event_unref(fake.event);
261}
262
263static bool_Bool test_parallel(const char *address,
264 const struct keysd_config *cfg)
265{
266 struct kmip_job jobs[4];
267 bool_Bool pass = true1;
268 int i, b_calls = 0;
269
270 printf("test_parallel:\n");
271
272 set_job(&jobs[0], cfg, "a", "k1");
1
Calling 'set_job'
3
Returned allocated memory
273 set_job(&jobs[1], cfg, "a", "k2");
4
Calling 'set_job'
274 set_job(&jobs[2], cfg, "b", "k3");
275 set_job(&jobs[3], cfg, "b", "k4");
276 run(address, jobs, 4, 2000000);
277
278 pass &= check(imports.nr == 4 && !imports.bad_key,
279 "all keys imported");
280 pass &= check(fake.max_in_flight_kms == 1,
281 "one call at a time per KMS");
282 pass &= check(fake.max_in_flight == 2, "KMSes asked in parallel");
283
284 for (i = 0; i < fake.nr_calls; i++) {
285 if (strcmp(fake.calls[i].kms, "b"))
286 continue;
287 if (b_calls)
288 pass &= check(fake.calls[i].server == 1,
289 "next call asks the last server first");
290 else
291 pass &= check(fake.calls[i].server == 0,
292 "first call asks server 0 first");
293 b_calls++;
294 }
295 free_jobs(jobs, 4);
296
297 return pass;
298}
299
300static bool_Bool test_retry(const char *address, const struct keysd_config *cfg)
301{
302 struct kmip_job job;
303 bool_Bool pass = true1;
304
305 printf("test_retry:\n");
306
307 set_job(&job, cfg, "a", "fail-once");
308 run(address, &job, 1, 2000000);
309 pass &= check(imports.nr == 1 && fake.nr_calls == 2,
310 "KMS error retried");
311 free_jobs(&job, 1);
312
313 set_job(&job, cfg, "a", "k1");
314 imports.fail_left = 2;
315 run(address, &job, 1, 2000000);
316 pass &= check(imports.nr == 1 && fake.nr_calls == 3,
317 "import error retried");
318 free_jobs(&job, 1);
319
320 set_job(&job, cfg, "a", "k1");
321 run("/nonexistent/kmip", &job, 1, 100000);
322 pass &= check(imports.nr == 0, "no helper: key stays pending");
323 free_jobs(&job, 1);
324
325 return pass;
326}
327
328int main(void)
329{
330 char dir[] = "/tmp/test-kmip-client-XXXXXX", address[108], *abs;
331 struct keysd_config *cfg;
332 bool_Bool pass = true1;
333
334 cfg = load_cfg();
335 if (!cfg || cfg->nr_kmip != 2 || !mkdtemp(dir) ||
336 sd_varlink_server_new(&server, 0) < 0) {
337 fprintf(stderrstderr, "test setup failed\n");
338 exit(EXIT_FAILURE1);
339 }
340 // A socket address must be absolute.
341 abs = realpath(dir, NULL((void*)0));
342 if (!abs || snprintf(address, sizeof(address), "%s/kmip", abs) >=
343 (int)sizeof(address)) {
344 fprintf(stderrstderr, "socket path too long\n");
345 exit(EXIT_FAILURE1);
346 }
347 free(abs);
348
349 if (sd_varlink_server_bind_method(server, KMIP_VARLINK_GET_KEY"io.nvme.keysd.Kmip" ".GetKey",
350 fake_get_key) < 0 ||
351 sd_varlink_server_listen_address(server, address, 0600) < 0) {
352 fprintf(stderrstderr, "fake helper setup failed\n");
353 exit(EXIT_FAILURE1);
354 }
355
356 pass &= test_parallel(address, cfg);
357 pass &= test_retry(address, cfg);
358
359 sd_varlink_server_unref(server);
360 unlink(address);
361 rmdir(dir);
362 config_free(cfg);
363
364 fflush(stdoutstdout);
365 exit(pass ? EXIT_SUCCESS0 : EXIT_FAILURE1);
366}