summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHauke Mehrtens2026-04-09 20:23:01 +0000
committerHauke Mehrtens2026-06-18 22:06:50 +0000
commit6f01162d29d6c9412f8c98f365a4e861274d5ff7 (patch)
tree5681bc2eb4f4eb83a0e4bf472a30605811ff34aa
parentafc0fa8680e2076af534c5ebc4dd73f5d8658e25 (diff)
downloadlibubox-6f01162d29d6c9412f8c98f365a4e861274d5ff7.tar.gz
blobmsg_json: floor strbuf size and tighten the post-format guard
setup_strbuf() initialised the strbuf size from blob_len(attr). For attributes with zero or very small payloads this resulted in a malloc(0) call, whose return value is implementation-defined: glibc hands back a non-null pointer, but other allocators may return NULL, which made blobmsg_format_json_with_cb() and blobmsg_format_json_value_with_cb() fail for valid but empty blobs. Floor the initial allocation at 16 bytes so the strbuf is always usable regardless of input size; the buffer still grows on demand in blobmsg_puts(). 16 bytes is enough to hold the literal scalars ("null", "true", "false") and small integer serialisations (int8/int16/int32) without an immediate realloc; int64 and double formatting still triggers one extra realloc, which is acceptable. With s->len now guaranteed to be >= 16 whenever s->buf is non-NULL, the post-format guard if (!s.len) { free(s.buf); return NULL; } in both formatters becomes dead code, so empty/invalid attributes that produced no output are returned as a freshly-malloc'd empty buffer instead of NULL. The original intent of the guard was to detect "nothing was written"; the correct expression for that is !s.pos. Switch the check accordingly so callers continue to see NULL on attributes that produce no output (and the leftover buffer is freed instead of being leaked through the caller as an empty string). Finally, give the floor/grow-slack value a name, STRBUF_MIN_SIZE, and a short comment explaining what it is and why; the same value was already used as the realloc slack in blobmsg_puts(), and using the named constant in both places makes the relationship explicit. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de> (cherry picked from commit 6351fe552162394429f263feb842cad3735110e7)
-rw-r--r--blobmsg_json.c20
1 files changed, 17 insertions, 3 deletions
diff --git a/blobmsg_json.c b/blobmsg_json.c
index 0fa5d85..49eea61 100644
--- a/blobmsg_json.c
+++ b/blobmsg_json.c
@@ -122,6 +122,16 @@ struct strbuf {
int indent_level;
};
+/*
+ * Minimum and growth slack for the JSON strbuf. The minimum size
+ * keeps malloc(0) out of setup_strbuf() and is large enough to hold
+ * any short scalar serialisation ("null", "true", "false", small
+ * numbers) without an immediate realloc. The same value is added on
+ * each grow in blobmsg_puts() so that successive small writes are
+ * amortised across a few extra bytes per realloc.
+ */
+#define STRBUF_MIN_SIZE 16
+
static bool blobmsg_puts(struct strbuf *s, const char *c, size_t len)
{
size_t new_len;
@@ -131,7 +141,9 @@ static bool blobmsg_puts(struct strbuf *s, const char *c, size_t len)
return true;
if (s->len - s->pos <= len) {
- new_len = s->len + 16 + len;
+ if (len > SIZE_MAX - STRBUF_MIN_SIZE - s->len)
+ return false;
+ new_len = s->len + STRBUF_MIN_SIZE + len;
new_buf = realloc(s->buf, new_len);
if (!new_buf)
return false;
@@ -303,6 +315,8 @@ static void blobmsg_format_json_list(struct strbuf *s, struct blob_attr *attr, i
static void setup_strbuf(struct strbuf *s, struct blob_attr *attr, blobmsg_json_format_t cb, void *priv, int indent)
{
s->len = blob_len(attr);
+ if (s->len < STRBUF_MIN_SIZE)
+ s->len = STRBUF_MIN_SIZE;
s->buf = malloc(s->len);
s->pos = 0;
s->custom_format = cb;
@@ -333,7 +347,7 @@ char *blobmsg_format_json_with_cb(struct blob_attr *attr, bool list, blobmsg_jso
else
blobmsg_format_element(&s, attr, false, false);
- if (!s.len) {
+ if (!s.pos) {
free(s.buf);
return NULL;
}
@@ -360,7 +374,7 @@ char *blobmsg_format_json_value_with_cb(struct blob_attr *attr, blobmsg_json_for
blobmsg_format_element(&s, attr, true, false);
- if (!s.len) {
+ if (!s.pos) {
free(s.buf);
return NULL;
}