[PATCH] misc: Fix out-of-bounds array write in tdelete (bug 34506)
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Thu Aug 13 14:45:02 GMT 2026
On 11/08/26 06:56, Florian Weimer wrote:
> Allocate the maximum array sizes directly, instead of resizing
> the arrays as needed. This eliminates alloca usage from the
> function, and fixes the out-of-bounds accesses. The asserts
> guard against the bug coming back if the balancing of the tree
> turns out not to work correctly.
>
> ---
> misc/tsearch.c | 36 ++++++++++++++++--------------------
> 1 file changed, 16 insertions(+), 20 deletions(-)
>
> diff --git a/misc/tsearch.c b/misc/tsearch.c
> index 9b2eb34b25..29b323c856 100644
> --- a/misc/tsearch.c
> +++ b/misc/tsearch.c
> @@ -406,12 +406,19 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar)
> int cmp;
> node *rootp = (node *) vrootp;
> node root, unchained;
> - /* Stack of nodes so we remember the parents without recursion. It's
> - _very_ unlikely that there are paths longer than 40 nodes. The tree
> - would need to have around 250.000 nodes. */
> - int stacksize = 40;
> + /* Stack of nodes so we remember the parents without recursion. The
> + stack size is based on the theoretical address space sizes (2**32
> + and 2**63 bytes), the size of the node struct (12 bytes and 24
> + bytes), and the resulting maximum height of a red-black tree. */
> +#if __WORDSIZE == 32
> + enum { stacksize = 60 };
> +#elif __WORDSIZE == 64
> + enum { stacksize = 120 };
> +#else
> +# error "unknown __WORDSIZE"
> +#endif
So assuming the red-black tree height bound h >= 2·log2(n+1), and
peak transient rebalancing push as h + 1:
* __WORDSIZE == 32:
n ≤ 2^32/12
log2(n+1) =~ 32 − log2 12 = 32 − 3.585 = 28.415
h <= 2 × 28.415 = 56.83 -> h <= 56
peak stack depth = h + 1 = 57
* __WORDSIZE == 64
n ≤ 2^53/24
log2(n+1) =~ 63 − log2 24 = 63 − 4.585 = 58.415
h <= 2 × 58.415 = 116.83 -> h <= 116
peak stack depth = h + 1 = 117
I think we can infer this without the using internal definitions:
enum { ptr_bits = CHAR_BIT * sizeof (void *),
stacksize = 2 * ptr_bits + 1 };
This yields 65 on 32-bit and 129 on 64-bit. We can tune it down a bit by
subtracting some factor from ptr_bits, but I think this is clear.
The rest looks ok.
> int sp = 0;
> - node **nodestack = alloca (sizeof (node *) * stacksize);
> + node *nodestack[stacksize];
>
> if (rootp == NULL)
> return NULL;
> @@ -424,14 +431,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar)
> root = DEREFNODEPTR(rootp);
> while ((cmp = (*compar) (key, root->key)) != 0)
> {
> - if (sp == stacksize)
> - {
> - node **newstack;
> - stacksize += 20;
> - newstack = alloca (sizeof (node *) * stacksize);
> - nodestack = memcpy (newstack, nodestack, sp * sizeof (node *));
> - }
> -
> + assert (sp < stacksize);
> nodestack[sp++] = rootp;
> p = DEREFNODEPTR(rootp);
> if (cmp < 0)
> @@ -470,13 +470,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar)
> node upn;
> for (;;)
> {
> - if (sp == stacksize)
> - {
> - node **newstack;
> - stacksize += 20;
> - newstack = alloca (sizeof (node *) * stacksize);
> - nodestack = memcpy (newstack, nodestack, sp * sizeof (node *));
> - }
> + assert (sp < stacksize);
> nodestack[sp++] = parentp;
> parentp = up;
> upn = DEREFNODEPTR(up);
> @@ -541,6 +535,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar)
> SETNODEPTR(pp,q);
> /* Make sure pp is right if the case below tries to use
> it. */
> + assert (sp < stacksize);
> nodestack[sp++] = pp = LEFTPTR(q);
> q = RIGHT(p);
> }
> @@ -625,6 +620,7 @@ __tdelete (const void *key, void **vrootp, __compar_fn_t compar)
> SETLEFT(p,RIGHT(q));
> SETRIGHT(q,p);
> SETNODEPTR(pp,q);
> + assert (sp < stacksize);
> nodestack[sp++] = pp = RIGHTPTR(q);
> q = LEFT(p);
> }
>
> base-commit: b589bd672c529cf264dc6dfdfa11f73c7e4e1666
>
More information about the Libc-alpha
mailing list