ccan: update to get json_escape_unescape_len()
What changed, and why it matters
This commit updates a bundled helper library (CCAN) used by Core Lightning, mainly adding a new function that can unescape JSON strings of a known length rather than relying on null-terminated strings. The change also hardens the unescape logic so malformed escape sequences (for example, a backslash at the very end of the string) are rejected instead of reading past the end of the buffer. There is no direct evidence in the commit that this fixes an exploitable vulnerability in Core Lightning itself, but it is a defensive improvement that removes a potential out-of-bounds read and adds safer memory handling.
Treat as a routine defensive library update. Review whether any Core Lightning callers that currently use json_escape_unescape() with externally supplied JSON should migrate to json_escape_unescape_len() to eliminate reliance on null termination. No emergency action is indicated by this commit alone.
Security signals we found
Bounds check added after backslash before reading next character
Malformed trailing backslash now returns failure instead of reading past buffer
Stolen input is freed on error paths to avoid leaks/double-free issues
Output buffer is resized to actual decoded length
New length-aware unescape API added, but no in-tree callers shown in diff
Evidence from the diff
The commit imports a newer CCAN snapshot (init-2595-ge43c61c4) to expose json_escape_unescape_len(). The implementation is refactored into an internal unescape() helper that takes an explicit length, checks bounds before dereferencing the character after a backslash, frees stolen input on error, and resizes the output allocation to the actual decoded length. The public json_escape_unescape() is now a thin wrapper that calls unescape() with strlen(esc->s). The new json_escape_unescape_len() lets callers pass a length-delimited buffer. These changes reduce the risk of an out-of-bounds read when a JSON escape sequence ends at the buffer boundary and improve memory hygiene, but the commit does not show any Core Lightning caller switching to the length-aware API yet, so the immediate security impact is limited to the library layer.
Changed components
ccan/ccan/json_escape/json_escape.cccan/ccan/json_escape/json_escape.hccan/tools/configurator/configurator.1ccan/READMEInspect captured patch +38 / −10
diff --git a/ccan/README b/ccan/README
index 926a8236..5aee7108 100644
--- a/ccan/README
+++ b/ccan/README
@@ -1,3 +1,3 @@
CCAN imported from http://ccodearchive.net.
-CCAN version: init-2593-gca094039
+CCAN version: init-2595-ge43c61c4
diff --git a/ccan/ccan/json_escape/json_escape.c b/ccan/ccan/json_escape/json_escape.c
index daa14abe..4bbb0032 100644
--- a/ccan/ccan/json_escape/json_escape.c
+++ b/ccan/ccan/json_escape/json_escape.c
@@ -1,6 +1,7 @@
/* MIT (BSD) license - see LICENSE file for details */
#include <ccan/json_escape/json_escape.h>
#include <stdio.h>
+#include <ccan/tal/str/str.h>
struct json_escape *json_escape_string_(const tal_t *ctx,
const void *bytes, size_t len)
@@ -137,19 +138,24 @@ struct json_escape *json_escape_len(const tal_t *ctx, const char *str TAKES,
}
/* By policy, we don't handle \u. Use UTF-8. */
-const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc)
+static const char *unescape(const tal_t *ctx, const char *esc TAKES, size_t len)
{
- char *unesc = tal_arr(ctx, char, strlen(esc->s) + 1);
+ /* Fast path: can steal, and nothing to unescape. */
+ if (is_taken(esc) && !memchr(esc, '\\', len))
+ return tal_strndup(ctx, esc, len);
+
+ char *unesc = tal_arr(ctx, char, len + 1);
size_t i, n;
- for (i = n = 0; esc->s[i]; i++, n++) {
- if (esc->s[i] != '\\') {
- unesc[n] = esc->s[i];
+ for (i = n = 0; i < len; i++, n++) {
+ if (esc[i] != '\\') {
+ unesc[n] = esc[i];
continue;
}
- i++;
- switch (esc->s[i]) {
+ if (++i == len)
+ goto error;
+ switch (esc[i]) {
case 'n':
unesc[n] = '\n';
break;
@@ -168,13 +174,31 @@ const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc
case '/':
case '\\':
case '"':
- unesc[n] = esc->s[i];
+ unesc[n] = esc[i];
break;
default:
+ error:
+ if (taken(esc))
+ tal_free(esc);
return tal_free(unesc);
}
}
unesc[n] = '\0';
+ if (!tal_resize(&unesc, n + 1))
+ goto error;
+ if (taken(esc))
+ tal_free(esc);
return unesc;
}
+
+const char *json_escape_unescape(const tal_t *ctx, const struct json_escape *esc)
+{
+ return unescape(ctx, esc->s, strlen(esc->s));
+}
+
+const char *json_escape_unescape_len(const tal_t *ctx,
+ const char *esc TAKES, size_t len)
+{
+ return unescape(ctx, esc, len);
+}
diff --git a/ccan/ccan/json_escape/json_escape.h b/ccan/ccan/json_escape/json_escape.h
index 5b33432f..88d1eefc 100644
--- a/ccan/ccan/json_escape/json_escape.h
+++ b/ccan/ccan/json_escape/json_escape.h
@@ -41,4 +41,8 @@ struct json_escape *json_escape_string_(const tal_t *ctx,
/* Be very careful here! Can fail! Doesn't handle \u: use UTF-8 please. */
const char *json_escape_unescape(const tal_t *ctx,
const struct json_escape *esc);
+
+/* Be very careful here! Can fail! Doesn't handle \u: use UTF-8 please. */
+const char *json_escape_unescape_len(const tal_t *ctx,
+ const char *esc TAKES, size_t len);
#endif /* CCAN_JSON_ESCAPE_H */
diff --git a/ccan/tools/configurator/configurator.1 b/ccan/tools/configurator/configurator.1
index aaa92c52..2fe7d5a7 100644
--- a/ccan/tools/configurator/configurator.1
+++ b/ccan/tools/configurator/configurator.1
@@ -208,7 +208,7 @@ It will exit with non\-zero status if it has a problem\&. \fB1\fR means bad comm
Rusty Russell wrote \fBconfigurator\fR\&.
.SH "RESOURCES"
.sp
-Main web site: http://ccodearchive\&.net/
+Main web site: https://github\&.com/rustyrussell/ccan
.sp
Wiki: https://github\&.com/rustyrussell/ccan/wiki/
.SH "COPYING"
Why this scored 34/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.