reftable: fix allocation count on realloc error

When realloc(3) fails, it returns NULL and keeps the original allocation
intact.  REFTABLE_ALLOC_GROW overwrites both the original pointer and
the allocation count variable in that case, simultaneously leaking the
original allocation and misrepresenting the number of storable items.

parse_names() avoids the leak by keeping the original pointer if
reallocation fails, but still increase the allocation count in such a
case as if it succeeded.  That's OK, because the error handling code
just frees everything and doesn't look at names_cap anymore.

reftable_buf_add() does the same, but here it is a problem as it leaves
the reftable_buf in a broken state, with ->alloc being roughly twice as
big as the actually allocated memory, allowing out-of-bounds writes in
subsequent calls.

Reimplement REFTABLE_ALLOC_GROW to avoid leaks, keep allocation counts
in sync and still signal failures to callers while avoiding code
duplication in callers.  Make it an expression that evaluates to 0 if no
reallocation is needed or it succeeded and 1 on failure while keeping
the original pointer and allocation counter values.

Adjust REFTABLE_ALLOC_GROW_OR_NULL to the new calling convention for
REFTABLE_ALLOC_GROW, but keep its support for non-size_t alloc variables
for now.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
René Scharfe 2024-12-28 10:48:00 +01:00 committed by Junio C Hamano
parent 8db127d43f
commit 2cca185e85
3 changed files with 55 additions and 21 deletions

View File

@ -124,11 +124,8 @@ int reftable_buf_add(struct reftable_buf *buf, const void *data, size_t len)
size_t newlen = buf->len + len; size_t newlen = buf->len + len;
if (newlen + 1 > buf->alloc) { if (newlen + 1 > buf->alloc) {
char *reallocated = buf->buf; if (REFTABLE_ALLOC_GROW(buf->buf, newlen + 1, buf->alloc))
REFTABLE_ALLOC_GROW(reallocated, newlen + 1, buf->alloc);
if (!reallocated)
return REFTABLE_OUT_OF_MEMORY_ERROR; return REFTABLE_OUT_OF_MEMORY_ERROR;
buf->buf = reallocated;
} }
memcpy(buf->buf + buf->len, data, len); memcpy(buf->buf + buf->len, data, len);
@ -233,11 +230,9 @@ char **parse_names(char *buf, int size)
next = end; next = end;
} }
if (p < next) { if (p < next) {
char **names_grown = names; if (REFTABLE_ALLOC_GROW(names, names_len + 1,
REFTABLE_ALLOC_GROW(names_grown, names_len + 1, names_cap); names_cap))
if (!names_grown)
goto err; goto err;
names = names_grown;
names[names_len] = reftable_strdup(p); names[names_len] = reftable_strdup(p);
if (!names[names_len++]) if (!names[names_len++])

View File

@ -120,22 +120,35 @@ char *reftable_strdup(const char *str);
#define REFTABLE_ALLOC_ARRAY(x, alloc) (x) = reftable_malloc(st_mult(sizeof(*(x)), (alloc))) #define REFTABLE_ALLOC_ARRAY(x, alloc) (x) = reftable_malloc(st_mult(sizeof(*(x)), (alloc)))
#define REFTABLE_CALLOC_ARRAY(x, alloc) (x) = reftable_calloc((alloc), sizeof(*(x))) #define REFTABLE_CALLOC_ARRAY(x, alloc) (x) = reftable_calloc((alloc), sizeof(*(x)))
#define REFTABLE_REALLOC_ARRAY(x, alloc) (x) = reftable_realloc((x), st_mult(sizeof(*(x)), (alloc))) #define REFTABLE_REALLOC_ARRAY(x, alloc) (x) = reftable_realloc((x), st_mult(sizeof(*(x)), (alloc)))
#define REFTABLE_ALLOC_GROW(x, nr, alloc) \
do { \ static inline void *reftable_alloc_grow(void *p, size_t nelem, size_t elsize,
if ((nr) > alloc) { \ size_t *allocp)
alloc = 2 * (alloc) + 1; \ {
if (alloc < (nr)) \ void *new_p;
alloc = (nr); \ size_t alloc = *allocp * 2 + 1;
REFTABLE_REALLOC_ARRAY(x, alloc); \ if (alloc < nelem)
} \ alloc = nelem;
} while (0) new_p = reftable_realloc(p, st_mult(elsize, alloc));
if (!new_p)
return p;
*allocp = alloc;
return new_p;
}
#define REFTABLE_ALLOC_GROW(x, nr, alloc) ( \
(nr) > (alloc) && ( \
(x) = reftable_alloc_grow((x), (nr), sizeof(*(x)), &(alloc)), \
(nr) > (alloc) \
) \
)
#define REFTABLE_ALLOC_GROW_OR_NULL(x, nr, alloc) do { \ #define REFTABLE_ALLOC_GROW_OR_NULL(x, nr, alloc) do { \
void *reftable_alloc_grow_or_null_orig_ptr = (x); \ size_t reftable_alloc_grow_or_null_alloc = alloc; \
REFTABLE_ALLOC_GROW((x), (nr), (alloc)); \ if (REFTABLE_ALLOC_GROW((x), (nr), reftable_alloc_grow_or_null_alloc)) { \
if (!(x)) { \ REFTABLE_FREE_AND_NULL(x); \
reftable_free(reftable_alloc_grow_or_null_orig_ptr); \
alloc = 0; \ alloc = 0; \
} else { \
alloc = reftable_alloc_grow_or_null_alloc; \
} \ } \
} while (0) } while (0)

View File

@ -146,6 +146,32 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
check_int(in, ==, out); check_int(in, ==, out);
} }
if_test ("REFTABLE_ALLOC_GROW works") {
int *arr = NULL, *old_arr;
size_t alloc = 0, old_alloc;
check(!REFTABLE_ALLOC_GROW(arr, 1, alloc));
check(arr != NULL);
check_uint(alloc, >=, 1);
arr[0] = 42;
old_alloc = alloc;
old_arr = arr;
reftable_set_alloc(malloc, realloc_stub, free);
check(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
check(arr == old_arr);
check_uint(alloc, ==, old_alloc);
old_alloc = alloc;
reftable_set_alloc(malloc, realloc, free);
check(!REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc));
check(arr != NULL);
check_uint(alloc, >, old_alloc);
arr[alloc - 1] = 42;
reftable_free(arr);
}
if_test ("REFTABLE_ALLOC_GROW_OR_NULL works") { if_test ("REFTABLE_ALLOC_GROW_OR_NULL works") {
int *arr = NULL; int *arr = NULL;
size_t alloc = 0, old_alloc; size_t alloc = 0, old_alloc;