From cc87074c1993381548be1ae180e481804fb5ae28 Mon Sep 17 00:00:00 2001 From: evilgensec Date: Thu, 11 Jun 2026 13:02:08 +0545 Subject: [PATCH] x32-unpool: bound the index map before dereferencing the output pointers The x32 unpooling microkernels copy each channel to output[index[c]], where index is the caller-supplied index map and output is the per-pixel indirection slice holding exactly kernel_elements pointers. The index value was used to select an output pointer with no check that it is less than kernel_elements, so an out-of-range index reads a pointer past the indirection buffer and then writes the input element through that out-of-bounds pointer (heap out-of-bounds read of the pointer table plus a write through an attacker-influenced wild pointer). Guard each index against the number of output pointers and drop values that fall outside it, in all four variants (scalar, neon, sse2, wasmsimd). Valid unpooling indices (produced by argmax pooling) are always < kernel_elements, so well-formed input is unaffected; the output is pre-initialized with the fill value, so a dropped index simply leaves that location at fill. --- src/x32-unpool/x32-unpool-neon.c | 9 ++++++++- src/x32-unpool/x32-unpool-scalar.c | 9 ++++++++- src/x32-unpool/x32-unpool-sse2.c | 9 ++++++++- src/x32-unpool/x32-unpool-wasmsimd.c | 9 ++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/x32-unpool/x32-unpool-neon.c b/src/x32-unpool/x32-unpool-neon.c index 7b6367fa420..e83018f804c 100644 --- a/src/x32-unpool/x32-unpool-neon.c +++ b/src/x32-unpool/x32-unpool-neon.c @@ -18,6 +18,7 @@ void xnn_x32_unpool_ukernel__neon( const uint32_t* index, uint32_t** output) { + const size_t num_outputs = kernel_elements; // Pre-initialize outputs with constant. const uint32x4_t vfill = vdupq_n_u32(fill); uint32_t** os = output; @@ -41,7 +42,13 @@ void xnn_x32_unpool_ukernel__neon( size_t offset = 0; do { const uint32_t i = *index++; - *((uint32_t*) ((uintptr_t) output[i] + offset)) = *input++; + const uint32_t v = *input++; + // Ignore indices outside the kernel_elements output pointers instead of + // dereferencing output[i] out of bounds. Valid unpooling indices are + // always < kernel_elements, so well-formed input is unaffected. + if (i < num_outputs) { + *((uint32_t*) ((uintptr_t) output[i] + offset)) = v; + } offset += sizeof(uint32_t); } while (--channels != 0); } diff --git a/src/x32-unpool/x32-unpool-scalar.c b/src/x32-unpool/x32-unpool-scalar.c index 4596622071d..7d1a279e059 100644 --- a/src/x32-unpool/x32-unpool-scalar.c +++ b/src/x32-unpool/x32-unpool-scalar.c @@ -16,6 +16,7 @@ void xnn_x32_unpool_ukernel__scalar( const uint32_t* index, uint32_t** output) { + const size_t num_outputs = kernel_elements; // Pre-initialize outputs with constant. uint32_t** os = output; do { @@ -30,7 +31,13 @@ void xnn_x32_unpool_ukernel__scalar( size_t offset = 0; do { const uint32_t i = *index++; - *((uint32_t*) ((uintptr_t) output[i] + offset)) = *input++; + const uint32_t v = *input++; + // Ignore indices outside the kernel_elements output pointers instead of + // dereferencing output[i] out of bounds. Valid unpooling indices are + // always < kernel_elements, so well-formed input is unaffected. + if (i < num_outputs) { + *((uint32_t*) ((uintptr_t) output[i] + offset)) = v; + } offset += sizeof(uint32_t); } while (--channels != 0); } diff --git a/src/x32-unpool/x32-unpool-sse2.c b/src/x32-unpool/x32-unpool-sse2.c index adcbe22b990..45e48c39525 100644 --- a/src/x32-unpool/x32-unpool-sse2.c +++ b/src/x32-unpool/x32-unpool-sse2.c @@ -18,6 +18,7 @@ void xnn_x32_unpool_ukernel__sse2( const uint32_t* index, uint32_t** output) { + const size_t num_outputs = kernel_elements; // Pre-initialize outputs with constant. const __m128i vfill = _mm_set1_epi32((int) fill); uint32_t** os = output; @@ -43,7 +44,13 @@ void xnn_x32_unpool_ukernel__sse2( size_t offset = 0; do { const uint32_t i = *index++; - *((uint32_t*) ((uintptr_t) output[i] + offset)) = *input++; + const uint32_t v = *input++; + // Ignore indices outside the kernel_elements output pointers instead of + // dereferencing output[i] out of bounds. Valid unpooling indices are + // always < kernel_elements, so well-formed input is unaffected. + if (i < num_outputs) { + *((uint32_t*) ((uintptr_t) output[i] + offset)) = v; + } offset += sizeof(uint32_t); } while (--channels != 0); } diff --git a/src/x32-unpool/x32-unpool-wasmsimd.c b/src/x32-unpool/x32-unpool-wasmsimd.c index 9d376cb1e95..60e03e8aa96 100644 --- a/src/x32-unpool/x32-unpool-wasmsimd.c +++ b/src/x32-unpool/x32-unpool-wasmsimd.c @@ -18,6 +18,7 @@ void xnn_x32_unpool_ukernel__wasmsimd( const uint32_t* index, uint32_t** output) { + const size_t num_outputs = kernel_elements; // Pre-initialize outputs with constant. const v128_t vfill = wasm_i32x4_splat(fill); uint32_t** os = output; @@ -43,7 +44,13 @@ void xnn_x32_unpool_ukernel__wasmsimd( size_t offset = 0; do { const uint32_t i = *index++; - *((uint32_t*) ((uintptr_t) output[i] + offset)) = *input++; + const uint32_t v = *input++; + // Ignore indices outside the kernel_elements output pointers instead of + // dereferencing output[i] out of bounds. Valid unpooling indices are + // always < kernel_elements, so well-formed input is unaffected. + if (i < num_outputs) { + *((uint32_t*) ((uintptr_t) output[i] + offset)) = v; + } offset += sizeof(uint32_t); } while (--channels != 0); }