Skip to content

Commit ca13ef8

Browse files
mdbmeskevinAlbs
authored andcommitted
CDRIVER-5915: Fix for allocation of bson_t larger than half max size (#1891)
addresses CDRIVER-5915, fixing three closely related problems: * rounding allocation size to the next power of two could cause BSON_MAX_SIZE to be exceeded * bson_reserve_buffer allocated more space than requested, equal to the previous document length * test_bson_reserve_buffer_errors had a flawed "too big" case which was masked by the other two issues additionally: * adds a test for allocating bson_t of exactly max size (on 64-bit systems only) * fix for potential integer overflow in bson_reserve_buffer() with overlong size * comments and assertions related to bson_t max size assumptions
1 parent 8b10413 commit ca13ef8

File tree

4 files changed

+108
-35
lines changed

4 files changed

+108
-35
lines changed

src/libbson/doc/bson_reserve_buffer.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,30 @@ Synopsis
99
.. code-block:: c
1010
1111
uint8_t *
12-
bson_reserve_buffer (bson_t *bson, uint32_t size);
12+
bson_reserve_buffer (bson_t *bson, uint32_t total_size);
1313
1414
Parameters
1515
----------
1616

1717
* ``bson``: An initialized :symbol:`bson_t`.
18-
* ``size``: The length in bytes of the new buffer.
18+
* ``total_size``: The length in bytes of the new buffer.
1919

2020
Description
2121
-----------
2222

23-
Grow the internal buffer of ``bson`` to ``size`` and set the document length to ``size``. Useful for eliminating copies when reading BSON bytes from a stream.
23+
Grow the internal buffer of ``bson`` to ``total_size`` and set the document length to ``total_size``. Useful for eliminating copies when reading BSON bytes from a stream.
2424

25-
First, initialize ``bson`` with :symbol:`bson_init` or :symbol:`bson_new`, then call this function. After it returns, the length of ``bson`` is set to ``size`` but its contents are uninitialized memory: you must fill the contents with a BSON document of the correct length before any other operations.
25+
First, initialize ``bson`` with :symbol:`bson_init` or :symbol:`bson_new`, then call this function. After it returns, the length of ``bson`` is set to ``total_size`` but its contents are uninitialized memory: you must fill the contents with a BSON document of the correct length before any other operations.
2626

2727
The document must be freed with :symbol:`bson_destroy`.
2828

29+
Note that, in this usage, the BSON header and footer bytes will not be verified or used by Libbson.
30+
The ``bson_t`` document length and buffer size limit are both set to ``total_size`` regardless of the value encoded in the document header.
31+
2932
Returns
3033
-------
3134

32-
A pointer to the internal buffer, which is at least ``size`` bytes, or NULL if the space could not be allocated.
35+
A pointer to the internal buffer, which is at least ``total_size`` bytes, or NULL if the space could not be allocated.
3336

3437
Example
3538
-------

src/libbson/src/bson/bson.c

Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <bson/bson-json-private.h>
2222
#include <common-string-private.h>
2323
#include <common-json-private.h>
24+
#include <common-macros-private.h>
2425
#include <bson/bson-iso8601-private.h>
2526
#include <common-cmp-private.h>
2627

@@ -61,6 +62,36 @@ typedef struct {
6162
*/
6263
static const uint8_t gZero;
6364

65+
/*
66+
*--------------------------------------------------------------------------
67+
*
68+
* _bson_round_up_alloc_size --
69+
*
70+
* Given a potential allocation length in bytes, round up to the
71+
* next power of two without exceeding BSON_MAX_SIZE.
72+
*
73+
* Returns:
74+
* If the input is <= BSON_MAX_SIZE, returns a value >= the input
75+
* and still <= BSON_MAX_SIZE. If the input was greater than
76+
* BSON_MAX_SIZE, it is returned unmodified.
77+
*
78+
* Side effects:
79+
* None.
80+
*
81+
*--------------------------------------------------------------------------
82+
*/
83+
84+
static BSON_INLINE size_t
85+
_bson_round_up_alloc_size (size_t size)
86+
{
87+
if (size <= BSON_MAX_SIZE) {
88+
size_t power_of_two = bson_next_power_of_two (size);
89+
return BSON_MIN (power_of_two, BSON_MAX_SIZE);
90+
} else {
91+
return size;
92+
}
93+
}
94+
6495
/*
6596
*--------------------------------------------------------------------------
6697
*
@@ -81,17 +112,20 @@ static const uint8_t gZero;
81112

82113
static bool
83114
_bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
84-
size_t size) /* IN */
115+
uint32_t grow_size) /* IN */
85116
{
86117
bson_impl_alloc_t *alloc = (bson_impl_alloc_t *) impl;
87118
uint8_t *data;
88-
size_t req;
89119

90-
if (((size_t) impl->len + size) <= sizeof impl->data) {
120+
MONGOC_DEBUG_ASSERT ((size_t) impl->len <= BSON_MAX_SIZE);
121+
MONGOC_DEBUG_ASSERT ((size_t) grow_size <= BSON_MAX_SIZE);
122+
size_t req = (size_t) impl->len + (size_t) grow_size;
123+
124+
if (req <= sizeof impl->data) {
91125
return true;
92126
}
93127

94-
req = bson_next_power_of_two (impl->len + size);
128+
req = _bson_round_up_alloc_size (req);
95129

96130
if (req <= BSON_MAX_SIZE) {
97131
data = bson_malloc (req);
@@ -123,11 +157,12 @@ _bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
123157
*
124158
* _bson_impl_alloc_grow --
125159
*
126-
* Document growth implementation for documents containing malloc
127-
* based buffers.
160+
* Document growth implementation for non-inline documents, possibly
161+
* containing a reallocatable buffer.
128162
*
129163
* Returns:
130-
* true if successful; otherwise false indicating BSON_MAX_SIZE overflow.
164+
* true if successful; otherwise false indicating BSON_MAX_SIZE overflow
165+
* or an attempt to grow a buffer with no realloc implementation.
131166
*
132167
* Side effects:
133168
* None.
@@ -137,21 +172,26 @@ _bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
137172

138173
static bool
139174
_bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
140-
size_t size) /* IN */
175+
uint32_t grow_size) /* IN */
141176
{
142-
size_t req;
143-
144-
/*
145-
* Determine how many bytes we need for this document in the buffer
177+
/* Determine how many bytes we need for this document in the buffer
146178
* including necessary trailing bytes for parent documents.
179+
*
180+
* On size assumptions: the previous grow operation has already checked
181+
* (len + offset + previous_depth) against BSON_MAX_SIZE. Current depth can be at most (previous_depth + 1). The
182+
* caller has checked grow_size against BSON_MAX_SIZE. On the smallest (32-bit) supported size_t, we can still add
183+
* these maximum values (2x BSON_MAX_SIZE, 1 additional byte of depth) without arithmetic overflow.
147184
*/
148-
req = (impl->offset + impl->len + size + impl->depth);
185+
MONGOC_DEBUG_ASSERT ((uint64_t) impl->len + (uint64_t) impl->offset + (uint64_t) impl->depth <=
186+
(uint64_t) BSON_MAX_SIZE);
187+
MONGOC_DEBUG_ASSERT ((size_t) grow_size <= BSON_MAX_SIZE);
188+
size_t req = impl->offset + (size_t) impl->len + (size_t) grow_size + (size_t) impl->depth;
149189

150190
if (req <= *impl->buflen) {
151191
return true;
152192
}
153193

154-
req = bson_next_power_of_two (req);
194+
req = _bson_round_up_alloc_size (req);
155195

156196
if ((req <= BSON_MAX_SIZE) && impl->realloc) {
157197
*impl->buf = impl->realloc (*impl->buf, req, impl->realloc_func_ctx);
@@ -168,11 +208,16 @@ _bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
168208
*
169209
* _bson_grow --
170210
*
171-
* Grows the bson_t structure to be large enough to contain @size
172-
* bytes.
211+
* Grows the bson_t structure to be large enough to contain @grow_size
212+
* bytes in addition to its current content.
213+
*
214+
* The caller is responsible for ensuring @grow_size itself is not
215+
* above BSON_MAX_SIZE, but a final determination of overflow status
216+
* can't be made until we are inside _bson_impl_*_grow().
173217
*
174218
* Returns:
175-
* true if successful, false if the size would overflow.
219+
* true if successful, false if the size would overflow or the buffer
220+
* needs to grow but does not support reallocation.
176221
*
177222
* Side effects:
178223
* None.
@@ -181,14 +226,16 @@ _bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
181226
*/
182227

183228
static bool
184-
_bson_grow (bson_t *bson, /* IN */
185-
uint32_t size) /* IN */
229+
_bson_grow (bson_t *bson, /* IN */
230+
uint32_t grow_size) /* IN */
186231
{
232+
BSON_ASSERT ((size_t) grow_size <= BSON_MAX_SIZE);
233+
187234
if ((bson->flags & BSON_FLAG_INLINE)) {
188-
return _bson_impl_inline_grow ((bson_impl_inline_t *) bson, size);
235+
return _bson_impl_inline_grow ((bson_impl_inline_t *) bson, grow_size);
189236
}
190237

191-
return _bson_impl_alloc_grow ((bson_impl_alloc_t *) bson, size);
238+
return _bson_impl_alloc_grow ((bson_impl_alloc_t *) bson, grow_size);
192239
}
193240

194241

@@ -268,6 +315,9 @@ BSON_STATIC_ASSERT2 (size_t_gte_int, SIZE_MAX >= INT_MAX);
268315
// To support unchecked cast from `uint32_t` to `size_t`.
269316
BSON_STATIC_ASSERT2 (size_t_gte_uint32_t, SIZE_MAX >= UINT32_MAX);
270317

318+
// Support largest _bson_impl_alloc_grow on smallest size_t
319+
BSON_STATIC_ASSERT2 (max_alloc_grow_fits_min_sizet, (uint64_t) BSON_MAX_SIZE * 2u + 1u <= (uint64_t) UINT32_MAX);
320+
271321
// Declare local state with the identifier `ident`.
272322
#define BSON_APPEND_BYTES_LIST_DECLARE(ident) \
273323
_bson_append_bytes_list ident = {.current = (ident).args, .n_bytes = 0u}; \
@@ -2019,7 +2069,8 @@ bson_copy_to (const bson_t *src, bson_t *dst)
20192069
}
20202070

20212071
data = _bson_data (src);
2022-
len = bson_next_power_of_two ((size_t) src->len);
2072+
len = _bson_round_up_alloc_size ((size_t) src->len);
2073+
MONGOC_DEBUG_ASSERT (len <= BSON_MAX_SIZE);
20232074

20242075
adst = (bson_impl_alloc_t *) dst;
20252076
adst->flags = BSON_FLAG_STATIC;
@@ -2135,21 +2186,35 @@ bson_destroy (bson_t *bson)
21352186

21362187

21372188
uint8_t *
2138-
bson_reserve_buffer (bson_t *bson, uint32_t size)
2189+
bson_reserve_buffer (bson_t *bson, uint32_t total_size)
21392190
{
21402191
if (bson->flags & (BSON_FLAG_CHILD | BSON_FLAG_IN_CHILD | BSON_FLAG_RDONLY)) {
21412192
return NULL;
21422193
}
21432194

2144-
if (!_bson_grow (bson, size)) {
2145-
return NULL;
2195+
if (total_size > bson->len) {
2196+
if ((size_t) total_size > BSON_MAX_SIZE) {
2197+
return NULL;
2198+
}
2199+
2200+
/* Note that the bson_t can also include space for parent or sibling documents (offset) and for trailing bytes
2201+
* (depth). These sizes will be considered by _bson_grow() but we can assume they are zero in documents without
2202+
* BSON_FLAG_CHILD or BSON_FLAG_IN_CHILD. If this is called on a document that's part of a bson_writer_t, it is
2203+
* correct to ignore offset: we set the size of the current document, leaving previous documents alone. */
2204+
if (!_bson_grow (bson, total_size - bson->len)) {
2205+
// Will fail due to overflow or when reallocation is needed on a buffer that does not support it.
2206+
return NULL;
2207+
}
21462208
}
21472209

21482210
if (bson->flags & BSON_FLAG_INLINE) {
21492211
/* bson_grow didn't spill over */
2150-
((bson_impl_inline_t *) bson)->len = size;
2212+
((bson_impl_inline_t *) bson)->len = total_size;
2213+
BSON_ASSERT (total_size <= BSON_INLINE_DATA_SIZE);
21512214
} else {
2152-
((bson_impl_alloc_t *) bson)->len = size;
2215+
bson_impl_alloc_t *impl = (bson_impl_alloc_t *) bson;
2216+
impl->len = total_size;
2217+
BSON_ASSERT (impl->offset <= *impl->buflen && *impl->buflen - impl->offset >= (size_t) total_size);
21532218
}
21542219

21552220
return _bson_data (bson);

src/libbson/src/bson/bson.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ BSON_EXPORT (void)
334334
bson_destroy (bson_t *bson);
335335

336336
BSON_EXPORT (uint8_t *)
337-
bson_reserve_buffer (bson_t *bson, uint32_t size);
337+
bson_reserve_buffer (bson_t *bson, uint32_t total_size);
338338

339339
BSON_EXPORT (bool)
340340
bson_steal (bson_t *dst, bson_t *src);

src/libbson/tests/test-bson.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,10 +1886,15 @@ test_bson_reserve_buffer_errors (void)
18861886
uint32_t len_le;
18871887

18881888
/* too big */
1889-
ASSERT (!bson_reserve_buffer (&bson, (uint32_t) (INT32_MAX - bson.len - 1)));
1889+
ASSERT (!bson_reserve_buffer (&bson, (uint32_t) (BSON_MAX_SIZE + 1u)));
1890+
/* exactly the maximum size */
1891+
#if BSON_WORD_SIZE > 32
1892+
ASSERT (bson_reserve_buffer (&bson, (uint32_t) BSON_MAX_SIZE));
1893+
ASSERT_CMPUINT32 (bson.len, ==, BSON_MAX_SIZE);
1894+
#endif
1895+
bson_destroy (&bson);
18901896

18911897
/* make a static bson, it refuses bson_reserve_buffer since it's read-only */
1892-
bson_destroy (&bson);
18931898
len_le = BSON_UINT32_TO_LE (5);
18941899
memcpy (data, &len_le, sizeof (len_le));
18951900
ASSERT (bson_init_static (&bson, data, sizeof data));

0 commit comments

Comments
 (0)