Fix off-by-one buffer growth in sixel_print_add and add regression test.

This commit is contained in:
Michael Grant
2026-10-09 18:38:02 +02:00
parent 554aedf09b
commit 4b13e0c3d7
4 changed files with 120 additions and 1 deletions

View File

@@ -0,0 +1,72 @@
/* Exercise SIXEL output lengths at the encoder's allocation boundary. */
#undef NDEBUG
#include <assert.h>
#include "../tmux.h"
#ifdef ENABLE_IMAGES
#include "../image-sixel.c"
#include "../xmalloc.c"
/* Abort if an allocation or formatting check fails. */
void
fatal(__unused const char *fmt, ...)
{
abort();
}
/* Abort if an allocation or formatting check fails. */
void
fatalx(__unused const char *fmt, ...)
{
abort();
}
/* Encode lengths below, at and above the initial 8192-byte allocation. */
int
main(void)
{
struct sixel_image image = { 0 };
u_int colour, width, x;
size_t size;
char *data;
colour = (2u << 25) | (100u << 16) | (100u << 8) | 100u;
image.sy = 2;
image.ncolours = 1;
image.used_colours = 1;
image.colours = &colour;
image.lines = xcalloc(2, sizeof *image.lines);
/* Headers and controls add 26 bytes to these uncompressed rasters. */
for (width = 8165; width <= 8167; width++) {
image.sx = width;
image.lines[0].sx = width;
image.lines[1].sx = width;
image.lines[0].pixels = xcalloc(width, sizeof(uint16_t));
image.lines[1].pixels = xcalloc(width, sizeof(uint16_t));
/* Alternate row bits so adjacent columns cannot be compressed. */
for (x = 0; x < width; x++)
image.lines[x % 2].pixels[x] = 1;
data = sixel_print(&image, NULL, &size);
assert(data != NULL);
assert(size == width + 26);
assert(data[size] == '\0');
assert(strlen(data) == size);
free(data);
free(image.lines[0].pixels);
free(image.lines[1].pixels);
}
free(image.lines);
return (0);
}
#else
/* Skip the encoder check when image support is disabled. */
int
main(void)
{
return (0);
}
#endif

View File

@@ -0,0 +1,15 @@
# Use the configured compiler and flags for the standalone encoder check.
.PHONY: sixel-buffer-probe sixel-buffer-test
sixel-buffer-probe:
$(CC) $(CFLAGS) $(LDFLAGS) -fsanitize=address \
-o "$(SIXEL_TEST_DIR)/probe" "$(SIXEL_TEST_DIR)/probe.c"
sixel-buffer-test:
$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -UNDEBUG \
-ffunction-sections -fdata-sections -fsanitize=address \
-fno-omit-frame-pointer $(SIXEL_TEST_LDFLAGS) $(LDFLAGS) \
-o "$(SIXEL_TEST_DIR)/test" "$(srcdir)/regress/image-sixel-buffer.c" \
"$(srcdir)/compat/reallocarray.c"

View File

@@ -0,0 +1,32 @@
#!/bin/sh
# Catch a trailing NUL written beyond an exactly full SIXEL output buffer.
PATH=/bin:/usr/bin
export PATH
SIXEL_TEST_DIR=$(mktemp -d) || exit 1
trap 'rm -rf "$SIXEL_TEST_DIR"' 0 1 15
export SIXEL_TEST_DIR
ASAN_OPTIONS=detect_leaks=0
export ASAN_OPTIONS
case $(uname -s) in
Darwin) SIXEL_TEST_LDFLAGS=-Wl,-dead_strip ;;
*) SIXEL_TEST_LDFLAGS=-Wl,--gc-sections ;;
esac
export SIXEL_TEST_LDFLAGS
# Skip when the configured compiler or its runtime cannot use ASan.
printf 'int main(void) { return 0; }\n' >"$SIXEL_TEST_DIR/probe.c"
if ! make -s -C .. -f Makefile -f regress/image-sixel-buffer.mk \
sixel-buffer-probe >"$SIXEL_TEST_DIR/probe.log" 2>&1; then
exit 0
fi
if ! "$SIXEL_TEST_DIR/probe" >"$SIXEL_TEST_DIR/probe.log" 2>&1; then
exit 0
fi
make -s -C .. -f Makefile -f regress/image-sixel-buffer.mk \
sixel-buffer-test || exit 1
"$SIXEL_TEST_DIR/test" || exit 1