From 2982bfb1c325ca139dc4b2ca891c69c3ab5f5a7e Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:13:42 +0200 Subject: [PATCH 01/21] blob: fix wrong type for realloc result in blob_buffer_grow() The local variable holding the realloc() result was declared as struct blob_buf * but buf->buf is void *. Use void * to match the actual type and avoid confusion. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- blob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blob.c b/blob.c index abcc9f2..735e5d5 100644 --- a/blob.c +++ b/blob.c @@ -21,7 +21,7 @@ static bool blob_buffer_grow(struct blob_buf *buf, int minlen) { - struct blob_buf *new; + void *new; int delta = ((minlen / 256) + 1) * 256; new = realloc(buf->buf, buf->buflen + delta); if (new) { From 78c20f6c8579c98a992c0a378850edeb5a63aace Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:15:02 +0200 Subject: [PATCH 02/21] json_script: convert recursive __json_script_file_free() to iterative The recursive implementation could overflow the stack with deeply chained file lists. Use an iterative loop instead. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- json_script.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/json_script.c b/json_script.c index 7177e9c..ddcb3d1 100644 --- a/json_script.c +++ b/json_script.c @@ -639,13 +639,11 @@ static void __json_script_file_free(struct json_script_file *f) { struct json_script_file *next; - if (!f) - return; - - next = f->next; - free(f); - - __json_script_file_free(next); + while (f) { + next = f->next; + free(f); + f = next; + } } void From e7c13bf8cbca063e0975c5e57abc2d704e23548c Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:37:47 +0200 Subject: [PATCH 03/21] usock: fix off-by-one in nanosecond normalization in poll_restart() When tv_nsec equals exactly 1000000000 (1 second), the > comparison misses this case, leaving an invalid timespec value that could cause incorrect timeout calculations. Use >= to properly normalize, matching the correct pattern already used in usock_inet_timeout(). Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- usock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usock.c b/usock.c index 2ac84de..18ba0fe 100644 --- a/usock.c +++ b/usock.c @@ -112,7 +112,7 @@ static int poll_restart(struct pollfd *fds, int nfds, int timeout) clock_gettime(CLOCK_MONOTONIC, &ts); ts.tv_nsec += msec * 1000000; - if (ts.tv_nsec > 1000000000) { + if (ts.tv_nsec >= 1000000000) { ts.tv_sec++; ts.tv_nsec -= 1000000000; } From 68b3f1588de472f531bedfd1e3b9243019699d88 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:37:54 +0200 Subject: [PATCH 04/21] uloop: usock: add error checking for fcntl and remove duplicate include Add error checking for fcntl F_GETFL/F_GETFD calls: if fcntl returns -1 on error, ORing flag bits with -1 produces garbage values. Check the return value before modifying flags. Also remove duplicate #include in usock.c. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- uloop.c | 17 ++++++++++++----- usock.c | 25 ++++++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/uloop.c b/uloop.c index fa86675..ef275c6 100644 --- a/uloop.c +++ b/uloop.c @@ -121,8 +121,15 @@ static struct uloop_fd waker_fd = { static void waker_init_fd(int fd) { - fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); - fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + int flags; + + flags = fcntl(fd, F_GETFD); + if (flags >= 0) + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + + flags = fcntl(fd, F_GETFL); + if (flags >= 0) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); } static int waker_init(void) @@ -235,7 +242,7 @@ static void uloop_run_events(int64_t timeout) int uloop_fd_add(struct uloop_fd *sock, unsigned int flags) { - unsigned int fl; + int fl; int ret; if (!(flags & (ULOOP_READ | ULOOP_WRITE | ULOOP_PRIORITY))) @@ -243,8 +250,8 @@ int uloop_fd_add(struct uloop_fd *sock, unsigned int flags) if (!sock->registered && !(flags & ULOOP_BLOCKING)) { fl = fcntl(sock->fd, F_GETFL, 0); - fl |= O_NONBLOCK; - fcntl(sock->fd, F_SETFL, fl); + if (fl >= 0) + fcntl(sock->fd, F_SETFL, fl | O_NONBLOCK); } ret = register_poll(sock, flags); diff --git a/usock.c b/usock.c index 18ba0fe..cf82bd2 100644 --- a/usock.c +++ b/usock.c @@ -28,18 +28,25 @@ #include #include #include -#include #include "usock.h" #include "utils.h" static void usock_set_flags(int sock, unsigned int type) { - if (!(type & USOCK_NOCLOEXEC)) - fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC); + int flags; + + if (!(type & USOCK_NOCLOEXEC)) { + flags = fcntl(sock, F_GETFD); + if (flags >= 0) + fcntl(sock, F_SETFD, flags | FD_CLOEXEC); + } - if (type & USOCK_NONBLOCK) - fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK); + if (type & USOCK_NONBLOCK) { + flags = fcntl(sock, F_GETFL); + if (flags >= 0) + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + } } static int usock_connect(int type, struct sockaddr *sa, int sa_len, int family, int socktype, bool server) @@ -202,6 +209,7 @@ int usock_inet_timeout(int type, const char *host, const char *service, int n_candidates, n_active = 0; int sock = -1; int fd, delay, i, j; + int flags; if (getaddrinfo(host, service, &hints, &result)) return -1; @@ -292,8 +300,11 @@ int usock_inet_timeout(int type, const char *host, const char *service, } if (sock >= 0) { - if (!(type & USOCK_NONBLOCK)) - fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) & ~O_NONBLOCK); + if (!(type & USOCK_NONBLOCK)) { + flags = fcntl(sock, F_GETFL); + if (flags >= 0) + fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); + } if (addr) memcpy(addr, rp->ai_addr, rp->ai_addrlen); } From 03821f942c499841c8c9fd52ebf7f4d4a97f752b Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:24:13 +0200 Subject: [PATCH 05/21] uloop: fix undefined behavior in signal bit operations for signals > 32 set_signo() and get_signo() use 1u (unsigned int, 32-bit) as the base for left-shifting by (signo - 1). For real-time signals with signal numbers 33-64, this shift exceeds the width of unsigned int, which is undefined behavior per the C standard. Use UINT64_C(1) to match the uint64_t type of the signums variable and correctly handle all 64 possible signals. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- uloop.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uloop.c b/uloop.c index ef275c6..403218b 100644 --- a/uloop.c +++ b/uloop.c @@ -85,12 +85,12 @@ int uloop_fd_add(struct uloop_fd *sock, unsigned int flags); static void set_signo(uint64_t *signums, int signo) { if (signo >= 1 && signo <= 64) - *signums |= (1u << (signo - 1)); + *signums |= (UINT64_C(1) << (signo - 1)); } static bool get_signo(uint64_t signums, int signo) { - return (signo >= 1) && (signo <= 64) && (signums & (1u << (signo - 1))); + return (signo >= 1) && (signo <= 64) && (signums & (UINT64_C(1) << (signo - 1))); } static void signal_consume(struct uloop_fd *fd, unsigned int events) From e6e6fd83e26d8677f15c78b7b2e30715c7522718 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:24:30 +0200 Subject: [PATCH 06/21] blobmsg: fix policy name length overflow and add bounds check in blobmsg_parse() pslen was uint8_t, silently truncating policy name lengths over 255 bytes. This could cause false matches or missed matches when comparing against blobmsg_namelen() which returns uint16_t. Change pslen to uint16_t to match. Also add a bounds check to reject policy_len values above 4096 to guard against stack overflow from the alloca call. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- blobmsg.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/blobmsg.c b/blobmsg.c index d87d607..af3bd23 100644 --- a/blobmsg.c +++ b/blobmsg.c @@ -163,18 +163,30 @@ int blobmsg_parse_array(const struct blobmsg_policy *policy, int policy_len, return 0; } +/* + * Upper bound on the number of policy entries blobmsg_parse() will + * accept. The pslen scratch array is allocated on the stack via + * alloca(); 4096 entries cap the per-call stack use at + * 4096 * sizeof(uint16_t) = 8 KiB, which comfortably fits the typical + * 8 KiB thread default while being far above any realistic policy + * size used in practice (a few dozen entries). + */ +#define BLOBMSG_PARSE_MAX_POLICY 4096 + int blobmsg_parse(const struct blobmsg_policy *policy, int policy_len, struct blob_attr **tb, void *data, unsigned int len) { const struct blobmsg_hdr *hdr; struct blob_attr *attr; - uint8_t *pslen; + uint16_t *pslen; int i; memset(tb, 0, policy_len * sizeof(*tb)); if (!data || !len) return -EINVAL; - pslen = alloca(policy_len); + if (policy_len < 0 || policy_len > BLOBMSG_PARSE_MAX_POLICY) + return -ENOMEM; + pslen = alloca(policy_len * sizeof(*pslen)); for (i = 0; i < policy_len; i++) { if (!policy[i].name) continue; From d30b9cc1a02de4a0a06b917b2eb8b0723211382c Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:47:39 +0200 Subject: [PATCH 07/21] usock: fix integer overflow in timeout calculations usock_timeout_remaining() and poll_restart() compute millisecond differences by multiplying a time_t difference by 1000 and storing the result in int. For large timeouts (>~24 days), this overflows int and produces incorrect values, turning a long timeout into an immediate expiry. Use int64_t for the intermediate calculation and clamp to INT_MAX before returning. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- usock.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/usock.c b/usock.c index cf82bd2..b2a2e99 100644 --- a/usock.c +++ b/usock.c @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include #include "usock.h" #include "utils.h" @@ -113,6 +115,7 @@ usock_inet_notimeout(int type, struct addrinfo *result, void *addr) static int poll_restart(struct pollfd *fds, int nfds, int timeout) { struct timespec ts, cur; + int64_t remaining; int msec = timeout % 1000; int ret; @@ -131,10 +134,11 @@ static int poll_restart(struct pollfd *fds, int nfds, int timeout) return ret; clock_gettime(CLOCK_MONOTONIC, &cur); - timeout = (ts.tv_sec - cur.tv_sec) * 1000; - timeout += (ts.tv_nsec - cur.tv_nsec) / 1000000; - if (timeout <= 0) + remaining = ((int64_t)ts.tv_sec - (int64_t)cur.tv_sec) * 1000; + remaining += (ts.tv_nsec - cur.tv_nsec) / 1000000; + if (remaining <= 0) return 0; + timeout = remaining > INT_MAX ? INT_MAX : (int)remaining; } } @@ -152,13 +156,16 @@ static int usock_check_connect(int fd) static int usock_timeout_remaining(struct timespec *deadline) { struct timespec cur; - int msec; + int64_t msec; clock_gettime(CLOCK_MONOTONIC, &cur); - msec = (deadline->tv_sec - cur.tv_sec) * 1000; + msec = ((int64_t)deadline->tv_sec - (int64_t)cur.tv_sec) * 1000; msec += (deadline->tv_nsec - cur.tv_nsec) / 1000000; - return msec > 0 ? msec : 0; + if (msec > INT_MAX) + return INT_MAX; + + return msec > 0 ? (int)msec : 0; } #define USOCK_MAX_CANDIDATES 8 From 406e342bb900cda5ce56481895db85a86a8f5dd7 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 00:49:03 +0200 Subject: [PATCH 08/21] udebug: fix double off-by-one in udebug_entry_vprintf() Two off-by-one errors cause silent string truncation: 1. The check after the first vsnprintf uses <= instead of <. When the output needs exactly UDEBUG_MIN_ALLOC_LEN chars, vsnprintf returns that count to indicate truncation, but the <= condition skips the reallocation path, silently using the truncated result. 2. After reallocating len+1 bytes, the second vsnprintf is called with len as the size limit. Since vsnprintf writes at most size-1 chars plus a null terminator, the last character is always lost. Pass len+1 to use the full allocated buffer. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- udebug.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/udebug.c b/udebug.c index e39a32c..1b43f69 100644 --- a/udebug.c +++ b/udebug.c @@ -625,14 +625,14 @@ int udebug_entry_vprintf(struct udebug_buf *buf, const char *fmt, va_list ap) va_copy(ap2, ap); len = vsnprintf(str, UDEBUG_MIN_ALLOC_LEN, fmt, ap2); va_end(ap2); - if (len <= UDEBUG_MIN_ALLOC_LEN) + if (len < UDEBUG_MIN_ALLOC_LEN) goto out; if (ptr->len + len > buf->data_size / 2) return -1; udebug_buf_alloc(buf, ofs, len + 1); - len = vsnprintf(str, len, fmt, ap); + len = vsnprintf(str, len + 1, fmt, ap); out: ptr->len += len; From 700eca0bac660268369ed7c77241a74971a67ee6 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 22:21:55 +0200 Subject: [PATCH 09/21] blobmsg_json: fix integer overflow in blobmsg_puts() The strbuf struct used 'int' for its len and pos fields, and blobmsg_puts() took an 'int' len parameter. When formatting very large JSON structures, the check 's->pos + len >= s->len' could overflow, wrapping to a negative value. This caused the bounds check to be bypassed, leading to a heap buffer overflow as data was written past the allocated buffer without triggering a realloc. Fix this by changing the strbuf len/pos fields and the blobmsg_puts() len parameter to size_t, and restructuring the bounds check as 's->len - s->pos <= len' to avoid overflow entirely. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- blobmsg_json.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/blobmsg_json.c b/blobmsg_json.c index 31eec09..0fa5d85 100644 --- a/blobmsg_json.c +++ b/blobmsg_json.c @@ -112,8 +112,8 @@ bool blobmsg_add_json_from_string(struct blob_buf *b, const char *str) struct strbuf { - int len; - int pos; + size_t len; + size_t pos; char *buf; blobmsg_json_format_t custom_format; @@ -122,15 +122,15 @@ struct strbuf { int indent_level; }; -static bool blobmsg_puts(struct strbuf *s, const char *c, int len) +static bool blobmsg_puts(struct strbuf *s, const char *c, size_t len) { size_t new_len; char *new_buf; - if (len <= 0) + if (!len) return true; - if (s->pos + len >= s->len) { + if (s->len - s->pos <= len) { new_len = s->len + 16 + len; new_buf = realloc(s->buf, new_len); if (!new_buf) @@ -170,7 +170,7 @@ static void blobmsg_format_string(struct strbuf *s, const char *str) blobmsg_puts(s, "\"", 1); for (p = (unsigned char *) str, last = p; *p; p++) { char escape = '\0'; - int len; + size_t len; switch(*p) { case '\b': From 6351fe552162394429f263feb842cad3735110e7 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 22:23:01 +0200 Subject: [PATCH 10/21] 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 Signed-off-by: Hauke Mehrtens --- blobmsg_json.c | 20 +++++++++++++++++--- 1 file 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; } From 58b6543f1b2521a5d55c5952f69b9ef5ade0f835 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 22:24:13 +0200 Subject: [PATCH 11/21] blobmsg: fix unsigned integer overflow in blobmsg_alloc_string_buffer() blobmsg_alloc_string_buffer() increments maxlen by 1 to account for the null terminator. When maxlen is UINT_MAX, this increment wraps to 0, causing blobmsg_new() to allocate a zero-length payload. A subsequent write to the returned buffer would then overflow the blob's allocated space, corrupting adjacent memory. Add a bounds check before the increment to reject UINT_MAX input. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- blobmsg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blobmsg.c b/blobmsg.c index af3bd23..4c36c57 100644 --- a/blobmsg.c +++ b/blobmsg.c @@ -340,6 +340,8 @@ blobmsg_alloc_string_buffer(struct blob_buf *buf, const char *name, unsigned int struct blob_attr *attr; void *data_dest; + if (maxlen == (unsigned int)-1) + return NULL; maxlen++; attr = blobmsg_new(buf, BLOBMSG_TYPE_STRING, name, maxlen, &data_dest); if (!attr) From d7a3ae699df0ae7f3c28cc1ac3786b2b89934304 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 9 Apr 2026 22:25:14 +0200 Subject: [PATCH 12/21] blobmsg: use correct byte-order macro when setting BLOB_ATTR_EXTENDED blobmsg_new() sets the BLOB_ATTR_EXTENDED flag in attr->id_len, which is stored in big-endian format. The code used be32_to_cpu() to convert the constant before OR-ing it into the field, but the correct macro is cpu_to_be32() since we are converting from CPU byte order to the on-wire big-endian format. Both macros produce identical results (byte-swapping is its own inverse), so this is a correctness/readability fix with no behavioral change. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Hauke Mehrtens --- blobmsg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blobmsg.c b/blobmsg.c index 4c36c57..9111894 100644 --- a/blobmsg.c +++ b/blobmsg.c @@ -252,7 +252,7 @@ blobmsg_new(struct blob_buf *buf, int type, const char *name, int payload_len, v if (!attr) return NULL; - attr->id_len |= be32_to_cpu(BLOB_ATTR_EXTENDED); + attr->id_len |= cpu_to_be32(BLOB_ATTR_EXTENDED); hdr = blob_data(attr); hdr->namelen = cpu_to_be16(namelen); From 23c6618a5b9088644bb6b509c83f4f88ee8998d3 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 16 Apr 2026 22:16:42 +0200 Subject: [PATCH 13/21] blobmsg_json: fix double format string to avoid truncation and data loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using %lf (= %f) with the default precision of 6 fractional decimal digits requires up to 317 characters for negative values close to -DBL_MAX (1 sign + 309 integer digits + 1 decimal point + 6 fraction digits + NUL), but buf[] only holds 317 bytes, leaving no room for the NUL terminator — snprintf truncates the last digit silently. Additionally, 6 fractional digits do not give round-trip accuracy for IEEE 754 doubles; 17 significant digits (%.17g) are required. Switch to %.17g which uses scientific notation for very large or small values, keeping the output well within the buffer, and guarantees round-trip accuracy for all finite doubles. Update the cram fixtures test_blobmsg.t and test_blobmsg_types.t to match the new output. The previous fixtures encoded the buggy behaviour: DBL_MIN serialised as "0.000000" and was silently flattened to zero on the JSON round-trip, while DBL_MAX produced a 309-digit decimal string. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens --- blobmsg_json.c | 2 +- tests/cram/test_blobmsg.t | 8 ++++---- tests/cram/test_blobmsg_types.t | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/blobmsg_json.c b/blobmsg_json.c index 49eea61..04bb9a1 100644 --- a/blobmsg_json.c +++ b/blobmsg_json.c @@ -272,7 +272,7 @@ static void blobmsg_format_element(struct strbuf *s, struct blob_attr *attr, boo snprintf(buf, sizeof(buf), "%" PRId64, (int64_t) be64_to_cpu(*(uint64_t *)data)); break; case BLOBMSG_TYPE_DOUBLE: - snprintf(buf, sizeof(buf), "%lf", blobmsg_get_double(attr)); + snprintf(buf, sizeof(buf), "%.17g", blobmsg_get_double(attr)); break; case BLOBMSG_TYPE_STRING: blobmsg_format_string(s, data); diff --git a/tests/cram/test_blobmsg.t b/tests/cram/test_blobmsg.t index 0c192c5..67a9d80 100644 --- a/tests/cram/test_blobmsg.t +++ b/tests/cram/test_blobmsg.t @@ -35,7 +35,7 @@ check that blobmsg is producing expected results: \tworld : 2 (str) (esc) } - [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":0.000000,"dbl-max":179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,0.000000,179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000]} + [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":2.2250738585072014e-308,"dbl-max":1.7976931348623157e+308,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,2.2250738585072014e-308,1.7976931348623157e+308]} [*] blobmsg from json: Message: Hello, world! @@ -102,7 +102,7 @@ check that blobmsg is producing expected results: \tworld : 2 (str) (esc) } - [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":0.000000,"dbl-max":179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,0.000000,179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000]} + [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":2.2250738585072014e-308,"dbl-max":1.7976931348623157e+308,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,2.2250738585072014e-308,1.7976931348623157e+308]} [*] blobmsg from json: Message: Hello, world! @@ -169,7 +169,7 @@ check that blobmsg is producing expected results: \tworld : 2 (str) (esc) } - [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":0.000000,"dbl-max":179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,0.000000,179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000]} + [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":2.2250738585072014e-308,"dbl-max":1.7976931348623157e+308,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,2.2250738585072014e-308,1.7976931348623157e+308]} [*] blobmsg from json: Message: Hello, world! @@ -236,7 +236,7 @@ check that blobmsg is producing expected results: \tworld : 2 (str) (esc) } - [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":0.000000,"dbl-max":179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,0.000000,179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000]} + [*] blobmsg to json: {"message":"Hello, world!","testdata":{"dbl-min":2.2250738585072014e-308,"dbl-max":1.7976931348623157e+308,"foo":false,"poo":true,"moo-min":true,"moo-max":true,"bar-min":-32768,"bar-max":32767,"baz-min":-2147483648,"baz-max":2147483647,"taz-min":-9223372036854775808,"taz-max":9223372036854775807,"world":"2"},"list":[false,true,true,true,-32768,32767,-2147483648,2147483647,-9223372036854775808,9223372036854775807,2.2250738585072014e-308,1.7976931348623157e+308]} [*] blobmsg from json: Message: Hello, world! diff --git a/tests/cram/test_blobmsg_types.t b/tests/cram/test_blobmsg_types.t index 190e1f2..4def0e6 100644 --- a/tests/cram/test_blobmsg_types.t +++ b/tests/cram/test_blobmsg_types.t @@ -40,7 +40,7 @@ check that blobmsg is producing expected results: double_max: 1.797693e+308 double_min: 2.225074e-308 - [*] blobmsg to json: {"string":"Hello, world!","int64_max":9223372036854775807,"int64_min":-9223372036854775808,"int32_max":2147483647,"int32_min":-2147483648,"int16_max":32767,"int16_min":-32768,"int8_max":true,"int8_min":true,"double_max":179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000,"double_min":0.000000} + [*] blobmsg to json: {"string":"Hello, world!","int64_max":9223372036854775807,"int64_min":-9223372036854775808,"int32_max":2147483647,"int32_min":-2147483648,"int16_max":32767,"int16_min":-32768,"int8_max":true,"int8_min":true,"double_max":1.7976931348623157e+308,"double_min":2.2250738585072014e-308} [*] blobmsg from json: string: Hello, world! @@ -53,7 +53,7 @@ check that blobmsg is producing expected results: int8_max: 1 int8_min: 1 double_max: 1.797693e+308 - double_min: 0.000000e+00 + double_min: 2.225074e-308 [*] blobmsg from json/cast_u64: string: Hello, world! @@ -66,7 +66,7 @@ check that blobmsg is producing expected results: int8_max: 1 int8_min: 1 double_max: 1.797693e+308 - double_min: 0.000000e+00 + double_min: 2.225074e-308 [*] blobmsg from json/cast_s64: string: Hello, world! @@ -79,4 +79,4 @@ check that blobmsg is producing expected results: int8_max: 1 int8_min: 1 double_max: 1.797693e+308 - double_min: 0.000000e+00 + double_min: 2.225074e-308 From 1edf1d704e769dd894076383e79fdb2fdc212735 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 16 Apr 2026 22:18:13 +0200 Subject: [PATCH 14/21] jshn: fix integer overflow and type confusion in jshn_parse_file Two problems existed in jshn_parse_file(): 1. Integer overflow: sb.st_size is off_t (signed). The expression sb.st_size + 1 triggers signed integer overflow (undefined behaviour) when sb.st_size equals OFF_MAX. Add an explicit bounds check (sb.st_size < 0 || (size_t)sb.st_size >= SIZE_MAX) before the allocation and cast to size_t to make the arithmetic well-defined. 2. Type mismatch in read() comparison: read() returns ssize_t while sb.st_size is off_t. Comparing them directly is implementation- defined when the values differ in sign or width. Store the read() result in a ssize_t variable and compare against sb.st_size explicitly to catch both errors (n < 0) and short reads (n != size). Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens --- jshn.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jshn.c b/jshn.c index 9887bfe..4b947e1 100644 --- a/jshn.c +++ b/jshn.c @@ -338,6 +338,7 @@ static int jshn_parse_file(const char *path) struct stat sb; int ret = 0; char *fbuf; + ssize_t n; int fd; if ((fd = open(path, O_RDONLY)) == -1) { @@ -351,13 +352,20 @@ static int jshn_parse_file(const char *path) return 3; } - if (!(fbuf = calloc(1, sb.st_size+1))) { + if (sb.st_size < 0 || (uintmax_t)sb.st_size >= SIZE_MAX) { + fprintf(stderr, "File %s has invalid size\n", path); + close(fd); + return 3; + } + + if (!(fbuf = calloc(1, (size_t)sb.st_size + 1))) { fprintf(stderr, "Error allocating memory for %s\n", path); close(fd); return 3; } - if (read(fd, fbuf, sb.st_size) != sb.st_size) { + n = read(fd, fbuf, (size_t)sb.st_size); + if (n < 0 || n != sb.st_size) { fprintf(stderr, "Error reading %s\n", path); free(fbuf); close(fd); From 9b488010c4a74202a85ed24e01108fe069bd42e4 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:35:47 +0200 Subject: [PATCH 15/21] utils: fix integer overflow in __calloc_a() The accumulated allocation size was stored in a signed int, so summing several large size_t arguments could overflow silently and result in a calloc() that is much smaller than intended. Subsequent writes through the per-chunk pointers would then corrupt the heap. Use size_t for the accumulator and explicitly check for overflow on both the per-argument alignment rounding and the running sum. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- utils.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/utils.c b/utils.c index 82af887..6d4c36c 100644 --- a/utils.c +++ b/utils.c @@ -38,14 +38,28 @@ void *__calloc_a(size_t len, ...) void *ret; void **cur_addr; size_t cur_len; - int alloc_len = 0; + size_t alloc_len = 0; char *ptr; va_start(ap, len); va_copy(ap1, ap); - foreach_arg(ap1, cur_addr, cur_len, &ret, len) - alloc_len += (cur_len + C_PTR_ALIGN - 1 ) & C_PTR_MASK; + foreach_arg(ap1, cur_addr, cur_len, &ret, len) { + size_t aligned; + + if (cur_len > SIZE_MAX - (C_PTR_ALIGN - 1)) { + va_end(ap1); + va_end(ap); + return NULL; + } + aligned = (cur_len + C_PTR_ALIGN - 1) & C_PTR_MASK; + if (aligned > SIZE_MAX - alloc_len) { + va_end(ap1); + va_end(ap); + return NULL; + } + alloc_len += aligned; + } va_end(ap1); ptr = calloc(1, alloc_len); From 40a87f734b940198600324c70df614d537d21e33 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:36:45 +0200 Subject: [PATCH 16/21] blob: fix integer overflow in buffer growth functions blob_buffer_grow() computed delta and the new buffer size as plain int additions without checking for overflow. With sufficiently large inputs the result could wrap around to a small positive value, so realloc() would shrink the buffer and subsequent writes would overflow the heap. blob_buf_grow() suffered from the same issue: '(buf->buflen + required) > BLOB_ATTR_LEN_MASK' is evaluated after the addition already wraps, so the bound check could be bypassed. Reject negative inputs explicitly and rewrite the bound checks so the arithmetic cannot overflow. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- blob.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/blob.c b/blob.c index 735e5d5..3c2dd81 100644 --- a/blob.c +++ b/blob.c @@ -16,13 +16,23 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #include "blob.h" static bool blob_buffer_grow(struct blob_buf *buf, int minlen) { void *new; - int delta = ((minlen / 256) + 1) * 256; + int delta; + + if (minlen < 0 || minlen > INT_MAX - 256) + return false; + + delta = ((minlen / 256) + 1) * 256; + if (buf->buflen < 0 || delta > INT_MAX - buf->buflen) + return false; + new = realloc(buf->buf, buf->buflen + delta); if (new) { buf->buf = new; @@ -58,7 +68,9 @@ blob_buf_grow(struct blob_buf *buf, int required) { int offset_head = attr_to_offset(buf, buf->head); - if ((buf->buflen + required) > BLOB_ATTR_LEN_MASK) + if (required < 0 || buf->buflen < 0) + return false; + if (required > BLOB_ATTR_LEN_MASK - buf->buflen) return false; if (!buf->grow || !buf->grow(buf, required)) return false; From 02fccb465651f470ddd988f60036f22e485b5ac8 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:37:22 +0200 Subject: [PATCH 17/21] blob: use size_t for blob_memdup() length blob_pad_len() returns size_t and the value is used as a memory allocation/copy size. Storing it in a signed int could truncate or turn the value negative on platforms or inputs where the padded length exceeds INT_MAX, leading to a too-small malloc() and an out-of-bounds memcpy(). Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- blob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blob.c b/blob.c index 3c2dd81..93321ed 100644 --- a/blob.c +++ b/blob.c @@ -338,7 +338,7 @@ struct blob_attr * blob_memdup(const struct blob_attr *attr) { struct blob_attr *ret; - int size = blob_pad_len(attr); + size_t size = blob_pad_len(attr); ret = malloc(size); if (!ret) From 0fa612ca08f7b7dce18397d531226d6678144439 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:38:25 +0200 Subject: [PATCH 18/21] json_script: avoid alloca() on attacker-controlled pattern length eval_string() copied the pattern into a stack buffer sized via alloca(strlen(pattern) + 1). Patterns come from the JSON script input and may be arbitrarily large; a multi-MB pattern would smash the stack and crash (or, in the worst case, jump past a guard page). Allocate the working copy on the heap instead and free it before returning. While at it, drop the strcpy() in favour of memcpy() with the length we just computed. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- json_script.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/json_script.c b/json_script.c index ddcb3d1..de7e546 100644 --- a/json_script.c +++ b/json_script.c @@ -426,17 +426,24 @@ static int json_process_expr(struct json_call *call, struct blob_attr *cur) static int eval_string(struct json_call *call, struct blob_buf *buf, const char *name, const char *pattern) { - char *dest, *next, *str; + char *dest, *next, *str, *buffer; + size_t pattern_len; int len = 0; bool var = false; char c = '%'; - dest = blobmsg_alloc_string_buffer(buf, name, 0); - if (!dest) + pattern_len = strlen(pattern); + buffer = malloc(pattern_len + 1); + if (!buffer) return -1; + memcpy(buffer, pattern, pattern_len + 1); + next = buffer; - next = alloca(strlen(pattern) + 1); - strcpy(next, pattern); + dest = blobmsg_alloc_string_buffer(buf, name, 0); + if (!dest) { + free(buffer); + return -1; + } for (str = next; str; str = next) { const char *cur; @@ -487,6 +494,7 @@ static int eval_string(struct json_call *call, struct blob_buf *buf, const char dest[len] = 0; blobmsg_add_string_buffer(buf); + free(buffer); if (var) return -1; From 8c9862b6921b8fa7a53a5454ceb62286eed64ba4 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:39:16 +0200 Subject: [PATCH 19/21] blobmsg: fix integer overflow in blobmsg_realloc_string_buffer() 'maxlen + 1' wraps to 0 when maxlen is UINT_MAX, and the mixed signed/unsigned subtraction that produced 'required' could store a huge unsigned value into a signed int. In either case the function might return without growing the buffer while the caller assumes maxlen bytes are now available, leading to an out-of-bounds write. Reject UINT_MAX explicitly (matching blobmsg_alloc_string_buffer()) and rewrite the size comparison so the arithmetic stays in unsigned space and is bounded. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- blobmsg.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/blobmsg.c b/blobmsg.c index 9111894..fdf3337 100644 --- a/blobmsg.c +++ b/blobmsg.c @@ -13,6 +13,8 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #include "blobmsg.h" static const int blob_type[__BLOBMSG_TYPE_LAST] = { @@ -358,11 +360,19 @@ blobmsg_realloc_string_buffer(struct blob_buf *buf, unsigned int maxlen) { struct blob_attr *attr = blob_next(buf->head); int offset = attr_to_offset(buf, blob_next(buf->head)) + blob_pad_len(attr) - BLOB_COOKIE; - int required = maxlen + 1 - (buf->buflen - offset); + int required; + + if (maxlen >= INT_MAX) + return NULL; - if (required <= 0) + if (buf->buflen < 0 || offset < 0 || offset > buf->buflen) + return NULL; + + if ((int)maxlen + 1 <= buf->buflen - offset) goto out; + required = (int)maxlen + 1 - (buf->buflen - offset); + if (!blob_buf_grow(buf, required)) return NULL; attr = blob_next(buf->head); From 5fbef5bb94fbddba71d43d816d637c13c8ddf4d3 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:40:10 +0200 Subject: [PATCH 20/21] ustream: avoid INT_MAX overflow on malloc in ustream_vprintf() vsnprintf() returns the length the formatted output would have had if the buffer were unlimited. When that length is INT_MAX, the following 'maxlen + 1' wraps to a negative value (-> huge size_t), so malloc() either fails or, on 32-bit systems, returns a far too small allocation that the subsequent vsnprintf() then overruns. Reject INT_MAX explicitly in both call sites that allocate the oversized buffer. Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- ustream.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ustream.c b/ustream.c index d200dba..ff3aa05 100644 --- a/ustream.c +++ b/ustream.c @@ -16,6 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include #include @@ -498,6 +499,8 @@ int ustream_vprintf(struct ustream *s, const char *format, va_list arg) maxlen -= wr; return ustream_write_buffered(s, buf, maxlen, wr); } else { + if (maxlen == INT_MAX) + return 0; buf = malloc(maxlen + 1); if (!buf) return 0; @@ -527,6 +530,8 @@ int ustream_vprintf(struct ustream *s, const char *format, va_list arg) if (maxlen < buflen) return wr; + if (maxlen == INT_MAX) + return wr; buf = malloc(maxlen + 1); if (!buf) return wr; From 1501e60e5554bd206c9b13532b7352e668508420 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 23 Apr 2026 23:40:36 +0200 Subject: [PATCH 21/21] md5: detect read errors in md5sum() instead of returning a bogus hash fread() returning 0 was treated as end of file, but it can also mean a read error. In that case the loop terminated and md5_end() ran on the partial data, so the caller received a md5 sum that did not correspond to the file content and no error was reported. Distinguish EOF from read errors with ferror() and bail out with -1 on a real failure. Also use size_t for the return value of fread(). Link: https://github.com/openwrt/libubox/pull/42 Co-Authored-By: Claude Opus 4.7 Signed-off-by: Hauke Mehrtens --- md5.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/md5.c b/md5.c index 3f8ad28..6941013 100644 --- a/md5.c +++ b/md5.c @@ -321,9 +321,14 @@ int md5sum(const char *file, void *md5_buf) md5_begin(&ctx); do { - int len = fread(buf, 1, sizeof(buf), f); - if (!len) + size_t len = fread(buf, 1, sizeof(buf), f); + if (!len) { + if (ferror(f)) { + fclose(f); + return -1; + } break; + } md5_hash(buf, len, &ctx); ret += len;