diff --git a/programs/fileio_asyncio.c b/programs/fileio_asyncio.c index 42a47201656..c9d8ea1718e 100644 --- a/programs/fileio_asyncio.c +++ b/programs/fileio_asyncio.c @@ -614,7 +614,8 @@ size_t AIO_ReadPool_fillBuffer(ReadPoolCtx_t* ctx, size_t n) { * and coalesce the remaining bytes with the next job's buffer */ if (ctx->srcBufferLoaded > 0) { useCoalesce = 1; - memcpy(ctx->coalesceBuffer, ctx->srcBuffer, ctx->srcBufferLoaded); + /* Remaining bytes may already be in coalesceBuffer. */ + memmove(ctx->coalesceBuffer, ctx->srcBuffer, ctx->srcBufferLoaded); ctx->srcBuffer = ctx->coalesceBuffer; } diff --git a/tests/cli-tests/decompression/skippable-overlap.sh b/tests/cli-tests/decompression/skippable-overlap.sh new file mode 100755 index 00000000000..d72d26cc599 --- /dev/null +++ b/tests/cli-tests/decompression/skippable-overlap.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -eu + +# An 8-byte empty skippable frame followed by a 9-byte empty zstd frame. +# The initial 18-byte header refill moves this 17-byte stream into the +# coalesce buffer. After consuming the skippable frame, the next refill +# moves 9 bytes from offset 8 back to offset 0. Run under ASan to detect +# an overlapping memcpy. +printf '\120\052\115\030\000\000\000\000\050\265\057\375\040\000\001\000\000' > skippable-overlap.zst + +# This is a valid stream and must decode successfully to empty output. +for io in --asyncio --no-asyncio; do + for operation in -t -dc; do + zstd -q "$io" "$operation" skippable-overlap.zst + done +done diff --git a/tests/cli-tests/decompression/skippable-overlap.sh.stderr.exact b/tests/cli-tests/decompression/skippable-overlap.sh.stderr.exact new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/cli-tests/decompression/skippable-overlap.sh.stdout.exact b/tests/cli-tests/decompression/skippable-overlap.sh.stdout.exact new file mode 100644 index 00000000000..e69de29bb2d