Skip to content

Commit 208f1d7

Browse files
committed
buffer: fix infinite loop when growing buffers
When growing buffers, we repeatedly multiply the currently allocated number of bytes by 1.5 until it exceeds the requested number of bytes. This has two major problems: 1. If the current number of bytes is tiny and one wishes to resize to a comparatively huge number of bytes, then we may need to loop thousands of times. 2. If resizing to a value close to `SIZE_MAX` (which would fail anyway), then we probably hit an infinite loop as multiplying the current amount of bytes will repeatedly result in integer overflows. When reallocating buffers, one typically chooses values close to 1.5 to enable re-use of resulting memory holes in later reallocations. But because of this, it really only makes sense to use a factor of 1.5 _once_, but not looping until we finally are able to fit it. Thus, we can completely avoid the loop and just opt for the much simpler algorithm of multiplying with 1.5 once and, if the result doesn't fit, just use the target size. This avoids both problems of looping extensively and hitting overflows. This commit also adds a test that would've previously resulted in an infinite loop.
1 parent 3e8a17b commit 208f1d7

File tree

2 files changed

+27
-8
lines changed

2 files changed

+27
-8
lines changed

src/buffer.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,17 @@ int git_buf_try_grow(
5858
new_ptr = NULL;
5959
} else {
6060
new_size = buf->asize;
61+
/*
62+
* Grow the allocated buffer by 1.5 to allow
63+
* re-use of memory holes resulting from the
64+
* realloc. If this is still too small, then just
65+
* use the target size.
66+
*/
67+
if ((new_size = (new_size << 1) - (new_size >> 1)) < target_size)
68+
new_size = target_size;
6169
new_ptr = buf->ptr;
6270
}
6371

64-
/* grow the buffer size by 1.5, until it's big enough
65-
* to fit our target size */
66-
while (new_size < target_size)
67-
new_size = (new_size << 1) - (new_size >> 1);
68-
6972
/* round allocation up to multiple of 8 */
7073
new_size = (new_size + 7) & ~7;
7174

tests/core/buffer.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,9 @@ check_buf_append(
231231
git_buf_puts(&tgt, data_b);
232232
cl_assert(git_buf_oom(&tgt) == 0);
233233
cl_assert_equal_s(expected_data, git_buf_cstr(&tgt));
234-
cl_assert(tgt.size == expected_size);
234+
cl_assert_equal_i(tgt.size, expected_size);
235235
if (expected_asize > 0)
236-
cl_assert(tgt.asize == expected_asize);
236+
cl_assert_equal_i(tgt.asize, expected_asize);
237237

238238
git_buf_dispose(&tgt);
239239
}
@@ -308,7 +308,7 @@ void test_core_buffer__5(void)
308308
REP16("x") REP16("o"), 32, 40);
309309

310310
check_buf_append(test_4096, "", test_4096, 4096, 4104);
311-
check_buf_append(test_4096, test_4096, test_8192, 8192, 9240);
311+
check_buf_append(test_4096, test_4096, test_8192, 8192, 8200);
312312

313313
/* check sequences of appends */
314314
check_buf_append_abc("a", "b", "c",
@@ -1204,3 +1204,19 @@ void test_core_buffer__dont_grow_borrowed(void)
12041204

12051205
cl_git_fail_with(GIT_EINVALID, git_buf_grow(&buf, 1024));
12061206
}
1207+
1208+
void test_core_buffer__dont_hit_infinite_loop_when_resizing(void)
1209+
{
1210+
git_buf buf = GIT_BUF_INIT;
1211+
1212+
cl_git_pass(git_buf_puts(&buf, "foobar"));
1213+
/*
1214+
* We do not care whether this succeeds or fails, which
1215+
* would depend on platform-specific allocation
1216+
* semantics. We only want to know that the function
1217+
* actually returns.
1218+
*/
1219+
(void)git_buf_try_grow(&buf, SIZE_MAX, true);
1220+
1221+
git_buf_dispose(&buf);
1222+
}

0 commit comments

Comments
 (0)