Skip to content

Commit e9098a5

Browse files
authored
PHPC-2219: Prohibit serializing PackedArray as root documents (#1480)
* PHPC-2219: Prohibit serializing PackedArray as root documents This adds logic to php_phongo_zval_to_bson_internal() to prohibit serializing PackedArray instances as a root document. Since this function is also used in specific cases to encode a BSON array, a PHONGO_BSON_ALLOW_ROOT_ARRAY flag is introduced to relax the restriction. * Check if existing field_path element must be freed before overwriting * Remove function name from Javascript code strings
1 parent a204130 commit e9098a5

File tree

51 files changed

+616
-141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+616
-141
lines changed

src/BSON/Document.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ static PHP_METHOD(MongoDB_BSON_Document, fromPHP)
160160
intern = Z_DOCUMENT_OBJ_P(&zv);
161161

162162
intern->bson = bson_new();
163-
php_phongo_zval_to_bson(data, PHONGO_BSON_NONE, intern->bson, NULL);
163+
164+
// Explicitly allow constructing a Document from a PackedArray
165+
php_phongo_zval_to_bson(data, PHONGO_BSON_ALLOW_ROOT_ARRAY, intern->bson, NULL);
164166

165167
RETURN_ZVAL(&zv, 1, 1);
166168
}

src/MongoDB/BulkWrite.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ static bool php_phongo_bulkwrite_opts_append_array(bson_t* opts, const char* key
157157
return false;
158158
}
159159

160-
php_phongo_zval_to_bson(value, PHONGO_BSON_NONE, &b, NULL);
160+
// Explicitly allow MongoDB\BSON\PackedArray for array values
161+
php_phongo_zval_to_bson(value, PHONGO_BSON_ALLOW_ROOT_ARRAY, &b, NULL);
161162

162163
if (EG(exception)) {
163164
bson_destroy(&b);
@@ -441,7 +442,8 @@ static PHP_METHOD(MongoDB_Driver_BulkWrite, update)
441442
goto cleanup;
442443
}
443444

444-
php_phongo_zval_to_bson(zupdate, PHONGO_BSON_NONE, &bupdate, NULL);
445+
// Explicitly allow MongoDB\BSON\PackedArray for update pipelines
446+
php_phongo_zval_to_bson(zupdate, PHONGO_BSON_ALLOW_ROOT_ARRAY, &bupdate, NULL);
445447

