PHPC-671: Avoid mongoc_client_t use-after-free by Cursor and Server

This commit is contained in:
Jeremy Mikola
2016-05-28 19:42:11 -04:00
parent 1978caad20
commit 05fcc2ea4f
11 changed files with 171 additions and 42 deletions

View File

@@ -224,7 +224,7 @@ static void php_phongo_log(mongoc_log_level_t log_level, const char *log_domain,
/* }}} */
/* {{{ Init objects */
static void phongo_cursor_init(zval *return_value, mongoc_cursor_t *cursor, mongoc_client_t *client TSRMLS_DC) /* {{{ */
static void phongo_cursor_init(zval *return_value, zval *manager, mongoc_cursor_t *cursor TSRMLS_DC) /* {{{ */
{
php_phongo_cursor_t *intern;
@@ -233,18 +233,32 @@ static void phongo_cursor_init(zval *return_value, mongoc_cursor_t *cursor, mong
intern = Z_CURSOR_OBJ_P(return_value);
intern->cursor = cursor;
intern->server_id = mongoc_cursor_get_hint(cursor);
intern->client = client;
intern->client = Z_MANAGER_OBJ_P(manager)->client;
#if PHP_VERSION_ID >= 70000
ZVAL_COPY(&intern->manager, manager);
#else
Z_ADDREF_P(manager);
intern->manager = manager;
#endif
} /* }}} */
void phongo_server_init(zval *return_value, mongoc_client_t *client, int server_id TSRMLS_DC) /* {{{ */
void phongo_server_init(zval *return_value, zval *manager, int server_id TSRMLS_DC) /* {{{ */
{
php_phongo_server_t *server;
object_init_ex(return_value, php_phongo_server_ce);
server = Z_SERVER_OBJ_P(return_value);
server->client = client;
server->server_id = server_id;
server->client = Z_MANAGER_OBJ_P(manager)->client;
#if PHP_VERSION_ID >= 70000
ZVAL_COPY(&server->manager, manager);
#else
Z_ADDREF_P(manager);
server->manager = manager;
#endif
}
/* }}} */
@@ -467,7 +481,7 @@ zend_bool phongo_writeerror_init(zval *return_value, bson_t *bson TSRMLS_DC) /*
return true;
} /* }}} */
php_phongo_writeresult_t *phongo_writeresult_init(zval *return_value, bson_t *reply, mongoc_client_t *client, int server_id TSRMLS_DC) /* {{{ */
php_phongo_writeresult_t *phongo_writeresult_init(zval *return_value, bson_t *reply, zval *manager, int server_id TSRMLS_DC) /* {{{ */
{
php_phongo_writeresult_t *writeresult;
@@ -475,8 +489,15 @@ php_phongo_writeresult_t *phongo_writeresult_init(zval *return_value, bson_t *re
writeresult = Z_WRITERESULT_OBJ_P(return_value);
writeresult->reply = bson_copy(reply);
writeresult->client = client;
writeresult->server_id = server_id;
writeresult->client = Z_MANAGER_OBJ_P(manager)->client;
#if PHP_VERSION_ID >= 70000
ZVAL_COPY(&writeresult->manager, manager);
#else
Z_ADDREF_P(manager);
writeresult->manager = manager;
#endif
return writeresult;
} /* }}} */
@@ -506,8 +527,9 @@ mongoc_bulk_operation_t *phongo_bulkwrite_init(zend_bool ordered) { /* {{{ */
return mongoc_bulk_operation_new(ordered);
} /* }}} */
bool phongo_execute_write(mongoc_client_t *client, const char *namespace, mongoc_bulk_operation_t *bulk, const mongoc_write_concern_t *write_concern, int server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */
bool phongo_execute_write(zval *manager, const char *namespace, mongoc_bulk_operation_t *bulk, const mongoc_write_concern_t *write_concern, int server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */
{
mongoc_client_t *client;
bson_error_t error;
char *dbname;
char *collname;
@@ -515,6 +537,8 @@ bool phongo_execute_write(mongoc_client_t *client, const char *namespace, mongoc
bson_t reply = BSON_INITIALIZER;
php_phongo_writeresult_t *writeresult;
client = Z_MANAGER_OBJ_P(manager)->client;
if (!phongo_split_namespace(namespace, &dbname, &collname)) {
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s: %s", "Invalid namespace provided", namespace);
return false;
@@ -553,7 +577,7 @@ bool phongo_execute_write(mongoc_client_t *client, const char *namespace, mongoc
return false;
}
writeresult = phongo_writeresult_init(return_value, &reply, client, bulk->hint TSRMLS_CC);
writeresult = phongo_writeresult_init(return_value, &reply, manager, bulk->hint TSRMLS_CC);
writeresult->write_concern = mongoc_write_concern_copy(write_concern);
/* The Write failed */
@@ -571,14 +595,17 @@ bool phongo_execute_write(mongoc_client_t *client, const char *namespace, mongoc
return success;
} /* }}} */
int phongo_execute_query(mongoc_client_t *client, const char *namespace, const php_phongo_query_t *query, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */
int phongo_execute_query(zval *manager, const char *namespace, const php_phongo_query_t *query, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */
{
mongoc_client_t *client;
const bson_t *doc = NULL;
mongoc_cursor_t *cursor;
char *dbname;
char *collname;
mongoc_collection_t *collection;
client = Z_MANAGER_OBJ_P(manager)->client;
if (!phongo_split_namespace(namespace, &dbname, &collname)) {
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s: %s", "Invalid namespace provided", namespace);
return false;
@@ -626,17 +653,19 @@ int phongo_execute_query(mongoc_client_t *client, const char *namespace, const p
return true;
}
phongo_cursor_init(return_value, cursor, client TSRMLS_CC);
phongo_cursor_init(return_value, manager, cursor TSRMLS_CC);
return true;
} /* }}} */
int phongo_execute_command(mongoc_client_t *client, const char *db, const bson_t *command, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */
int phongo_execute_command(zval *manager, const char *db, const bson_t *command, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC) /* {{{ */
{
mongoc_client_t *client;
mongoc_cursor_t *cursor;
const bson_t *doc;
bson_iter_t iter;
bson_iter_t child;
client = Z_MANAGER_OBJ_P(manager)->client;
cursor = mongoc_client_command(client, db, MONGOC_QUERY_NONE, 0, 1, 0, command, NULL, read_preference);
if (server_id > 0) {
@@ -702,7 +731,7 @@ int phongo_execute_command(mongoc_client_t *client, const char *db, const bson_t
mongoc_cursor_next(cursor, &doc);
}
phongo_cursor_init(return_value, cursor, client TSRMLS_CC);
phongo_cursor_init(return_value, manager, cursor TSRMLS_CC);
return true;
} /* }}} */

View File

@@ -114,15 +114,15 @@ void phongo_throw_exception_from_bson_error_t(bson_error_t *error TSRMLS_DC);
PHONGO_API zend_object_handlers *phongo_get_std_object_handlers(void);
void phongo_server_init (zval *return_value, mongoc_client_t *client, int server_id TSRMLS_DC);
void phongo_server_init (zval *return_value, zval *manager, int server_id TSRMLS_DC);
void phongo_readconcern_init (zval *return_value, const mongoc_read_concern_t *read_concern TSRMLS_DC);
void phongo_readpreference_init (zval *return_value, const mongoc_read_prefs_t *read_prefs TSRMLS_DC);
void phongo_writeconcern_init (zval *return_value, const mongoc_write_concern_t *write_concern TSRMLS_DC);
bool phongo_query_init (php_phongo_query_t *query, bson_t *filter, bson_t *options TSRMLS_DC);
mongoc_bulk_operation_t* phongo_bulkwrite_init (zend_bool ordered);
bool phongo_execute_write (mongoc_client_t *client, const char *namespace, mongoc_bulk_operation_t *bulk, const mongoc_write_concern_t *write_concern, int server_id, zval *return_value, int return_value_used TSRMLS_DC);
int phongo_execute_command (mongoc_client_t *client, const char *db, const bson_t *command, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC);
int phongo_execute_query (mongoc_client_t *client, const char *namespace, const php_phongo_query_t *query, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC);
bool phongo_execute_write (zval *manager, const char *namespace, mongoc_bulk_operation_t *bulk, const mongoc_write_concern_t *write_concern, int server_id, zval *return_value, int return_value_used TSRMLS_DC);
int phongo_execute_command (zval *manager, const char *db, const bson_t *command, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC);
int phongo_execute_query (zval *manager, const char *namespace, const php_phongo_query_t *query, const mongoc_read_prefs_t *read_preference, int server_id, zval *return_value, int return_value_used TSRMLS_DC);
mongoc_stream_t* phongo_stream_initiator (const mongoc_uri_t *uri, const mongoc_host_list_t *host, void *user_data, bson_error_t *error);
const mongoc_read_concern_t* phongo_read_concern_from_zval (zval *zread_concern TSRMLS_DC);

View File

@@ -40,6 +40,7 @@ typedef struct {
typedef struct {
zend_object std;
mongoc_cursor_t *cursor;
zval *manager;
mongoc_client_t *client;
int server_id;
php_phongo_bson_state visitor_data;
@@ -80,6 +81,7 @@ typedef struct {
typedef struct {
zend_object std;
zval *manager;
mongoc_client_t *client;
int server_id;
} php_phongo_server_t;
@@ -113,6 +115,7 @@ typedef struct {
zend_object std;
mongoc_write_concern_t *write_concern;
bson_t *reply;
zval *manager;
mongoc_client_t *client;
int server_id;
} php_phongo_writeresult_t;

View File

@@ -39,6 +39,7 @@ typedef struct {
typedef struct {
mongoc_cursor_t *cursor;
zval manager;
mongoc_client_t *client;
int server_id;
php_phongo_bson_state visitor_data;
@@ -79,6 +80,7 @@ typedef struct {
} php_phongo_readpreference_t;
typedef struct {
zval manager;
mongoc_client_t *client;
int server_id;
zend_object std;
@@ -112,6 +114,7 @@ typedef struct {
typedef struct {
mongoc_write_concern_t *write_concern;
bson_t *reply;
zval manager;
mongoc_client_t *client;
int server_id;
zend_object std;

View File

@@ -157,8 +157,11 @@ PHP_METHOD(Cursor, getServer)
return;
}
phongo_server_init(return_value, intern->cursor->client, intern->server_id TSRMLS_CC);
#if PHP_VERSION_ID >= 70000
phongo_server_init(return_value, &intern->manager, intern->server_id TSRMLS_CC);
#else
phongo_server_init(return_value, intern->manager, intern->server_id TSRMLS_CC);
#endif
}
/* }}} */
@@ -222,6 +225,8 @@ static void php_phongo_cursor_free_object(phongo_free_object_arg *object TSRMLS_
php_phongo_cursor_free(intern);
zval_ptr_dtor(&intern->manager);
#if PHP_VERSION_ID < 70000
efree(intern);
#endif

View File

@@ -89,7 +89,6 @@ PHP_METHOD(Manager, __construct)
Execute a command */
PHP_METHOD(Manager, executeCommand)
{
php_phongo_manager_t *intern;
char *db;
phongo_zpp_char_len db_len;
zval *command;
@@ -98,23 +97,19 @@ PHP_METHOD(Manager, executeCommand)
DECLARE_RETURN_VALUE_USED
SUPPRESS_UNUSED_WARNING(return_value_ptr)
intern = Z_MANAGER_OBJ_P(getThis());
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO|O!", &db, &db_len, &command, php_phongo_command_ce, &readPreference, php_phongo_readpreference_ce) == FAILURE) {
return;
}
cmd = Z_COMMAND_OBJ_P(command);
phongo_execute_command(intern->client, db, cmd->bson, phongo_read_preference_from_zval(readPreference TSRMLS_CC), -1, return_value, return_value_used TSRMLS_CC);
phongo_execute_command(getThis(), db, cmd->bson, phongo_read_preference_from_zval(readPreference TSRMLS_CC), -1, return_value, return_value_used TSRMLS_CC);
}
/* }}} */
/* {{{ proto MongoDB\Driver\Cursor Manager::executeQuery(string $namespace, MongoDB\Driver\Query $zquery[, MongoDB\Driver\ReadPreference $readPreference = null])
Execute a Query */
PHP_METHOD(Manager, executeQuery)
{
php_phongo_manager_t *intern;
char *namespace;
phongo_zpp_char_len namespace_len;
zval *zquery;
@@ -122,22 +117,18 @@ PHP_METHOD(Manager, executeQuery)
DECLARE_RETURN_VALUE_USED
SUPPRESS_UNUSED_WARNING(return_value_ptr)
intern = Z_MANAGER_OBJ_P(getThis());
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO|O!", &namespace, &namespace_len, &zquery, php_phongo_query_ce, &readPreference, php_phongo_readpreference_ce) == FAILURE) {
return;
}
phongo_execute_query(intern->client, namespace, phongo_query_from_zval(zquery TSRMLS_CC), phongo_read_preference_from_zval(readPreference TSRMLS_CC), -1, return_value, return_value_used TSRMLS_CC);
phongo_execute_query(getThis(), namespace, phongo_query_from_zval(zquery TSRMLS_CC), phongo_read_preference_from_zval(readPreference TSRMLS_CC), -1, return_value, return_value_used TSRMLS_CC);
}
/* }}} */
/* {{{ proto MongoDB\Driver\WriteResult Manager::executeBulkWrite(string $namespace, MongoDB\Driver\BulkWrite $zbulk[, MongoDB\Driver\WriteConcern $writeConcern = null])
Executes a write operation bulk (e.g. insert, update, delete) */
PHP_METHOD(Manager, executeBulkWrite)
{
php_phongo_manager_t *intern;
char *namespace;
phongo_zpp_char_len namespace_len;
zval *zbulk;
@@ -146,16 +137,13 @@ PHP_METHOD(Manager, executeBulkWrite)
DECLARE_RETURN_VALUE_USED
SUPPRESS_UNUSED_WARNING(return_value_ptr)
intern = Z_MANAGER_OBJ_P(getThis());
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO|O!", &namespace, &namespace_len, &zbulk, php_phongo_bulkwrite_ce, &zwrite_concern, php_phongo_writeconcern_ce) == FAILURE) {
return;
}
bulk = Z_BULKWRITE_OBJ_P(zbulk);
phongo_execute_write(intern->client, namespace, bulk->bulk, phongo_write_concern_from_zval(zwrite_concern TSRMLS_CC), -1, return_value, return_value_used TSRMLS_CC);
phongo_execute_write(getThis(), namespace, bulk->bulk, phongo_write_concern_from_zval(zwrite_concern TSRMLS_CC), -1, return_value, return_value_used TSRMLS_CC);
}
/* }}} */
@@ -230,12 +218,12 @@ PHP_METHOD(Manager, getServers)
}
#if PHP_VERSION_ID >= 70000
phongo_server_init(&obj, intern->client, ((mongoc_server_description_t *)set->items[i].item)->id TSRMLS_CC);
phongo_server_init(&obj, getThis(), ((mongoc_server_description_t *)set->items[i].item)->id TSRMLS_CC);
add_next_index_zval(return_value, &obj);
#else
MAKE_STD_ZVAL(obj);
phongo_server_init(obj, intern->client, sd->id TSRMLS_CC);
phongo_server_init(obj, getThis(), sd->id TSRMLS_CC);
add_next_index_zval(return_value, obj);
#endif
}
@@ -281,7 +269,7 @@ PHP_METHOD(Manager, selectServer)
readPreference = phongo_read_preference_from_zval(zreadPreference TSRMLS_CC);
selected_server = mongoc_topology_select(intern->client->topology, MONGOC_SS_READ, readPreference, MONGOC_SS_DEFAULT_LOCAL_THRESHOLD_MS, &error);
if (selected_server) {
phongo_server_init(return_value, intern->client, selected_server->id TSRMLS_CC);
phongo_server_init(return_value, getThis(), selected_server->id TSRMLS_CC);
mongoc_server_description_destroy(selected_server);
} else {
/* Check for connection related exceptions */

View File

@@ -80,7 +80,11 @@ PHP_METHOD(Server, executeCommand)
cmd = Z_COMMAND_OBJ_P(command);
phongo_execute_command(intern->client, db, cmd->bson, phongo_read_preference_from_zval(readPreference TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#if PHP_VERSION_ID >= 70000
phongo_execute_command(&intern->manager, db, cmd->bson, phongo_read_preference_from_zval(readPreference TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#else
phongo_execute_command(intern->manager, db, cmd->bson, phongo_read_preference_from_zval(readPreference TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#endif
}
/* }}} */
/* {{{ proto MongoDB\Driver\Cursor Server::executeQuery(string $namespace, MongoDB\Driver\Query $zquery[, MongoDB\Driver\ReadPreference $readPreference = null]))
@@ -102,8 +106,11 @@ PHP_METHOD(Server, executeQuery)
return;
}
phongo_execute_query(intern->client, namespace, phongo_query_from_zval(zquery TSRMLS_CC), phongo_read_preference_from_zval(readPreference TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#if PHP_VERSION_ID >= 70000
phongo_execute_query(&intern->manager, namespace, phongo_query_from_zval(zquery TSRMLS_CC), phongo_read_preference_from_zval(readPreference TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#else
phongo_execute_query(intern->manager, namespace, phongo_query_from_zval(zquery TSRMLS_CC), phongo_read_preference_from_zval(readPreference TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#endif
}
/* }}} */
/* {{{ proto MongoDB\Driver\WriteResult Server::executeBulkWrite(string $namespace, MongoDB\Driver\BulkWrite $zbulk[, MongoDB\Driver\WriteConcern $writeConcern = null])
@@ -128,7 +135,11 @@ PHP_METHOD(Server, executeBulkWrite)
bulk = Z_BULKWRITE_OBJ_P(zbulk);
phongo_execute_write(intern->client, namespace, bulk->bulk, phongo_write_concern_from_zval(zwrite_concern TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#if PHP_VERSION_ID >= 70000
phongo_execute_write(&intern->manager, namespace, bulk->bulk, phongo_write_concern_from_zval(zwrite_concern TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#else
phongo_execute_write(intern->manager, namespace, bulk->bulk, phongo_write_concern_from_zval(zwrite_concern TSRMLS_CC), intern->server_id, return_value, return_value_used TSRMLS_CC);
#endif
}
/* }}} */
/* {{{ proto string Server::getHost()
@@ -522,6 +533,8 @@ static void php_phongo_server_free_object(phongo_free_object_arg *object TSRMLS_
zend_object_std_dtor(&intern->std TSRMLS_CC);
zval_ptr_dtor(&intern->manager);
#if PHP_VERSION_ID < 70000
efree(intern);
#endif

View File

@@ -155,8 +155,11 @@ PHP_METHOD(WriteResult, getServer)
return;
}
phongo_server_init(return_value, intern->client, intern->server_id TSRMLS_CC);
#if PHP_VERSION_ID >= 70000
phongo_server_init(return_value, &intern->manager, intern->server_id TSRMLS_CC);
#else
phongo_server_init(return_value, intern->manager, intern->server_id TSRMLS_CC);
#endif
}
/* }}} */
/* {{{ proto array WriteResult::getUpsertedIds()
@@ -422,6 +425,8 @@ static void php_phongo_writeresult_free_object(phongo_free_object_arg *object TS
mongoc_write_concern_destroy(intern->write_concern);
}
zval_ptr_dtor(&intern->manager);
#if PHP_VERSION_ID < 70000
efree(intern);
#endif

View File

@@ -0,0 +1,26 @@
--TEST--
PHPC-671: Segfault if Manager is already freed when destructing live Cursor
--SKIPIF--
<?php require __DIR__ . "/../utils/basic-skipif.inc"; CLEANUP(STANDALONE) ?>
--FILE--
<?php
require_once __DIR__ . "/../utils/basic.inc";
$manager = new MongoDB\Driver\Manager(STANDALONE);
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert(['_id' => 1]);
$bulk->insert(['_id' => 2]);
$bulk->insert(['_id' => 3]);
$manager->executeBulkWrite(NS, $bulk);
$cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([], ['batchSize' => 2]));
unset($manager);
unset($cursor);
?>
===DONE===
<?php exit(0); ?>
--EXPECT--
===DONE===

View File

@@ -0,0 +1,25 @@
--TEST--
PHPC-671: Segfault if Manager is already freed when using selected Server
--SKIPIF--
<?php require __DIR__ . "/../utils/basic-skipif.inc"; CLEANUP(STANDALONE) ?>
--FILE--
<?php
require_once __DIR__ . "/../utils/basic.inc";
$manager = new MongoDB\Driver\Manager(STANDALONE);
$server = $manager->selectServer(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRIMARY));
unset($manager);
$cursor = $server->executeCommand(DATABASE_NAME, new MongoDB\Driver\Command(['ping' => 1]));
var_dump($cursor->toArray()[0]);
?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
object(stdClass)#%d (%d) {
["ok"]=>
float(1)
}
===DONE===

View File

@@ -0,0 +1,32 @@
--TEST--
PHPC-671: Segfault if Manager is already freed when using WriteResult's Server
--SKIPIF--
<?php require __DIR__ . "/../utils/basic-skipif.inc"; CLEANUP(STANDALONE) ?>
--FILE--
<?php
require_once __DIR__ . "/../utils/basic.inc";
$manager = new MongoDB\Driver\Manager(STANDALONE);
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->insert(['_id' => 1]);
$writeResult = $manager->executeBulkWrite(NS, $bulk);
unset($manager);
$server = $writeResult->getServer();
/* WriteResult only uses the client to construct a Server. We need to interact
* with the Server to test for a user-after-free. */
$cursor = $server->executeCommand(DATABASE_NAME, new MongoDB\Driver\Command(['ping' => 1]));
var_dump($cursor->toArray()[0]);
?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
object(stdClass)#%d (%d) {
["ok"]=>
float(1)
}
===DONE===