Skip to content

Commit 57bffac

Browse files
author
mdbmes
authored
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 b9045da commit 57bffac

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

2627
#include <string.h>
@@ -60,6 +61,36 @@ typedef struct {
6061
*/
6162
static const uint8_t gZero;
6263

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

81112
static bool
82113
_bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
83-
size_t size) /* IN */
114+
uint32_t grow_size) /* IN */
84115
{
85116
bson_impl_alloc_t *alloc = (bson_impl_alloc_t *) impl;
86117
uint8_t *data;
87-
size_t req;
88118

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

93-
req = bson_next_power_of_two (impl->len + size);
127+
req = _bson_round_up_alloc_size (req);
94128

95129
if (req <= BSON_MAX_SIZE) {
96130
data = bson_malloc (req);
@@ -122,11 +156,12 @@ _bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
122156
*
123157
* _bson_impl_alloc_grow --
124158
*
125-
* Document growth implementation for documents containing malloc
126-
* based buffers.
159+
* Document growth implementation for non-inline documents, possibly
160+
* containing a reallocatable buffer.
127161
*
128162
* Returns:
129-
* true if successful; otherwise false indicating BSON_MAX_SIZE overflow.
163+
* true if successful; otherwise false indicating BSON_MAX_SIZE overflow
164+
* or an attempt to grow a buffer with no realloc implementation.
130165
*
131166
* Side effects:
132167
* None.
@@ -136,21 +171,26 @@ _bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
136171

137172
static bool
138173
_bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
139-
size_t size) /* IN */
174+
uint32_t grow_size) /* IN */
140175
{
141-
size_t req;
142-
143-
/*
144-
* Determine how many bytes we need for this document in the buffer
176+
/* Determine how many bytes we need for this document in the buffer
145177
* including necessary trailing bytes for parent documents.
178+
*
179+
* On size assumptions: the previous grow operation has already checked
180+
* (len + offset + previous_depth) against BSON_MAX_SIZE. Current depth can be at most (previous_depth + 1). The
181+
* caller has checked grow_size against BSON_MAX_SIZE. On the smallest (32-bit) supported size_t, we can still add
182+
* these maximum values (2x BSON_MAX_SIZE, 1 additional byte of depth) without arithmetic overflow.
146183
*/
147-
req = (impl->offset + impl->len + size + impl->depth);
184+
MONGOC_DEBUG_ASSERT ((uint64_t) impl->len + (uint64_t) impl->offset + (uint64_t) impl->depth <=
185+
(uint64_t) BSON_MAX_SIZE);
186+
MONGOC_DEBUG_ASSERT ((size_t) grow_size <= BSON_MAX_SIZE);
187+
size_t req = impl->offset + (size_t) impl->len + (size_t) grow_size + (size_t) impl->depth;
148188

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

153-
req = bson_next_power_of_two (req);
193+
req = _bson_round_up_alloc_size (req);
154194

155195
if ((req <= BSON_MAX_SIZE) && impl->realloc) {
156196
*impl->buf = impl->realloc (*impl->buf, req, impl->realloc_func_ctx);
@@ -167,11 +207,16 @@ _bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
167207
*
168208
* _bson_grow --
169209
*
170-
* Grows the bson_t structure to be large enough to contain @size
171-
* bytes.
210+
* Grows the bson_t structure to be large enough to contain @grow_size
211+
* bytes in addition to its current content.
212+
*
213+
* The caller is responsible for ensuring @grow_size itself is not
214+
* above BSON_MAX_SIZE, but a final determination of overflow status
215+
* can't be made until we are inside _bson_impl_*_grow().
172216
*
173217
* Returns:
174-
* true if successful, false if the size would overflow.
218+
* true if successful, false if the size would overflow or the buffer
219+
* needs to grow but does not support reallocation.
175220
*
176221
* Side effects:
177222
* None.
@@ -180,14 +225,16 @@ _bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
180225
*/
181226

182227
static bool
183-
_bson_grow (bson_t *bson, /* IN */
184-
uint32_t size) /* IN */
228+
_bson_grow (bson_t *bson, /* IN */
229+
uint32_t grow_size) /* IN */
185230
{
231+
BSON_ASSERT ((size_t) grow_size <= BSON_MAX_SIZE);
232+
186233
if ((bson->flags & BSON_FLAG_INLINE)) {
187-
return _bson_impl_inline_grow ((bson_impl_inline_t *) bson, size);
234+
return _bson_impl_inline_grow ((bson_impl_inline_t *) bson, grow_size);
188235
}
189236

190-
return _bson_impl_alloc_grow ((bson_impl_alloc_t *) bson, size);
237+
return _bson_impl_alloc_grow ((bson_impl_alloc_t *) bson, grow_size);
191238
}
192239

193240

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

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

20202070
data = _bson_data (src);
2021-
len = bson_next_power_of_two ((size_t) src->len);
2071+
len = _bson_round_up_alloc_size ((size_t) src->len);
2072+
MONGOC_DEBUG_ASSERT (len <= BSON_MAX_SIZE);
20222073

20232074
adst = (bson_impl_alloc_t *) dst;
20242075
adst->flags = BSON_FLAG_STATIC;
@@ -2134,21 +2185,35 @@ bson_destroy (bson_t *bson)
21342185

21352186

21362187
uint8_t *
2137-
bson_reserve_buffer (bson_t *bson, uint32_t size)
2188+
bson_reserve_buffer (bson_t *bson, uint32_t total_size)
21382189
{
21392190
if (bson->flags & (BSON_FLAG_CHILD | BSON_FLAG_IN_CHILD | BSON_FLAG_RDONLY)) {
21402191
return NULL;
21412192
}
21422193

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

21472209
if (bson->flags & BSON_FLAG_INLINE) {
21482210
/* bson_grow didn't spill over */
2149-
((bson_impl_inline_t *) bson)->len = size;
2211+
((bson_impl_inline_t *) bson)->len = total_size;
2212+
BSON_ASSERT (total_size <= BSON_INLINE_DATA_SIZE);
21502213
} else {
2151-
((bson_impl_alloc_t *) bson)->len = size;
2214+
bson_impl_alloc_t *impl = (bson_impl_alloc_t *) bson;
2215+
impl->len = total_size;
2216+
BSON_ASSERT (impl->offset <= *impl->buflen && *impl->buflen - impl->offset >= (size_t) total_size);
21522217
}
21532218

21542219
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)