|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2014-01-22 17:06 UTC] sean at persistencelabs dot com
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();
?>
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Oct 26 20:00:01 2025 UTC |
From 8eb7e567ebfc4abf7ffe754370301565796a42fd Mon Sep 17 00:00:00 2001 From: Sean Heelan <sean@persistencelabs.com> Date: Fri, 20 Dec 2013 21:29:50 +0000 Subject: [PATCH] Check that php_sqlite3_stmt objects are in a valid state prior to using them --- ext/sqlite3/sqlite3.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 19bf58d..e7c9726 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1274,6 +1274,8 @@ PHP_METHOD(sqlite3stmt, paramCount) php_sqlite3_stmt *stmt_obj; zval *object = getThis(); stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) if (zend_parse_parameters_none() == FAILURE) { return; @@ -1290,6 +1292,8 @@ PHP_METHOD(sqlite3stmt, close) php_sqlite3_stmt *stmt_obj; zval *object = getThis(); stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) if (zend_parse_parameters_none() == FAILURE) { return; @@ -1308,6 +1312,8 @@ PHP_METHOD(sqlite3stmt, reset) php_sqlite3_stmt *stmt_obj; zval *object = getThis(); stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) if (zend_parse_parameters_none() == FAILURE) { return; @@ -1328,6 +1334,8 @@ PHP_METHOD(sqlite3stmt, clear) php_sqlite3_stmt *stmt_obj; zval *object = getThis(); stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) if (zend_parse_parameters_none() == FAILURE) { return; @@ -1349,6 +1357,8 @@ PHP_METHOD(sqlite3stmt, readOnly) php_sqlite3_stmt *stmt_obj; zval *object = getThis(); stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) if (zend_parse_parameters_none() == FAILURE) { return; @@ -1416,6 +1426,8 @@ PHP_METHOD(sqlite3stmt, bindParam) zval *object = getThis(); struct php_sqlite3_bound_param param = {0}; stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) param.param_number = -1; param.type = SQLITE3_TEXT; @@ -1447,6 +1459,8 @@ PHP_METHOD(sqlite3stmt, bindValue) zval *object = getThis(); struct php_sqlite3_bound_param param = {0}; stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) param.param_number = -1; param.type = SQLITE3_TEXT; @@ -1482,6 +1496,8 @@ PHP_METHOD(sqlite3stmt, execute) stmt_obj = (php_sqlite3_stmt *)zend_object_store_get_object(object TSRMLS_CC); + SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3) + if (zend_parse_parameters_none() == FAILURE) { return; } -- 1.7.9.5