printbuf: preserve self-append sources across growth

When the appended data points into the printbuf itself, growing the buffer may invalidate the source pointer before it is copied. Preserve the source offset across realloc and use memmove for internal overlap.
This commit is contained in:
Darren Carreras
2026-08-01 14:12:56 -04:00
parent 1bd2e4b3ef
commit 62bb838c57
3 changed files with 49 additions and 1 deletions
+12 -1
View File
@@ -28,6 +28,7 @@
#endif /* HAVE_STDARG_H */ #endif /* HAVE_STDARG_H */
#include "debug.h" #include "debug.h"
#include "json_inttypes.h"
#include "printbuf.h" #include "printbuf.h"
#include "snprintf_compat.h" #include "snprintf_compat.h"
#include "vasprintf_compat.h" #include "vasprintf_compat.h"
@@ -96,6 +97,8 @@ static int printbuf_extend(struct printbuf *p, int min_size)
int printbuf_memappend(struct printbuf *p, const char *buf, int size) int printbuf_memappend(struct printbuf *p, const char *buf, int size)
{ {
int buf_offset = -1;
/* Prevent signed integer overflows with large buffers. */ /* Prevent signed integer overflows with large buffers. */
if (size < 0 || size > INT_MAX - p->bpos - 1) if (size < 0 || size > INT_MAX - p->bpos - 1)
{ {
@@ -104,10 +107,18 @@ int printbuf_memappend(struct printbuf *p, const char *buf, int size)
} }
if (p->size <= p->bpos + size + 1) if (p->size <= p->bpos + size + 1)
{ {
const uintptr_t buf_addr = (uintptr_t)(const void *)buf;
const uintptr_t p_buf_addr = (uintptr_t)(const void *)p->buf;
/* realloc() invalidates source pointers into the old buffer. */
if (buf_addr >= p_buf_addr && buf_addr - p_buf_addr < (uintptr_t)p->size)
buf_offset = (int)(buf_addr - p_buf_addr);
if (printbuf_extend(p, p->bpos + size + 1) < 0) if (printbuf_extend(p, p->bpos + size + 1) < 0)
return -1; return -1;
if (buf_offset >= 0)
buf = p->buf + buf_offset;
} }
memcpy(p->buf + p->bpos, buf, size); memmove(p->buf + p->bpos, buf, size);
p->bpos += size; p->bpos += size;
p->buf[p->bpos] = '\0'; p->buf[p->bpos] = '\0';
return size; return size;
+34
View File
@@ -126,6 +126,38 @@ static void test_printbuf_memappend(int *before_resize)
printf("%s: end test\n", __func__); printf("%s: end test\n", __func__);
} }
static void test_printbuf_self_append(void);
static void test_printbuf_self_append(void)
{
struct printbuf *pb;
char *data;
int data_size;
int i;
int initial_size;
printf("%s: starting test\n", __func__);
pb = printbuf_new();
assert(pb != NULL);
initial_size = pb->size;
data_size = initial_size / 2 + 1;
data = malloc(data_size);
assert(data != NULL);
memset(data, 'X', data_size);
assert(printbuf_memappend(pb, data, data_size) == data_size);
free(data);
assert(pb->size == initial_size);
assert(printbuf_memappend(pb, pb->buf, pb->bpos) == data_size);
assert(pb->size > initial_size);
assert(pb->bpos == data_size * 2);
for (i = 0; i < pb->bpos; i++)
assert(pb->buf[i] == 'X');
assert(pb->buf[pb->bpos] == '\0');
printbuf_free(pb);
printf("%s: end test\n", __func__);
}
static void test_sprintbuf(int before_resize); static void test_sprintbuf(int before_resize);
static void test_sprintbuf(int before_resize) static void test_sprintbuf(int before_resize)
{ {
@@ -182,6 +214,8 @@ int main(int argc, char **argv)
printf("========================================\n"); printf("========================================\n");
test_printbuf_memappend(&before_resize); test_printbuf_memappend(&before_resize);
printf("========================================\n"); printf("========================================\n");
test_printbuf_self_append();
printf("========================================\n");
test_sprintbuf(before_resize); test_sprintbuf(before_resize);
printf("========================================\n"); printf("========================================\n");
+3
View File
@@ -21,6 +21,9 @@ Append to just after resize: 32, [XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]
Buffer size after printbuf_strappend(): 16, [XXXXXXXXXXXXXXXX] Buffer size after printbuf_strappend(): 16, [XXXXXXXXXXXXXXXX]
test_printbuf_memappend: end test test_printbuf_memappend: end test
======================================== ========================================
test_printbuf_self_append: starting test
test_printbuf_self_append: end test
========================================
test_sprintbuf: starting test test_sprintbuf: starting test
Buffer length: 0 Buffer length: 0
sprintbuf to just after resize(31+1): 32, [XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX], strlen(buf)=32 sprintbuf to just after resize(31+1): 32, [XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX], strlen(buf)=32