|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
[2014-01-22 17:07 UTC] sean at persistencelabs dot com
[2014-02-03 18:56 UTC] [email protected]
-Type: Security
+Type: Bug
-Package: *General Issues
+Package: SQLite related
[2015-04-06 05:34 UTC] [email protected]
-Assigned To:
+Assigned To: stas
[2015-04-06 05:46 UTC] [email protected]
[2015-04-06 05:46 UTC] [email protected]
-Status: Assigned
+Status: Closed
[2015-04-15 18:06 UTC] [email protected]
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Wed Dec 31 05:00:01 2025 UTC |
Description: ------------ Summary ------- The sqlite3_close method, which is used to close a database connection, tears down the sqlite3_stmt objects associated with any prepared statements that have been created using the database. The prepared statements can still be accessed directly after this has occured, leading to a use-after-free condition. Impact ------ This bug leads to a fairly straightforward use-after-free scenario. Once the sqlite3_stmt object has been returned to the heap, the attacker may reallocate the buffer as they wish and then later access the memory as a sqlite3_stmt object through their handle to the containing php_sqlite3_stmt. Patch Details ------------- When the sqlite3_stmt objects are torn down the 'initialised' field of the containing php_sqlite3_stmt is set to 0. The attached patch adds a check on this field in each of the PHP_METHODs associated with an sqlite3stmt. Bug Details ----------- The trigger file below demonstrates the issue. We begin by setting a breakpoint on sqlite3Prepare in order to find the sqlite3_stmt that will be associated with our prepared statement. This is created in trigger.php via $stmt = $db->prepare('SELECT bar FROM foo WHERE id=:id'); Breakpoint 4, sqlite3Prepare (db=0x8a6ba58, zSql=0xb7eb9274 "SELECT bar FROM foo WHERE id=:id", nBytes=32, saveSqlFlag=1, pReprepare=0x0, ppStmt=0xb7fc3650, pzTail=0x0) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:90141 90141 char *zErrMsg = 0; /* Error message */ (gdb) The ppStmt variable is a sqlite3_stmt** and *ppStmt will be filled in with a pointer to the associated statement. (gdb) b 90286 Breakpoint 5 at 0x81caa03: file /home/user/php/ext/sqlite3/libsqlite/sqlite3.c, line 90286. (gdb) c Continuing. Breakpoint 5, sqlite3Prepare (db=0x8a6ba58, zSql=0xb7eb9274 "SELECT bar FROM foo WHERE id=:id", nBytes=32, saveSqlFlag=1, pReprepare=0x0, ppStmt=0xb7fc3650, pzTail=0x0) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:90286 90286 sqlite3StackFree(db, pParse); (gdb) p *ppStmt $7 = (sqlite3_stmt *) 0x8a78b08 Next we will use sqlite3_close to free the memory at 0x8a78b08. This is performed by the call to $db->close() in trigger.php resulting in a call to zim_sqlite3_close. File : ext/sqlite3/sqlite3.c 182 PHP_METHOD(sqlite3, close) 183 { 184 php_sqlite3_db_object *db_obj; 185 zval *object = getThis(); 186 int errcode; ... 192 193 if (db_obj->initialised) { 194 zend_llist_clean(&(db_obj->free_list)); 195 errcode = sqlite3_close(db_obj->db); ... 204 } On line 194 the zend_llist_clean function is called. The intention is to tear down any objects associated with the database object prior to destroying the object itself. Each object in the list has a registered destructor, and we eventually find ourselves in php_sqlite3_free_list_dtor (gdb) bt 4 #0 php_sqlite3_free_list_dtor (item=0xb7fc3674) at ext/sqlite3/sqlite3.c:2004 #1 0x0849b81d in zend_llist_destroy (l=0xb7fc361c) at Zend/zend_llist.c:112 #2 0x0849b887 in zend_llist_clean (l=0xb7fc361c) at Zend/zend_llist.c:124 #3 0x08134e5f in zim_sqlite3_close (ht=0, return_value=0xb7fc0bec, return_value_ptr=0xb7fa7100, this_ptr=0xb7fc0c08, return_value_used=0) at ext/sqlite3/sqlite3.c:194 (More stack frames follow...) File : ext/sqlite3/sqlite3.c 2002 static void php_sqlite3_free_list_dtor(void **item) 2003 { 2004 php_sqlite3_free_list *free_item = (php_sqlite3_free_list *)*item; 2005 2006 if (free_item->stmt_obj && free_item->stmt_obj->initialised) { 2007 sqlite3_finalize(free_item->stmt_obj->stmt); 2008 free_item->stmt_obj->initialised = 0; 2009 } 2010 efree(*item); 2011 } On line 2007 we can print the sqlite3_stmt *, to ensure it refers to our prepared statement (gdb) p/x free_item->stmt_obj->stmt $9 = 0x8a78b08 The function of interest to us is sqlite3VdbeDeleteObject, which is called to free all memory associated with a Vdbe (Virtual Database Engine) object. Apparently a sqlite3_stmt is also a Vdbe, and we can see our prepared statement is passed as the argument p. (gdb) bt 7 #0 sqlite3VdbeDeleteObject (db=0x8a6ba58, p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59981 #1 0x08184ded in sqlite3VdbeDelete (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:60016 #2 0x08142bdc in sqlite3VdbeFinalize (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59948 #3 0x08142b00 in sqlite3_finalize (pStmt=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:60876 #4 0x0813a828 in php_sqlite3_free_list_dtor (item=0xb7fc3674) at ext/sqlite3/sqlite3.c:2007 #5 0x0849b81d in zend_llist_destroy (l=0xb7fc361c) at Zend/zend_llist.c:112 #6 0x0849b887 in zend_llist_clean (l=0xb7fc361c) at Zend/zend_llist.c:124 (More stack frames follow...) File : ext/sqlite3/libsqlite/sqlite3.c 59971 /* 59972 ** Free all memory associated with the Vdbe passed as the second argument. 59973 ** The difference between this function and sqlite3VdbeDelete() is that 59974 ** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with 59975 ** the database connection. 59976 */ 59977 SQLITE_PRIVATE void sqlite3VdbeDeleteObject(sqlite3 *db, Vdbe *p){ ... 59994 sqlite3DbFree(db, p); ... 59995 } 18489 /* 18490 ** Free memory that might be associated with a particular database 18491 ** connection. 18492 */ 18493 SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ 18494 assert( db==0 || sqlite3_mutex_held(db->mutex) ); 18495 if( db ){ 18496 if( db->pnBytesFreed ){ 18497 *db->pnBytesFreed += sqlite3DbMallocSize(db, p); 18498 return; 18499 } 18500 if( isLookaside(db, p) ){ 18501 LookasideSlot *pBuf = (LookasideSlot*)p; 18502 pBuf->pNext = db->lookaside.pFree; 18503 db->lookaside.pFree = pBuf; 18504 db->lookaside.nOut--; 18505 return; 18506 } 18507 } ... 18512 sqlite3_free(p); 18513 } In order to reach sqlite3_free, a wrapper around free(), we just need to pass the isLookaside check. This is a simple range check on the span of the lookaside list which the sqlite3_stmt pointer will pass by default. isLookaside (db=0x8a6ba58, p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:18444 18444 return p && p>=db->lookaside.pStart && p<db->lookaside.pEnd; (gdb) p/x db->lookaside.pStart $10 = 0x8a6c918 (gdb) p/x db->lookaside.pEnd $11 = 0x8a78498 (gdb) p/x p $12 = 0x8a78b08 sqlite3_free eventually routes through to sqlite3MemFree which results in a free() call on the prepared statement. The actual address of the chunk associated with the sqlite3_stmt is ADDR-8 as sqlite adds 8 to the pointer returned by malloc and stores the size of the buffer in the first 8 bytes. (gdb) bt 11 #0 __GI___libc_free (mem=0x8a78b00) at malloc.c:2954 #1 0x0817b588 in sqlite3MemFree (pPrior=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:14795 #2 0x0813cd01 in sqlite3_free (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:18482 #3 0x08144b6f in sqlite3DbFree (db=0x8a6ba58, p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:18512 #4 0x0813c379 in sqlite3VdbeDeleteObject (db=0x8a6ba58, p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59994 #5 0x08184ded in sqlite3VdbeDelete (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:60016 #6 0x08142bdc in sqlite3VdbeFinalize (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59948 #7 0x08142b00 in sqlite3_finalize (pStmt=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:60876 #8 0x0813a828 in php_sqlite3_free_list_dtor (item=0xb7fc3674) at ext/sqlite3/sqlite3.c:2007 #9 0x0849b81d in zend_llist_destroy (l=0xb7fc361c) at Zend/zend_llist.c:112 #10 0x0849b887 in zend_llist_clean (l=0xb7fc361c) at Zend/zend_llist.c:124 (More stack frames follow...) At this point the sqlite3_stmt has been returned to the system allocator. However, the memory is still referenced by the php_sqlite3_stmt object, and is accessible through any of the functions on the statement object. In trigger.php we demonstrate this via $stmt->reset(). To illustrate this, after __GI___libc_free has returned we set the db pointer of the sqlite3_stmt object to be 0x41414141. (gdb) set {int}0x8a78b08=0x41414141 (gdb) p/x ((Vdbe*)0x8a78b08)->db $22 = 0x41414141 (gdb) c Continuing. The use-after-free condition can then be seen in the handling of the $stmt->reset() call. Program received signal SIGSEGV, Segmentation fault. 0x081d83ea in sqlite3VdbeHalt (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59669 59669 if( p->db->mallocFailed ){ (gdb) bt 5 #0 0x081d83ea in sqlite3VdbeHalt (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59669 #1 0x08142d40 in sqlite3VdbeReset (p=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:59874 #2 0x08142ce6 in sqlite3_reset (pStmt=0x8a78b08) at /home/user/php/ext/sqlite3/libsqlite/sqlite3.c:60898 #3 0x08137f0d in zim_sqlite3stmt_reset (ht=0, return_value=0xb7fc0bec, return_value_ptr=0xb7fa70e0, this_ptr=0xb7fc0bd0, return_value_used=0) at ext/sqlite3/sqlite3.c:1316 #4 0x08592b91 in zend_do_fcall_common_helper_SPEC (execute_data=0xb7fa71ac) at Zend/zend_vm_execute.h:554 (More stack frames follow...) (gdb) p/x p->db $25 = 0x41414141 In order to make use of this bug an attacker just needs to reallocate the free'd sqlite3_stmt as some controllable datastructure. They are then free to rewrite the contents before triggering the UAF via any of the methods of the $stmt object. EOF Test script: --------------- <?php $db = new SQLite3(':memory:'); $db->exec('CREATE TABLE foo (id INTEGER, bar STRING)'); $stmt = $db->prepare('SELECT bar FROM foo WHERE id=:id'); // Close the database connection and free the internal sqlite3_stmt object $db->close(); // Access the sqlite3_stmt object via the php_sqlite3_stmt container $stmt->reset(); ?>