446448
if (EG(exception)) {
447449
goto cleanup;

src/phongo_bson.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <Zend/zend_enum.h>
2222
#endif
2323
#include <Zend/zend_interfaces.h>
24+
#include <Zend/zend_portability.h>
2425

2526
#include "php_array_api.h"
2627

@@ -162,6 +163,13 @@ void php_phongo_field_path_write_item_at_current_level(php_phongo_field_path* fi
162163
php_phongo_field_path_ensure_allocation(field_path, field_path->size);
163164

164165
if (field_path->owns_elements) {
166+
/* Note: owns_elements is only used for field paths parsed from a type
167+
* map, so it's unlikely that an element would already have been
168+
* allocated here. This is in contrast to BSON encoding/decoding, which
169+
* frequently updates the field path and does not own elements. */
170+
if (UNEXPECTED(field_path->elements[field_path->size])) {
171+
efree(field_path->elements[field_path->size]);
172+
}
165173
field_path->elements[field_path->size] = estrdup(element);
166174
} else {
167175
field_path->elements[field_path->size] = (char*) element;

src/phongo_bson_encode.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,15 @@ static void php_phongo_zval_to_bson_internal(zval* data, php_phongo_field_path*
466466
}
467467

468468
if (instanceof_function(Z_OBJCE_P(data), php_phongo_packedarray_ce)) {
469+
/* If we are at the root-level, PackedArray instances should be
470+
* prohibited unless PHONGO_BSON_ALLOW_ROOT_ARRAY is set. */
471+
bool is_root_level = (field_path->size == 0);
472+
473+
if (is_root_level && !(flags & PHONGO_BSON_ALLOW_ROOT_ARRAY)) {
474+
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "%s cannot be serialized as a root document", ZSTR_VAL(Z_OBJCE_P(data)->name));
475+
return;
476+
}
477+
469478
php_phongo_packedarray_t* intern = Z_PACKEDARRAY_OBJ_P(data);
470479

471480
phongo_bson_copy_to_noinit(intern->bson, bson);

src/phongo_bson_encode.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
#include <php.h>
2323

2424
typedef enum {
25-
PHONGO_BSON_NONE = 0x00,
26-
PHONGO_BSON_ADD_ID = 0x01,
27-
PHONGO_BSON_RETURN_ID = 0x02
25+
PHONGO_BSON_NONE = 0,
26+
PHONGO_BSON_ADD_ID = (1 << 0),
27+
PHONGO_BSON_RETURN_ID = (1 << 1),
28+
PHONGO_BSON_ALLOW_ROOT_ARRAY = (1 << 2)
2829
} php_phongo_bson_flags_t;
2930

3031
void php_phongo_zval_to_bson(zval* data, php_phongo_bson_flags_t flags, bson_t* bson, bson_t** bson_out);

tests/bson/bson-fromPHP-001.phpt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ $tests = array(
3434
array('foo' => 'bar'),
3535
(object) array(1, 2, 3),
3636
(object) array('foo' => 'bar'),
37-
# The PackedArray check will fail for instances of Persistable
37+
/* PackedArray cannot be serialized as a root document. Additionally, it
38+
* will fail return type validation for Persistable::bsonSerialize(). */
3839
MongoDB\BSON\PackedArray::fromPHP([1, 2, 3]),
3940
MongoDB\BSON\Document::fromPHP(['foo' => 'bar']),
4041
);
@@ -44,20 +45,28 @@ echo "Testing top-level objects\n";
4445
foreach ($tests as $test) {
4546
try {
4647
echo toJson(fromPHP(new MyDocument($test))), "\n";
48+
} catch (Exception $e) {
49+
printf("%s: %s\n", get_class($e), $e->getMessage());
50+
}
51+
try {
4752
echo toJson(fromPHP(new MyPersistableDocument($test))), "\n";
48-
} catch (MongoDB\Driver\Exception\UnexpectedValueException $e) {
49-
echo $e->getMessage(), "\n";
53+
} catch (Exception $e) {
54+
printf("%s: %s\n", get_class($e), $e->getMessage());
5055
}
5156
}
5257

5358
echo "\nTesting nested objects\n";
5459

5560
foreach ($tests as $test) {
5661
try {
57-
echo toJson(fromPHP(new MyDocument(array('nested' => new MyDocument($test))))), "\n";
58-
echo toJson(fromPHP(new MyDocument(array('nested' => new MyPersistableDocument($test))))), "\n";
59-
} catch (MongoDB\Driver\Exception\UnexpectedValueException $e) {
60-
echo $e->getMessage(), "\n";
62+
echo toJson(fromPHP(new MyDocument(['nested' => new MyDocument($test)]))), "\n";
63+
} catch (Exception $e) {
64+
printf("%s: %s\n", get_class($e), $e->getMessage());
65+
}
66+
try {
67+
echo toJson(fromPHP(new MyDocument(['nested' => new MyPersistableDocument($test)]))), "\n";
68+
} catch (Exception $e) {
69+
printf("%s: %s\n", get_class($e), $e->getMessage());
6170
}
6271
}
6372

@@ -74,8 +83,8 @@ Testing top-level objects
7483
{ "__pclass" : { "$binary" : "TXlQZXJzaXN0YWJsZURvY3VtZW50", "$type" : "80" }, "0" : 1, "1" : 2, "2" : 3 }
7584
{ "foo" : "bar" }
7685
{ "__pclass" : { "$binary" : "TXlQZXJzaXN0YWJsZURvY3VtZW50", "$type" : "80" }, "foo" : "bar" }
77-
{ "0" : 1, "1" : 2, "2" : 3 }
78-
Expected MyPersistableDocument::bsonSerialize() to return an array, stdClass, or MongoDB\BSON\Document, MongoDB\BSON\PackedArray given
86+
MongoDB\Driver\Exception\UnexpectedValueException: MongoDB\BSON\PackedArray cannot be serialized as a root document
87+
MongoDB\Driver\Exception\UnexpectedValueException: Expected MyPersistableDocument::bsonSerialize() to return an array, stdClass, or MongoDB\BSON\Document, MongoDB\BSON\PackedArray given
7988
{ "foo" : "bar" }
8089
{ "__pclass" : { "$binary" : "TXlQZXJzaXN0YWJsZURvY3VtZW50", "$type" : "80" }, "foo" : "bar" }
8190

@@ -89,7 +98,7 @@ Testing nested objects
8998
{ "nested" : { "foo" : "bar" } }
9099
{ "nested" : { "__pclass" : { "$binary" : "TXlQZXJzaXN0YWJsZURvY3VtZW50", "$type" : "80" }, "foo" : "bar" } }
91100
{ "nested" : [ 1, 2, 3 ] }
92-
Expected MyPersistableDocument::bsonSerialize() to return an array, stdClass, or MongoDB\BSON\Document, MongoDB\BSON\PackedArray given
101+
MongoDB\Driver\Exception\UnexpectedValueException: Expected MyPersistableDocument::bsonSerialize() to return an array, stdClass, or MongoDB\BSON\Document, MongoDB\BSON\PackedArray given
93102
{ "nested" : { "foo" : "bar" } }
94103
{ "nested" : { "__pclass" : { "$binary" : "TXlQZXJzaXN0YWJsZURvY3VtZW50", "$type" : "80" }, "foo" : "bar" } }
95104
===DONE===

tests/bson/bson-fromPHP_error-003.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class UnknownType implements MongoDB\BSON\Type {}
1010
$tests = array(
1111
new UnknownType,
1212
new MongoDB\BSON\Binary('foobar', MongoDB\BSON\Binary::TYPE_GENERIC),
13-
new MongoDB\BSON\Javascript('function foo(bar) {var baz = bar; var bar = foo; return bar; }'),
13+
new MongoDB\BSON\Javascript('function(bar) {var baz = bar; var bar = foo; return bar; }'),
1414
new MongoDB\BSON\MinKey,
1515
new MongoDB\BSON\MaxKey,
1616
new MongoDB\BSON\ObjectId,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
MongoDB\BSON\fromPHP(): PackedArray cannot be serialized as root document
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/../utils/basic.inc';
7+
8+
echo throws(function() {
9+
fromPHP(MongoDB\BSON\PackedArray::fromPHP([]));
10+
}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n";
11+
12+
?>
13+
===DONE===
14+
<?php exit(0); ?>
15+
--EXPECT--
16+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
17+
MongoDB\BSON\PackedArray cannot be serialized as a root document
18+
===DONE===

tests/bson/bson-javascript-001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ MongoDB\BSON\Javascript #001
55

66
require_once __DIR__ . '/../utils/basic.inc';
77

8-
$js = new MongoDB\BSON\Javascript("function foo(bar) {var baz = bar; var bar = foo; return bar; }");
9-
$jswscope = new MongoDB\BSON\Javascript("function foo(bar) {var baz = bar; var bar = foo; return bar; }", array("foo" => 42));
8+
$js = new MongoDB\BSON\Javascript("function(bar) {var baz = bar; var bar = foo; return bar; }");
9+
$jswscope = new MongoDB\BSON\Javascript("function(bar) {var baz = bar; var bar = foo; return bar; }", array("foo" => 42));
1010
$tests = array(
1111
array("js" => $js),
1212
array("js" => $jswscope),

tests/bson/bson-javascript-002.phpt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ MongoDB\BSON\Javascript debug handler
55

66
$tests = array(
77
array(
8-
'function foo(bar) { return bar; }',
8+
'function(bar) { return bar; }',
99
array(),
1010
),
1111
array(
12-
'function foo() { return foo; }',
12+
'function() { return foo; }',
1313
array('foo' => 42),
1414
),
1515
array(
16-
'function foo() { return id; }',
16+
'function() { return id; }',
1717
array('id' => new MongoDB\BSON\ObjectId('53e2a1c40640fd72175d4603')),
1818
),
1919
);
@@ -31,14 +31,14 @@ foreach ($tests as $test) {
3131
--EXPECTF--
3232
object(MongoDB\BSON\Javascript)#%d (%d) {
3333
["code"]=>
34-
string(33) "function foo(bar) { return bar; }"
34+
string(29) "function(bar) { return bar; }"
3535
["scope"]=>
3636
object(stdClass)#%d (%d) {
3737
}
3838
}
3939
object(MongoDB\BSON\Javascript)#%d (%d) {
4040
["code"]=>
41-
string(30) "function foo() { return foo; }"
41+
string(26) "function() { return foo; }"
4242
["scope"]=>
4343
object(stdClass)#%d (%d) {
4444
["foo"]=>
@@ -47,7 +47,7 @@ object(MongoDB\BSON\Javascript)#%d (%d) {
4747
}
4848
object(MongoDB\BSON\Javascript)#%d (%d) {
4949
["code"]=>
50-
string(29) "function foo() { return id; }"
50+
string(25) "function() { return id; }"
5151
["scope"]=>
5252
object(stdClass)#%d (%d) {
5353
["id"]=>

tests/bson/bson-javascript-clone-001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ MongoDB\BSON\Javascript can be cloned (PHP < 8.2)
88

99
require_once __DIR__ . '/../utils/basic.inc';
1010

11-
$javascript = new MongoDB\BSON\Javascript("function foo(bar) {var baz = bar; var bar = foo; return bar; }", ['foo' => 42]);
11+
$javascript = new MongoDB\BSON\Javascript("function(bar) {var baz = bar; var bar = foo; return bar; }", ['foo' => 42]);
1212

1313
$clone = clone $javascript;
1414

@@ -26,7 +26,7 @@ bool(true)
2626
bool(false)
2727
object(MongoDB\BSON\Javascript)#%d (%d) {
2828
["code"]=>
29-
string(62) "function foo(bar) {var baz = bar; var bar = foo; return bar; }"
29+
string(58) "function(bar) {var baz = bar; var bar = foo; return bar; }"
3030
["scope"]=>
3131
object(stdClass)#%d (%d) {
3232
["foo"]=>

tests/bson/bson-javascript-clone-002.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ MongoDB\BSON\Javascript can be cloned (PHP >= 8.2)
88

99
require_once __DIR__ . '/../utils/basic.inc';
1010

11-
$javascript = new MongoDB\BSON\Javascript("function foo(bar) {var baz = bar; var bar = foo; return bar; }", ['foo' => 42]);
11+
$javascript = new MongoDB\BSON\Javascript("function(bar) {var baz = bar; var bar = foo; return bar; }", ['foo' => 42]);
1212

1313
$clone = clone $javascript;
1414

@@ -26,7 +26,7 @@ bool(true)
2626
bool(false)
2727
object(MongoDB\BSON\Javascript)#%d (%d) {
2828
["code"]=>
29-
string(62) "function foo(bar) {var baz = bar; var bar = foo; return bar; }"
29+
string(58) "function(bar) {var baz = bar; var bar = foo; return bar; }"
3030
["scope"]=>
3131
object(stdClass)#%d (%d) {
3232
["foo"]=>

tests/bson/bson-javascript-getCode-001.phpt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ MongoDB\BSON\Javascript::getCode()
44
<?php
55

66
$tests = [
7-
['function foo(bar) { return bar; }', null],
8-
['function foo(bar) { return bar; }', []],
9-
['function foo() { return foo; }', ['foo' => 42]],
10-
['function foo() { return id; }', ['id' => new MongoDB\BSON\ObjectId('53e2a1c40640fd72175d4603')]],
7+
['function(bar) { return bar; }', null],
8+
['function(bar) { return bar; }', []],
9+
['function() { return foo; }', ['foo' => 42]],
10+
['function() { return id; }', ['id' => new MongoDB\BSON\ObjectId('53e2a1c40640fd72175d4603')]],
1111
];
1212

1313
foreach ($tests as $test) {
@@ -21,8 +21,8 @@ foreach ($tests as $test) {
2121
===DONE===
2222
<?php exit(0); ?>
2323
--EXPECT--
24-
string(33) "function foo(bar) { return bar; }"
25-
string(33) "function foo(bar) { return bar; }"
26-
string(30) "function foo() { return foo; }"
27-
string(29) "function foo() { return id; }"
24+
string(29) "function(bar) { return bar; }"
25+
string(29) "function(bar) { return bar; }"
26+
string(26) "function() { return foo; }"
27+
string(25) "function() { return id; }"
2828
===DONE===

tests/bson/bson-javascript-getScope-001.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ MongoDB\BSON\Javascript::getScope()
44
<?php
55

66
$tests = [
7-
['function foo(bar) { return bar; }', null],
8-
['function foo(bar) { return bar; }', []],
9-
['function foo() { return foo; }', ['foo' => 42]],
10-
['function foo() { return id; }', ['id' => new MongoDB\BSON\ObjectId('53e2a1c40640fd72175d4603')]],
7+
['function(bar) { return bar; }', null],
8+
['function(bar) { return bar; }', []],
9+
['function() { return foo; }', ['foo' => 42]],
10+
['function() { return id; }', ['id' => new MongoDB\BSON\ObjectId('53e2a1c40640fd72175d4603')]],
1111
];
1212

1313
foreach ($tests as $test) {

tests/bson/bson-javascript-get_properties-001.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ MongoDB\BSON\Javascript get_properties handler (get_object_vars)
44
<?php
55

66
$tests = [
7-
new MongoDB\BSON\Javascript('function foo(bar) { return bar; }'),
8-
new MongoDB\BSON\Javascript('function foo() { return bar; }', ['bar' => 42]),
7+
new MongoDB\BSON\Javascript('function(bar) { return bar; }'),
8+
new MongoDB\BSON\Javascript('function() { return bar; }', ['bar' => 42]),
99
];
1010

1111
foreach ($tests as $test) {
@@ -18,13 +18,13 @@ foreach ($tests as $test) {
1818
--EXPECTF--
1919
array(2) {
2020
["code"]=>
21-
string(33) "function foo(bar) { return bar; }"
21+
string(29) "function(bar) { return bar; }"
2222
["scope"]=>
2323
NULL
2424
}
2525
array(2) {
2626
["code"]=>
27-
string(30) "function foo() { return bar; }"
27+
string(26) "function() { return bar; }"
2828
["scope"]=>
2929
object(stdClass)#%d (%d) {
3030
["bar"]=>

tests/bson/bson-javascript-get_properties-002.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ MongoDB\BSON\Javascript get_properties handler (foreach)
44
<?php
55

66
$tests = [
7-
new MongoDB\BSON\Javascript('function foo(bar) { return bar; }'),
8-
new MongoDB\BSON\Javascript('function foo() { return bar; }', ['bar' => 42]),
7+
new MongoDB\BSON\Javascript('function(bar) { return bar; }'),
8+
new MongoDB\BSON\Javascript('function() { return bar; }', ['bar' => 42]),
99
];
1010

1111
foreach ($tests as $test) {
@@ -20,11 +20,11 @@ foreach ($tests as $test) {
2020
<?php exit(0); ?>
2121
--EXPECTF--
2222
string(4) "code"
23-
string(33) "function foo(bar) { return bar; }"
23+
string(29) "function(bar) { return bar; }"
2424
string(5) "scope"
2525
NULL
2626
string(4) "code"
27-
string(30) "function foo() { return bar; }"
27+
string(26) "function() { return bar; }"
2828
string(5) "scope"
2929
object(stdClass)#%d (%d) {
3030
["bar"]=>

tests/bson/bson-javascript-jsonserialize-001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ MongoDB\BSON\Javascript::jsonSerialize() return value (without scope)
33
--FILE--
44
<?php
55

6-
$js = new MongoDB\BSON\Javascript('function foo(bar) { return bar; }');
6+
$js = new MongoDB\BSON\Javascript('function(bar) { return bar; }');
77
var_dump($js->jsonSerialize());
88

99
?>
@@ -12,6 +12,6 @@ var_dump($js->jsonSerialize());
1212
--EXPECT--
1313
array(1) {
1414
["$code"]=>
15-
string(33) "function foo(bar) { return bar; }"
15+
string(29) "function(bar) { return bar; }"
1616
}
1717
===DONE===

tests/bson/bson-javascript-jsonserialize-002.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ MongoDB\BSON\Javascript::jsonSerialize() return value (with scope)
33
--FILE--
44
<?php
55

6-
$js = new MongoDB\BSON\Javascript('function foo(bar) { return bar; }', ['foo' => 42]);
6+
$js = new MongoDB\BSON\Javascript('function(bar) { return bar; }', ['foo' => 42]);
77
var_dump($js->jsonSerialize());
88

99
?>
@@ -12,7 +12,7 @@ var_dump($js->jsonSerialize());
1212
--EXPECTF--
1313
array(2) {
1414
["$code"]=>
15-
string(33) "function foo(bar) { return bar; }"
15+
string(29) "function(bar) { return bar; }"
1616
["$scope"]=>
1717
object(stdClass)#%d (%d) {
1818
["foo"]=>

0 commit comments

Comments
 (0)