Uploaded image for project: 'PHP Driver: Extension'
  1. PHP Driver: Extension
  2. PHPC-1151

Possible segfault is Session object is freed before Cursor

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Critical - P2 Critical - P2
    • 1.4.3
    • Affects Version/s: 1.4.0
    • Component/s: None
    • None

      I discovered this while investigating mongodb/mongo-php-library#517. If the MongoDB\Driver\Session object passed as a "session" option to an execute method is freed (i.e. mongoc_client_session_destroy() is called) while the cursor is still alive, a segfault could occur. This can manifest itself in a number of ways:

      Session is unset before first getMore

      In this example, the "test.test" collection on the replica set has more than 101 documents so that iteration will invoke a getMore command.

      $client = new MongoDB\Client(REPLICASET_URI);
      
      $session = $client->startSession();
      $cursor = $client->test->test->find([], ['session' => $session]);
      $count = 0;
      
      foreach ($cursor as $row) {
          unset($session);
          echo ++$count, "\n";
      }
      

      Backtrace:

      #0  0x00007fab9422601b in _bson_data (bson=0xbd10e80000000118) at /home/jmikola/workspace/mongodb/phpc/src/libbson/src/bson/bson.c:245
      #1  0x00007fab94227a69 in bson_append_document (bson=0x7ffe59ff1bb0, key=0x7fab942e52a6 "lsid", key_length=4, value=0xbd10e80000000118)
          at /home/jmikola/workspace/mongodb/phpc/src/libbson/src/bson/bson.c:1134
      #2  0x00007fab9425f241 in mongoc_cmd_parts_assemble (parts=0x7ffe59ff1a40, server_stream=0x23044e0, error=0x2305158)
          at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cmd.c:630
      #3  0x00007fab9426cf55 in _mongoc_cursor_run_command (cursor=0x2304f10, command=0x7ffe59ff1d60, opts=0x0, reply=0x2303f90)
          at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor.c:1395
      #4  0x00007fab9426fdd5 in _mongoc_cursor_cursorid_refresh_from_command (cursor=0x2304f10, command=0x7ffe59ff1d60, opts=0x0)
          at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor-cursorid.c:122
      #5  0x00007fab9427040b in _mongoc_cursor_cursorid_get_more (cursor=0x2304f10) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor-cursorid.c:264
      #6  0x00007fab942706b8 in _mongoc_cursor_cursorid_next (cursor=0x2304f10, bson=0x7ffe59ff1e78) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor-cursorid.c:323
      #7  0x00007fab9426e596 in mongoc_cursor_next (cursor=0x2304f10, bson=0x7ffe59ff1e78) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor.c:1829
      #8  0x00007fab942c94aa in php_phongo_cursor_iterator_move_forward (iter=0x7fab9489c480) at /home/jmikola/workspace/mongodb/phpc/src/MongoDB/Cursor.c:109
      #9  0x00000000009bf222 in ZEND_FE_FETCH_R_SPEC_VAR_HANDLER () at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_vm_execute.h:16846
      #10 0x0000000000999704 in execute_ex (ex=0x7fab94814030) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_vm_execute.h:429
      #11 0x0000000000999815 in zend_execute (op_array=0x7fab94885000, return_value=0x0) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_vm_execute.h:474
      #12 0x000000000093630d in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend.c:1482
      #13 0x000000000089b894 in php_execute_script (primary_file=0x7ffe59ff4810) at /tmp/build_php-7.1.15.LmM/php-7.1.15/main/main.c:2577
      #14 0x0000000000a2057a in do_cli (argc=2, argv=0x2193550) at /tmp/build_php-7.1.15.LmM/php-7.1.15/sapi/cli/php_cli.c:993
      #15 0x0000000000a2174d in main (argc=2, argv=0x2193550) at /tmp/build_php-7.1.15.LmM/php-7.1.15/sapi/cli/php_cli.c:1381
      
      Session is unset before killCursors

      In this example, the "test.test" collection on the replica set has a non-zero number of documents. The cursor returned by the find command has a non-zero ID, and libmongoc will attempt to kill the cursor when the PHP object is freed.

      $client = new MongoDB\Client(REPLICASET_URI);
      
      $cursor = $client->test->test->find([], ['session' => $client->startSession()]);
      

      Backtrace:

      #0  0x00007f960842601b in _bson_data (bson=0x7365740000000518) at /home/jmikola/workspace/mongodb/phpc/src/libbson/src/bson/bson.c:245
      #1  0x00007f9608427a69 in bson_append_document (bson=0x7ffc10fa4860, key=0x7f96084e52a6 "lsid", key_length=4, value=0x7365740000000518)
          at /home/jmikola/workspace/mongodb/phpc/src/libbson/src/bson/bson.c:1134
      #2  0x00007f960845f241 in mongoc_cmd_parts_assemble (parts=0x7ffc10fa46f0, server_stream=0x2371f10, error=0x0) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cmd.c:630
      #3  0x00007f96084533eb in _mongoc_client_killcursors_command (cluster=0x2373c78, server_stream=0x2371f10, cursor_id=61008089270, db=0x7ffc10fa4980 "test", collection=0x2377449 "test", 
          cs=0x23769c0) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-client.c:2242
      #4  0x00007f9608452b27 in _mongoc_client_kill_cursor (client=0x2373c70, server_id=1, cursor_id=61008089270, operation_id=1804289384, db=0x7ffc10fa4980 "test", collection=0x2377449 "test", 
          cs=0x23769c0) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-client.c:2030
      #5  0x00007f9608469c0a in _mongoc_cursor_destroy (cursor=0x2377290) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor.c:546
      #6  0x00007f960846faad in _mongoc_cursor_cursorid_destroy (cursor=0x2377290) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor-cursorid.c:60
      #7  0x00007f9608469a0d in mongoc_cursor_destroy (cursor=0x2377290) at /home/jmikola/workspace/mongodb/phpc/src/libmongoc/src/mongoc/mongoc-cursor.c:520
      #8  0x00007f96084c9c8b in php_phongo_cursor_free_object (object=0x7f9608ac86b8) at /home/jmikola/workspace/mongodb/phpc/src/MongoDB/Cursor.c:373
      #9  0x0000000000988f93 in zend_objects_store_del (object=0x7f9608ac86b8) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_objects_API.c:178
      #10 0x000000000093253e in _zval_dtor_func (p=0x7f9608ac86b8, __zend_filename=0xf989e8 "/tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_execute_API.c", __zend_lineno=210)
          at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_variables.c:56
      #11 0x00000000009193e8 in i_zval_ptr_dtor (zval_ptr=0x7f9608a14090, __zend_filename=0xf989e8 "/tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_execute_API.c", __zend_lineno=210)
          at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_variables.h:48
      #12 0x0000000000919f6b in zend_unclean_zval_ptr_dtor (zv=0x7f9608a14090) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_execute_API.c:210
      #13 0x000000000094b501 in _zend_hash_del_el_ex (ht=0x134cfd0 <executor_globals+304>, idx=8, p=0x7f9608a59200, prev=0x0) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_hash.c:997
      #14 0x000000000094b5e1 in _zend_hash_del_el (ht=0x134cfd0 <executor_globals+304>, idx=8, p=0x7f9608a59200) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_hash.c:1020
      #15 0x000000000094cecb in zend_hash_reverse_apply (ht=0x134cfd0 <executor_globals+304>, apply_func=0x919ed6 <zval_call_destructor>) at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_hash.c:1602
      #16 0x000000000091a174 in shutdown_destructors () at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend_execute_API.c:242
      #17 0x0000000000934c1e in zend_call_destructors () at /tmp/build_php-7.1.15.LmM/php-7.1.15/Zend/zend.c:990
      #18 0x000000000089a151 in php_request_shutdown (dummy=0x0) at /tmp/build_php-7.1.15.LmM/php-7.1.15/main/main.c:1849
      #19 0x0000000000a20ecc in do_cli (argc=2, argv=0x2205550) at /tmp/build_php-7.1.15.LmM/php-7.1.15/sapi/cli/php_cli.c:1160
      #20 0x0000000000a2174d in main (argc=2, argv=0x2205550) at /tmp/build_php-7.1.15.LmM/php-7.1.15/sapi/cli/php_cli.c:1381
      
      Cases that do not produce segfaults

      Noting that passing $client->startSession() as the "session" object is the easiest way to reproduce this error, because the Session is freed immediately after the PHP statement is executed, there are a few cases that do not produce a segfault:

      Querying or aggregating an empty collection with an ephemeral session object isn't an issue. I believe this is because the find or aggregate command returns a cursor with a zero ID. Therefore, libmongoc does not attempt to send a killCursors command when the cursor is destroyed.

      Write methods and basic commands (e.g. ping) do not crash. Similarly, I believe this is because the ephemeral session lasts long enough to execute the command and libmongoc never needs to access it again. In the case of basic commands, the ID of the cursor is zero.

            Assignee:
            jmikola@mongodb.com Jeremy Mikola
            Reporter:
            jmikola@mongodb.com Jeremy Mikola
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: