What changed, and why it matters
This is a routine update to an internal code library (CCAN) used by Core Lightning. The main user-visible effect is fixing build failures on newer macOS and on 32-bit ARM when compiling with optimizations. It also replaces several old-style sprintf() calls with safer snprintf() versions. The sprintf() cases shown are generally low risk because the inputs are controlled (process IDs, small integers, fixed format strings), but switching to snprintf() is a defensive hardening improvement. There is no indication this fixes an active, exploitable vulnerability in running software.
Treat as a normal build-fix and defensive-hardening update. Review the snprintf() conversions for correctness, particularly the opt helpers where buffer size is computed from strlen(fmt)+strlen(arg). No urgent security response is warranted, but ensure CI covers macOS 15 SDK and 32-bit ARM builds.
Security signals we found
sprintf replaced by snprintf in multiple locations (defensive hardening)
build failure fixes on macOS 15 SDK and 32-bit ARM with -O2
removed take(NULL) pass-through tests in tal/str/path modules
no evidence of memory corruption bug or active exploit path
Evidence from the diff
The commit imports CCAN init-2602-gfd3fd70c. The functional changes visible in the diff are: (1) a configurator test for HAVE_UNALIGNED_ACCESS is corrected from sizeof(int *) * 1 to sizeof(int) + 1 so it compiles on 32-bit gcc with -O2/-Werror=array-bounds; (2) sprintf() in closefrom.c, likely.c, opt helpers, rune.c, tal.c, and many test files are replaced with snprintf(); (3) several tal/str/path tests that exercised take(NULL) pass-through behavior are removed, matching upstream CCAN behavior changes. The snprintf() conversions fix build errors on macOS 15 SDK where sprintf is marked deprecated with -Werror. Most conversions are straightforward and bounded by known buffers; a few helpers allocate strlen(fmt)+strlen(arg) bytes and use snprintf with that size, which is still safe because the format contains a single %s and no other width/precision modifiers. The removed NULL pass-through tests are behavior changes but not security-relevant.
Changed components
ccan/ccan/closefrom/closefrom.cccan/ccan/likely/likely.cccan/ccan/opt/helpers.cccan/ccan/opt/opt.cccan/ccan/rune/rune.cccan/ccan/tal/tal.cccan/tools/configurator/configurator.cvarious ccan test filesInspect captured patch +57 / −92
diff --git a/ccan/README b/ccan/README
index 8dbc5520..0710c5f1 100644
--- a/ccan/README
+++ b/ccan/README
@@ -1,3 +1,3 @@
CCAN imported from http://ccodearchive.net.
-CCAN version: init-2597-gc4760b30
+CCAN version: init-2602-gfd3fd70c
diff --git a/ccan/ccan/closefrom/closefrom.c b/ccan/ccan/closefrom/closefrom.c
index 177b05ee..e796ed29 100644
--- a/ccan/ccan/closefrom/closefrom.c
+++ b/ccan/ccan/closefrom/closefrom.c
@@ -80,7 +80,7 @@ static bool can_open_proc_pid_fd(void)
char dnam[PROC_PID_FD_LEN];
DIR *dir;
- sprintf(dnam, "/proc/%ld/fd", (long) getpid());
+ snprintf(dnam, sizeof(dnam), "/proc/%ld/fd", (long) getpid());
dir = opendir(dnam);
if (!dir)
return false;
@@ -159,7 +159,7 @@ void closefrom(int fromfd)
maxfd = sysconf(_SC_OPEN_MAX);
- sprintf(dnam, "/proc/%ld/fd", (long) getpid());
+ snprintf(dnam, sizeof(dnam), "/proc/%ld/fd", (long) getpid());
dir = try_opendir(dnam, &fromfd, maxfd);
if (!dir)
dir = try_opendir("/dev/fd", &fromfd, maxfd);
diff --git a/ccan/ccan/json_escape/_info b/ccan/ccan/json_escape/_info
index 934857f8..6d387767 100644
--- a/ccan/ccan/json_escape/_info
+++ b/ccan/ccan/json_escape/_info
@@ -32,6 +32,7 @@ int main(int argc, char *argv[])
if (strcmp(argv[1], "depends") == 0) {
printf("ccan/tal\n");
+ printf("ccan/tal/str\n");
return 0;
}
diff --git a/ccan/ccan/json_out/_info b/ccan/ccan/json_out/_info
index fbb4fa56..304652e9 100644
--- a/ccan/ccan/json_out/_info
+++ b/ccan/ccan/json_out/_info
@@ -40,7 +40,7 @@
* json_out_start(jout, NULL, '{');
* for (int i = 1; i < argc; i++) {
* char fieldname[80];
- * sprintf(fieldname, "argv%i", i);
+ * snprintf(fieldname, sizeof(fieldname), "argv%i", i);
* json_out_add(jout, fieldname,
* !can_be_json_literal(argv[i]),
* "%s", argv[i]);
diff --git a/ccan/ccan/likely/likely.c b/ccan/ccan/likely/likely.c
index 6b6e4aea..aabb51ed 100644
--- a/ccan/ccan/likely/likely.c
+++ b/ccan/ccan/likely/likely.c
@@ -84,6 +84,7 @@ char *likely_stats(unsigned int min_hits, unsigned int percent)
struct thash_iter i;
char *ret;
struct trace *t;
+ size_t maxlen;
worst = NULL;
worst_ratio = 2;
@@ -101,15 +102,16 @@ char *likely_stats(unsigned int min_hits, unsigned int percent)
if (worst_ratio * 100 > percent)
return NULL;
- ret = malloc(strlen(worst->condstr) +
- strlen(worst->file) +
- sizeof(long int) * 8 +
- sizeof("%s:%u:%slikely(%s) correct %u%% (%lu/%lu)"));
- sprintf(ret, "%s:%u:%slikely(%s) correct %u%% (%lu/%lu)",
- worst->file, worst->line,
- worst->expect ? "" : "un", worst->condstr,
- (unsigned)(worst_ratio * 100),
- worst->right, worst->count);
+ maxlen = strlen(worst->condstr) +
+ strlen(worst->file) +
+ sizeof(long int) * 8 +
+ sizeof("%s:%u:%slikely(%s) correct %u%% (%lu/%lu)");
+ ret = malloc(maxlen);
+ snprintf(ret, maxlen - 1, "%s:%u:%slikely(%s) correct %u%% (%lu/%lu)",
+ worst->file, worst->line,
+ worst->expect ? "" : "un", worst->condstr,
+ (unsigned)(worst_ratio * 100),
+ worst->right, worst->count);
thash_del(&htable, worst);
free(worst);
diff --git a/ccan/ccan/opt/helpers.c b/ccan/ccan/opt/helpers.c
index df7ee6bb..5db87bba 100644
--- a/ccan/ccan/opt/helpers.c
+++ b/ccan/ccan/opt/helpers.c
@@ -16,8 +16,9 @@
static char *arg_bad(const char *fmt, const char *arg)
{
- char *str = opt_alloc.alloc(strlen(fmt) + strlen(arg));
- sprintf(str, fmt, arg);
+ char *str = opt_alloc.alloc(strlen(fmt) + strlen(arg) + 1);
+ snprintf(str, strlen(fmt) + strlen(arg), fmt, arg);
+ str[strlen(fmt) + strlen(arg)] = '\0';
return str;
}
diff --git a/ccan/ccan/opt/opt.c b/ccan/ccan/opt/opt.c
index 9149374c..3cafedeb 100644
--- a/ccan/ccan/opt/opt.c
+++ b/ccan/ccan/opt/opt.c
@@ -296,8 +296,10 @@ void opt_log_stderr_exit(const char *fmt, ...)
char *opt_invalid_argument(const char *arg)
{
- char *str = opt_alloc.alloc(sizeof("Invalid argument '%s'") + strlen(arg));
- sprintf(str, "Invalid argument '%s'", arg);
+ size_t maxlen = sizeof("Invalid argument '%s'") + strlen(arg);
+ char *str = opt_alloc.alloc(maxlen + 1);
+ snprintf(str, maxlen, "Invalid argument '%s'", arg);
+ str[maxlen] = '\0';
return str;
}
diff --git a/ccan/ccan/opt/test/run-helpers.c b/ccan/ccan/opt/test/run-helpers.c
index 9aa41fe8..31611838 100644
--- a/ccan/ccan/opt/test/run-helpers.c
+++ b/ccan/ccan/opt/test/run-helpers.c
@@ -163,7 +163,7 @@ int main(int argc, char *argv[])
pass("Can't test error message");
} else {
char buf[30];
- sprintf(buf, "%lu", ULONG_MAX);
+ snprintf(buf, sizeof(buf), "%lu", ULONG_MAX);
ok1(!parse_args(&argc, &argv, "-a", buf, NULL));
ok1(strstr(err_output, ": -a: value '")
&& strstr(err_output, buf)
diff --git a/ccan/ccan/rune/rune.c b/ccan/ccan/rune/rune.c
index efbd8c49..7937f8cb 100644
--- a/ccan/ccan/rune/rune.c
+++ b/ccan/ccan/rune/rune.c
@@ -308,7 +308,8 @@ static const char *rune_alt_single(const tal_t *ctx,
/* Caller can't set both! */
if (fieldval_int) {
assert(!fieldval_str);
- sprintf(strfield, "%"PRIi64, *fieldval_int);
+ snprintf(strfield, sizeof(strfield) - 1, "%"PRIi64, *fieldval_int);
+ strfield[sizeof(strfield) - 1] = '\0';
fieldval_str = strfield;
fieldval_strlen = strlen(strfield);
}
diff --git a/ccan/ccan/str/test/run-STR_MAX_CHARS.c b/ccan/ccan/str/test/run-STR_MAX_CHARS.c
index fa45bad8..80663c8e 100644
--- a/ccan/ccan/str/test/run-STR_MAX_CHARS.c
+++ b/ccan/ccan/str/test/run-STR_MAX_CHARS.c
@@ -24,35 +24,35 @@ int main(void)
memset(&types, 0xFF, sizeof(types));
/* Hex versions */
- sprintf(str, "0x%llx", (unsigned long long)types.u1byte);
+ snprintf(str, sizeof(str), "0x%llx", (unsigned long long)types.u1byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u1byte));
- sprintf(str, "0x%llx", (unsigned long long)types.u2byte);
+ snprintf(str, sizeof(str), "0x%llx", (unsigned long long)types.u2byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u2byte));
- sprintf(str, "0x%llx", (unsigned long long)types.u4byte);
+ snprintf(str, sizeof(str), "0x%llx", (unsigned long long)types.u4byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u4byte));
- sprintf(str, "0x%llx", (unsigned long long)types.u8byte);
+ snprintf(str, sizeof(str), "0x%llx", (unsigned long long)types.u8byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u8byte));
/* Decimal versions */
- sprintf(str, "%u", types.u1byte);
+ snprintf(str, sizeof(str), "%u", types.u1byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u1byte));
- sprintf(str, "%d", types.s1byte);
+ snprintf(str, sizeof(str), "%d", types.s1byte);
ok1(strlen(str) < STR_MAX_CHARS(types.s1byte));
- sprintf(str, "%u", types.u2byte);
+ snprintf(str, sizeof(str), "%u", types.u2byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u2byte));
- sprintf(str, "%d", types.s2byte);
+ snprintf(str, sizeof(str), "%d", types.s2byte);
ok1(strlen(str) < STR_MAX_CHARS(types.s2byte));
- sprintf(str, "%u", types.u4byte);
+ snprintf(str, sizeof(str), "%u", types.u4byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u4byte));
- sprintf(str, "%d", types.s4byte);
+ snprintf(str, sizeof(str), "%d", types.s4byte);
ok1(strlen(str) < STR_MAX_CHARS(types.s4byte));
- sprintf(str, "%llu", (unsigned long long)types.u8byte);
+ snprintf(str, sizeof(str), "%llu", (unsigned long long)types.u8byte);
ok1(strlen(str) < STR_MAX_CHARS(types.u8byte));
- sprintf(str, "%lld", (long long)types.s8byte);
+ snprintf(str, sizeof(str), "%lld", (long long)types.s8byte);
ok1(strlen(str) < STR_MAX_CHARS(types.s8byte));
/* Pointer version. */
- sprintf(str, "%p", types.ptr);
+ snprintf(str, sizeof(str), "%p", types.ptr);
ok1(strlen(str) < STR_MAX_CHARS(types.ptr));
return exit_status();
diff --git a/ccan/ccan/str/test/run.c b/ccan/ccan/str/test/run.c
index 9917fe71..a188ff38 100644
--- a/ccan/ccan/str/test/run.c
+++ b/ccan/ccan/str/test/run.c
@@ -31,8 +31,10 @@ int main(void)
for (j = 0; j < NUM_SUBSTRINGS; j++) {
strings[n] = malloc(strlen(substrings[i])
+ strlen(substrings[j]) + 1);
- sprintf(strings[n++], "%s%s",
- substrings[i], substrings[j]);
+ snprintf(strings[n++],
+ strlen(substrings[i]) + strlen(substrings[j]),
+ "%s%s",
+ substrings[i], substrings[j]);
}
}
diff --git a/ccan/ccan/strmap/test/run-order.c b/ccan/ccan/strmap/test/run-order.c
index b541666d..8ad7aefb 100644
--- a/ccan/ccan/strmap/test/run-order.c
+++ b/ccan/ccan/strmap/test/run-order.c
@@ -43,7 +43,7 @@ int main(void)
for (i = 0; i < NUM; i++) {
char template[10];
- sprintf(template, "%08u", i);
+ snprintf(template, sizeof(template), "%08u", i);
str[i] = strdup(template);
}
diff --git a/ccan/ccan/strmap/test/run-prefix.c b/ccan/ccan/strmap/test/run-prefix.c
index 6232d71b..1bfb7412 100644
--- a/ccan/ccan/strmap/test/run-prefix.c
+++ b/ccan/ccan/strmap/test/run-prefix.c
@@ -35,7 +35,7 @@ int main(void)
for (i = 0; i < NUM; i++) {
char template[10];
- sprintf(template, "%08u", i);
+ snprintf(template, sizeof(template), "%08u", i);
str[i] = strdup(template);
}
diff --git a/ccan/ccan/strset/test/run-order.c b/ccan/ccan/strset/test/run-order.c
index 910a9f7a..c748432d 100644
--- a/ccan/ccan/strset/test/run-order.c
+++ b/ccan/ccan/strset/test/run-order.c
@@ -38,7 +38,7 @@ int main(void)
for (i = 0; i < NUM; i++) {
char template[10];
- sprintf(template, "%08u", i);
+ snprintf(template, sizeof(template), "%08u", i);
str[i] = strdup(template);
}
diff --git a/ccan/ccan/strset/test/run-prefix.c b/ccan/ccan/strset/test/run-prefix.c
index e88f2dd0..62b73b56 100644
--- a/ccan/ccan/strset/test/run-prefix.c
+++ b/ccan/ccan/strset/test/run-prefix.c
@@ -33,7 +33,7 @@ int main(void)
for (i = 0; i < NUM; i++) {
char template[10];
- sprintf(template, "%08u", i);
+ snprintf(template, sizeof(template), "%08u", i);
str[i] = strdup(template);
}
diff --git a/ccan/ccan/take/take.c b/ccan/ccan/take/take.c
index 4833bf93..437855a2 100644
--- a/ccan/ccan/take/take.c
+++ b/ccan/ccan/take/take.c
@@ -107,7 +107,7 @@ const char *taken_any(void)
return labelarr[i];
}
- sprintf(pointer_buf, "%p", takenarr[0]);
+ snprintf(pointer_buf, sizeof(pointer_buf), "%p", takenarr[0]);
return pointer_buf;
}
diff --git a/ccan/ccan/tal/path/test/run-simplify.c b/ccan/ccan/tal/path/test/run-simplify.c
index 3d1b3514..9591132d 100644
--- a/ccan/ccan/tal/path/test/run-simplify.c
+++ b/ccan/ccan/tal/path/test/run-simplify.c
@@ -6,7 +6,7 @@ int main(void)
{
char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx");
- plan_tests(87);
+ plan_tests(85);
if (!getcwd(cwd, sizeof(cwd)))
abort();
@@ -234,10 +234,6 @@ int main(void)
tal_free(path);
ok1(tal_first(ctx) == NULL);
- path = path_simplify(ctx, take(NULL));
- ok1(!path);
- ok1(tal_first(ctx) == NULL);
-
tal_free(ctx);
return exit_status();
diff --git a/ccan/ccan/tal/path/test/run-split.c b/ccan/ccan/tal/path/test/run-split.c
index 1083cc88..7b524cb0 100644
--- a/ccan/ccan/tal/path/test/run-split.c
+++ b/ccan/ccan/tal/path/test/run-split.c
@@ -6,7 +6,7 @@ int main(void)
{
char *ctx = tal_strdup(NULL, "ctx"), **split;
- plan_tests(46);
+ plan_tests(44);
split = path_split(ctx, "foo" PATH_SEP_STR "bar");
ok1(tal_parent(split) == ctx);
@@ -94,10 +94,6 @@ int main(void)
tal_free(split);
ok1(tal_first(ctx) == NULL);
- split = path_split(ctx, take(NULL));
- ok1(!split);
- ok1(tal_first(ctx) == NULL);
-
ok1(tal_first(NULL) == ctx && tal_next(ctx) == NULL && tal_first(ctx) == NULL);
tal_free(ctx);
diff --git a/ccan/ccan/tal/str/test/run-string.c b/ccan/ccan/tal/str/test/run-string.c
index 03d4eb51..a4f5f9b1 100644
--- a/ccan/ccan/tal/str/test/run-string.c
+++ b/ccan/ccan/tal/str/test/run-string.c
@@ -7,7 +7,7 @@ int main(void)
{
char *parent, *c;
- plan_tests(43);
+ plan_tests(34);
parent = tal(NULL, char);
ok1(parent);
@@ -69,20 +69,6 @@ int main(void)
ok1(tal_parent(c) == parent);
ok1(single_child(parent, c));
- /* NULL pass through works... */
- c = tal_strcat(parent, take(NULL), take(c));
- ok1(!c);
- ok1(no_children(parent));
-
- c = tal_strcat(parent, take(tal_strdup(parent, "hi")),
- take(NULL));
- ok1(!c);
- ok1(no_children(parent));
-
- c = tal_strcat(parent, take(NULL), take(NULL));
- ok1(!c);
- ok1(no_children(parent));
-
/* Appending formatted strings. */
c = tal_strdup(parent, "hi");
ok1(tal_count(c) == strlen(c) + 1);
@@ -91,10 +77,6 @@ int main(void)
ok1(tal_count(c) == strlen(c) + 1);
ok1(tal_parent(c) == parent);
- ok1(!tal_append_fmt(&c, take(NULL), "there", "world"));
- ok1(strcmp(c, "hithere world") == 0);
- ok1(tal_count(c) == strlen(c) + 1);
-
tal_free(parent);
return exit_status();
diff --git a/ccan/ccan/tal/str/test/run-strreg.c b/ccan/ccan/tal/str/test/run-strreg.c
index 55e244c6..dab3b2a3 100644
--- a/ccan/ccan/tal/str/test/run-strreg.c
+++ b/ccan/ccan/tal/str/test/run-strreg.c
@@ -21,7 +21,7 @@ int main(void)
/* If it accesses this, it will crash. */
char **invalid = (char **)1L;
- plan_tests(54);
+ plan_tests(53);
/* Simple matching. */
ok1(tal_strreg(ctx, "hello world!", "hello") == true);
ok1(tal_strreg(ctx, "hello world!", "hi") == false);
@@ -90,10 +90,6 @@ int main(void)
/* No leaks! */
ok1(no_children(ctx));
- /* NULL arg with take means always fail. */
- ok1(tal_strreg(ctx, take(NULL), "((hello|goodbye) world)",
- &b, NULL, invalid) == false);
-
/* Take string. */
a = tal_strdup(ctx, "hello world!");
ok1(tal_strreg(ctx, take(a), "([a-z]+)", &b, invalid) == true);
diff --git a/ccan/ccan/tal/str/test/run-take.c b/ccan/ccan/tal/str/test/run-take.c
index edf173f8..f12c8ea4 100644
--- a/ccan/ccan/tal/str/test/run-take.c
+++ b/ccan/ccan/tal/str/test/run-take.c
@@ -7,7 +7,7 @@ int main(void)
{
char *parent, *c;
- plan_tests(14);
+ plan_tests(11);
parent = tal(NULL, char);
ok1(parent);
@@ -38,11 +38,5 @@ int main(void)
tal_free(parent);
ok1(!taken_any());
- /* NULL pass-through. */
- c = NULL;
- ok1(tal_strdup(NULL, take(c)) == NULL);
- ok1(tal_strndup(NULL, take(c), 5) == NULL);
- ok1(tal_fmt(NULL, take(c), 0) == NULL);
-
return exit_status();
}
diff --git a/ccan/ccan/tal/str/test/run.c b/ccan/ccan/tal/str/test/run.c
index 626434c3..6c50373f 100644
--- a/ccan/ccan/tal/str/test/run.c
+++ b/ccan/ccan/tal/str/test/run.c
@@ -15,7 +15,7 @@ int main(void)
char **split, *str;
void *ctx;
- plan_tests(78);
+ plan_tests(72);
split = tal_strsplit(NULL, "hello world", " ", STR_EMPTY_OK);
ok1(!strcmp(split[0], "hello"));
ok1(!strcmp(split[1], ""));
@@ -71,9 +71,6 @@ int main(void)
tal_free(ctx);
ctx = tal_strdup(NULL, "context");
- /* Pass through NULLs from take. */
- ok1(tal_strsplit(NULL, take(NULL), " ", STR_EMPTY_OK) == NULL);
- ok1(tal_strsplit(NULL, "foo", take(NULL), STR_EMPTY_OK) == NULL);
/* tal_strsplit take string. It reallocs it to same size, but
* that sometimes causes a move, so we can't directly check
@@ -120,12 +117,6 @@ int main(void)
/* temp allocs are gone... */
ok1(no_children(ctx));
- /* tal_strjoin passthrough taken NULLs OK. */
- ok1(tal_strjoin(ctx, take(NULL), "", STR_TRAIL) == NULL);
- ok1(tal_strjoin(ctx, take(NULL), "", STR_NO_TRAIL) == NULL);
- ok1(tal_strjoin(ctx, split, take(NULL), STR_TRAIL) == NULL);
- ok1(tal_strjoin(ctx, split, take(NULL), STR_NO_TRAIL) == NULL);
-
/* tal_strjoin take strings[] */
split = tal_strsplit(ctx, "hello world", " ", STR_EMPTY_OK);
str = tal_strjoin(ctx, take(split), " there ", STR_NO_TRAIL);
diff --git a/ccan/ccan/tal/tal.c b/ccan/ccan/tal/tal.c
index e799059f..39eb2153 100644
--- a/ccan/ccan/tal/tal.c
+++ b/ccan/ccan/tal/tal.c
@@ -953,7 +953,8 @@ static bool check_err(struct tal_hdr *t, const char *errorstr,
if (errorstr) {
/* Try not to malloc: it may be corrupted. */
char msg[strlen(errorstr) + 20 + strlen(errmsg) + 1];
- sprintf(msg, "%s:%p %s", errorstr, from_tal_hdr(t), errmsg);
+ snprintf(msg, sizeof(msg), "%s:%p %s", errorstr, from_tal_hdr(t), errmsg);
+ msg[sizeof(msg) - 1] = '\0';
call_error(msg);
}
return false;
diff --git a/ccan/tools/configurator/configurator.c b/ccan/tools/configurator/configurator.c
index 7d8f6b09..4efeaba3 100644
--- a/ccan/tools/configurator/configurator.c
+++ b/ccan/tools/configurator/configurator.c
@@ -413,7 +413,7 @@ static const struct test base_tests[] = {
"#include <string.h>\n"
"int main(int argc, char *argv[]) {\n"
" (void)argc;\n"
- " char pad[sizeof(int *) * 1];\n"
+ " char pad[sizeof(int) + 1];\n"
" memcpy(pad, argv[0], sizeof(pad));\n"
" int *x = (int *)pad, *y = (int *)(pad + 1);\n"
" return *x == *y;\n"
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.