Zstd: Fix ASan poisoning for pooled Zstd contexts

The Zstd context mempool can reuse buffers that were previously poisoned
under AddressSanitizer, leading to false-positive use-after-poison reports
during zloop and other stress tests.

Explicitly unpoison memory when handing buffers out to Zstd and poison the
user-visible region again when buffers are returned to the pool. This makes
the allocator ASan-correct while preserving existing pooling behavior.

Also fix non-standard void * pointer arithmetic in zstd_free() and remove an
early return in zstd_dctx_alloc() so kmem_type/kmem_size are always set on
pool hits.

This only affects ASan bookkeeping in user space, does not change runtime
behavior in non-ASan configurations, and does not affect on-disk formats.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Alexander Moch <mail@alexmoch.com>
Closes #18089
This commit is contained in:
Alexander Moch 2025-12-30 14:32:25 +01:00 committed by Brian Behlendorf
parent a2ac9cd606
commit e7f9734bc7

View File

@ -264,6 +264,8 @@ static struct zstd_pool *zstd_mempool_dctx;
#if defined(ZFS_ASAN_ENABLED)
#define ADDRESS_SANITIZER 1
#endif
/* Kernel space. */
#if defined(_KERNEL) && defined(ADDRESS_SANITIZER)
void __asan_unpoison_memory_region(void const volatile *addr, size_t size);
void __asan_poison_memory_region(void const volatile *addr, size_t size);
@ -271,6 +273,14 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size) {};
void __asan_poison_memory_region(void const volatile *addr, size_t size) {};
#endif
/* User space. */
#if defined(ADDRESS_SANITIZER) && !defined(_KERNEL)
#define ZSTD_ASAN_POISON(p, n) __asan_poison_memory_region((p), (n))
#define ZSTD_ASAN_UNPOISON(p, n) __asan_unpoison_memory_region((p), (n))
#else
#define ZSTD_ASAN_POISON(p, n) do { } while (0)
#define ZSTD_ASAN_UNPOISON(p, n) do { } while (0)
#endif
static void
zstd_mempool_reap(struct zstd_pool *zstd_mempool)
@ -408,6 +418,10 @@ zstd_mempool_alloc(struct zstd_pool *zstd_mempool, size_t size)
static void
zstd_mempool_free(struct zstd_kmem *z)
{
/* Poison only the user-visible region (exclude header). */
ZSTD_ASAN_POISON((char *)z + sizeof (struct zstd_kmem),
z->kmem_size - sizeof (struct zstd_kmem));
mutex_exit(&z->pool->barrier);
}
@ -689,7 +703,6 @@ ZFS_COMPRESS_WRAP_DECL(zfs_zstd_compress)
ZFS_DECOMPRESS_WRAP_DECL(zfs_zstd_decompress)
ZFS_DECOMPRESS_LEVEL_WRAP_DECL(zfs_zstd_decompress_level)
/* Allocator for zstd compression context using mempool_allocator */
static void *
zstd_alloc(void *opaque __maybe_unused, size_t size)
@ -704,7 +717,9 @@ zstd_alloc(void *opaque __maybe_unused, size_t size)
return (NULL);
}
return ((void*)z + (sizeof (struct zstd_kmem)));
void *p = (char *)z + sizeof (struct zstd_kmem);
ZSTD_ASAN_UNPOISON(p, size);
return (p);
}
/*
@ -719,15 +734,15 @@ zstd_dctx_alloc(void *opaque __maybe_unused, size_t size)
enum zstd_kmem_type type = ZSTD_KMEM_DEFAULT;
z = (struct zstd_kmem *)zstd_mempool_alloc(zstd_mempool_dctx, nbytes);
if (!z) {
if (z) {
type = ZSTD_KMEM_POOL;
} else {
/* Try harder, decompression shall not fail */
z = vmem_alloc(nbytes, KM_SLEEP);
if (z) {
z->pool = NULL;
}
ZSTDSTAT_BUMP(zstd_stat_alloc_fail);
} else {
return ((void*)z + (sizeof (struct zstd_kmem)));
}
/* Fallback if everything fails */
@ -752,14 +767,17 @@ zstd_dctx_alloc(void *opaque __maybe_unused, size_t size)
z->kmem_type = type;
z->kmem_size = nbytes;
return ((void*)z + (sizeof (struct zstd_kmem)));
void *p = (char *)z + sizeof (struct zstd_kmem);
ZSTD_ASAN_UNPOISON(p, size);
return (p);
}
/* Free allocated memory by its specific type */
static void
zstd_free(void *opaque __maybe_unused, void *ptr)
{
struct zstd_kmem *z = (ptr - sizeof (struct zstd_kmem));
struct zstd_kmem *z =
(struct zstd_kmem *)((char *)ptr - sizeof (struct zstd_kmem));
enum zstd_kmem_type type;
ASSERT3U(z->kmem_type, <, ZSTD_KMEM_COUNT);
@ -774,6 +792,8 @@ zstd_free(void *opaque __maybe_unused, void *ptr)
zstd_mempool_free(z);
break;
case ZSTD_KMEM_DCTX:
/* Poison fallback user region on release. */
ZSTD_ASAN_POISON(ptr, z->kmem_size - sizeof (struct zstd_kmem));
mutex_exit(&zstd_dctx_fallback.barrier);
break;
default: