Skip to content

Commit e9679a4

Browse files
committed
PHPC-531: Fix double free in corrupt BSON visitor
Freeing the current zval in the corrupt BSON visitor causes a segfault if done for the root document, since phongo_bson_to_zval_ex() expects the calling code to initialize and free that zval on success or error. That said, we should free when encountering an error via the document or array visitors (i.e. nested context), which would have just been initialized.
1 parent ff2709d commit e9679a4

File tree

3 files changed

+84
-16
lines changed

3 files changed

+84
-16
lines changed

src/bson.c

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -179,21 +179,9 @@ bool php_phongo_bson_visit_after(const bson_iter_t *iter ARG_UNUSED, const char
179179
}
180180
/* }}} */
181181
#endif
182-
void php_phongo_bson_visit_corrupt(const bson_iter_t *iter ARG_UNUSED, void *data) /* {{{ */
182+
void php_phongo_bson_visit_corrupt(const bson_iter_t *iter ARG_UNUSED, void *data ARG_UNUSED) /* {{{ */
183183
{
184-
#if PHP_VERSION_ID >= 70000
185-
zval *retval = &((php_phongo_bson_state *)data)->zchild;
186-
#else
187-
zval *retval = ((php_phongo_bson_state *)data)->zchild;
188-
#endif
189-
190184
mongoc_log(MONGOC_LOG_LEVEL_TRACE, MONGOC_LOG_DOMAIN, "Corrupt BSON data detected!");
191-
192-
#if PHP_VERSION_ID >= 70000
193-
zval_ptr_dtor(retval);
194-
#else
195-
zval_ptr_dtor(&retval);
196-
#endif
197185
}
198186
/* }}} */
199187
bool php_phongo_bson_visit_double(const bson_iter_t *iter ARG_UNUSED, const char *key, double v_double, void *data) /* {{{ */
@@ -612,7 +600,7 @@ bool php_phongo_bson_visit_document(const bson_iter_t *iter ARG_UNUSED, const ch
612600
array_init(state.zchild);
613601
#endif
614602

615-
if (!bson_iter_visit_all(&child, &php_bson_visitors, &state)) {
603+
if (!bson_iter_visit_all(&child, &php_bson_visitors, &state) && !child.err_off) {
616604
/* If php_phongo_bson_visit_binary() finds an ODM class, it should
617605
* supersede a default type map and named document class. */
618606
if (state.odm && state.map.document_type == PHONGO_TYPEMAP_NONE) {
@@ -662,6 +650,11 @@ bool php_phongo_bson_visit_document(const bson_iter_t *iter ARG_UNUSED, const ch
662650
Z_SET_REFCOUNT_P(state.zchild, 1);
663651
#endif
664652
}
653+
} else {
654+
/* Iteration stopped prematurely due to corruption or a failed
655+
* visitor. Free state.zchild, which we just initialized, and return
656+
* true to stop iteration for our parent context. */
657+
zval_ptr_dtor(&state.zchild);
665658
}
666659
}
667660

@@ -691,7 +684,7 @@ bool php_phongo_bson_visit_array(const bson_iter_t *iter ARG_UNUSED, const char
691684
array_init(state.zchild);
692685
#endif
693686

694-
if (!bson_iter_visit_all(&child, &php_bson_visitors, &state)) {
687+
if (!bson_iter_visit_all(&child, &php_bson_visitors, &state) && !child.err_off) {
695688

696689
switch(state.map.array_type) {
697690
case PHONGO_TYPEMAP_CLASS: {
@@ -737,6 +730,11 @@ bool php_phongo_bson_visit_array(const bson_iter_t *iter ARG_UNUSED, const char
737730
#endif
738731
break;
739732
}
733+
} else {
734+
/* Iteration stopped prematurely due to corruption or a failed
735+
* visitor. Free state.zchild, which we just initialized, and return
736+
* true to stop iteration for our parent context. */
737+
zval_ptr_dtor(&state.zchild);
740738
}
741739

742740
}
@@ -1385,7 +1383,15 @@ PHONGO_API int phongo_bson_to_zval_ex(const unsigned char *data, int data_len, p
13851383
#else
13861384
array_init(state->zchild);
13871385
#endif
1388-
bson_iter_visit_all(&iter, &php_bson_visitors, state);
1386+
1387+
if (bson_iter_visit_all(&iter, &php_bson_visitors, state) || iter.err_off) {
1388+
/* Iteration stopped prematurely due to corruption or a failed visitor.
1389+
* While we free the reader, state->zchild should be left as-is, since
1390+
* the calling code may want to zval_ptr_dtor() it. */
1391+
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC, "Could not convert BSON document to a PHP variable");
1392+
bson_reader_destroy(reader);
1393+
return 0;
1394+
}
13891395

13901396
/* If php_phongo_bson_visit_binary() finds an ODM class, it should supersede
13911397
* a default type map and named root class. */

tests/bson/bson-toPHP_error-004.phpt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
BSON\toPHP(): BSON decoding exceptions for bson_iter_visit_all() failure
3+
--SKIPIF--
4+
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
5+
--FILE--
6+
<?php
7+
require_once __DIR__ . "/../utils/basic.inc";
8+
9+
$tests = array(
10+
// Invalid UTF-8 character in root document's field name
11+
str_replace('INVALID!', "INVALID\xFE", fromPHP(['INVALID!' => 'bar'])),
12+
/* Note: we don't use a three-character string in the underflow case, as
13+
* the 4-byte string length and payload (i.e. three characters + null byte)
14+
* coincidentally satisfy the expected size for an 8-byte double. We also
15+
* don't use a four-character string, since its null byte would be
16+
* interpreted as the document terminator. The actual document terminator
17+
* would then remain in the buffer and trigger a "did not exhaust" error.
18+
*/
19+
pack('VCa*xVa*xx', 17, 1, 'foo', 3, 'ab'), // Invalid field type (underflow)
20+
pack('VCa*xVa*xx', 20, 1, 'foo', 6, 'abcde'), // Invalid field type (overflow)
21+
);
22+
23+
foreach ($tests as $bson) {
24+
echo throws(function() use ($bson) {
25+
toPHP($bson);
26+
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";
27+
}
28+
29+
?>
30+
===DONE===
31+
<?php exit(0); ?>
32+
--EXPECTF--
33+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
34+
Could not convert BSON document to a PHP variable
35+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
36+
Could not convert BSON document to a PHP variable
37+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
38+
Could not convert BSON document to a PHP variable
39+
===DONE===

tests/bson/bug0531.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
PHPC-531: Segfault due to double free by corrupt BSON visitor
3+
--SKIPIF--
4+
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
5+
--FILE--
6+
<?php
7+
8+
require_once __DIR__ . "/../utils/basic.inc";
9+
10+
$bson = fromPHP(["hello" => "world"]);
11+
$bson[4] = 1;
12+
13+
echo throws(function() use ($bson) {
14+
toPHP($bson);
15+
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";
16+
17+
?>
18+
===DONE===
19+
<?php exit(0); ?>
20+
--EXPECTF--
21+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
22+
Could not convert BSON document to a PHP variable
23+
===DONE===

0 commit comments

Comments
 (0)