Skip to content

Commit 072b44d

Browse files
author
Micah Scott
committed
CDRIVER-5915: Fix for allocation of bson_t larger than half max size
1 parent 33ed617 commit 072b44d

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

src/libbson/src/bson/bson.c

Lines changed: 58 additions & 10 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_next_power_of_two_for_alloc --
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_next_power_of_two_for_alloc (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
*
@@ -90,7 +121,7 @@ _bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
90121
return true;
91122
}
92123

93-
req = bson_next_power_of_two (impl->len + size);
124+
req = _bson_next_power_of_two_for_alloc (impl->len + size);
94125

95126
if (req <= BSON_MAX_SIZE) {
96127
data = bson_malloc (req);
@@ -122,11 +153,12 @@ _bson_impl_inline_grow (bson_impl_inline_t *impl, /* IN */
122153
*
123154
* _bson_impl_alloc_grow --
124155
*
125-
* Document growth implementation for documents containing malloc
126-
* based buffers.
156+
* Document growth implementation for non-inline documents, possibly
157+
* containing a reallocatable buffer.
127158
*
128159
* Returns:
129-
* true if successful; otherwise false indicating BSON_MAX_SIZE overflow.
160+
* true if successful; otherwise false indicating BSON_MAX_SIZE overflow
161+
* or an attempt to grow a buffer with no realloc implementation.
130162
*
131163
* Side effects:
132164
* None.
@@ -143,14 +175,20 @@ _bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
143175
/*
144176
* Determine how many bytes we need for this document in the buffer
145177
* including necessary trailing bytes for parent documents.
178+
*
179+
* Note that the buffer offset and nesting depth are not available
180+
* outside bson_impl_alloc_t, meaning it's not possible for callers to
181+
* fully rule out BSON_MAX_SIZE overflow before _bson_grow().
182+
* Some earlier checks against BSON_MAX_SIZE serve to prevent intermediate
183+
* overflows rather than to validate the final allocation size.
146184
*/
147185
req = (impl->offset + impl->len + size + impl->depth);
148186

149187
if (req <= *impl->buflen) {
150188
return true;
151189
}
152190

153-
req = bson_next_power_of_two (req);
191+
req = _bson_next_power_of_two_for_alloc (req);
154192

155193
if ((req <= BSON_MAX_SIZE) && impl->realloc) {
156194
*impl->buf = impl->realloc (*impl->buf, req, impl->realloc_func_ctx);
@@ -168,10 +206,11 @@ _bson_impl_alloc_grow (bson_impl_alloc_t *impl, /* IN */
168206
* _bson_grow --
169207
*
170208
* Grows the bson_t structure to be large enough to contain @size
171-
* bytes.
209+
* bytes in addition to its current content.
172210
*
173211
* Returns:
174-
* true if successful, false if the size would overflow.
212+
* true if successful, false if the size would overflow or the buffer
213+
* needs to grow but does not support reallocation.
175214
*
176215
* Side effects:
177216
* None.
@@ -2018,7 +2057,7 @@ bson_copy_to (const bson_t *src, bson_t *dst)
20182057
}
20192058

20202059
data = _bson_data (src);
2021-
len = bson_next_power_of_two ((size_t) src->len);
2060+
len = _bson_next_power_of_two_for_alloc ((size_t) src->len);
20222061

20232062
adst = (bson_impl_alloc_t *) dst;
20242063
adst->flags = BSON_FLAG_STATIC;
@@ -2140,15 +2179,24 @@ bson_reserve_buffer (bson_t *bson, uint32_t size)
21402179
return NULL;
21412180
}
21422181

2143-
if (!_bson_grow (bson, size)) {
2182+
/* The caller wants a total document size of "size".
2183+
* Note that the bson_t can also include space for parent or sibling documents (offset) and for trailing bytes
2184+
* (depth). These sizes will be considered by _bson_grow() but we can assume they are zero in documents without
2185+
* 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
2186+
* correct to ignore offset: we set the size of the current document, leaving previous documents alone. */
2187+
if (size > bson->len && !_bson_grow (bson, size - bson->len)) {
2188+
// Will fail due to overflow or when reallocation is needed on a buffer that does not support it.
21442189
return NULL;
21452190
}
21462191

21472192
if (bson->flags & BSON_FLAG_INLINE) {
21482193
/* bson_grow didn't spill over */
21492194
((bson_impl_inline_t *) bson)->len = size;
2195+
BSON_ASSERT (size <= BSON_INLINE_DATA_SIZE);
21502196
} else {
2151-
((bson_impl_alloc_t *) bson)->len = size;
2197+
bson_impl_alloc_t *impl = (bson_impl_alloc_t *) bson;
2198+
impl->len = size;
2199+
BSON_ASSERT (impl->offset <= *impl->buflen && *impl->buflen - impl->offset >= (size_t) size);
21522200
}
21532201

21542202
return _bson_data (bson);

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)