Skip to content

SQLite3 authorizer callback $this freed via setAuthorizer during execution (Use After Free) #22122

@therealcoiffeur

Description

@therealcoiffeur

Description

Summary

SQLite invokes an authorizer callback during query parsing. zend_call_known_fcc does not addref fcc->object. From inside the authorizer, $db->setAuthorizer(null) destroys the FCC, releases $this, and frees it, method body continues on freed memory.

Vulnerable Source Code

// ext/sqlite3/sqlite3.c:2161-2197 -- authorizer dispatch
if (!ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) {
    return SQLITE_OK;
}
...
zend_call_known_fcc(
    &db_obj->authorizer_fcc,     // fcc->object NOT addref'd
    &retval, 5, argv, NULL
);

// ext/sqlite3/sqlite3.c:1284-1305 -- setAuthorizer replaces FCC
PHP_METHOD(SQLite3, setAuthorizer)
{
    ...
    if (ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) {
        zend_fcc_dtor(&db_obj->authorizer_fcc);  // <- OBJ_RELEASE($this) if refcount=1
    }
    ...
    zend_fcc_dup(&db_obj->authorizer_fcc, &fcc);
}

How to Trigger

<?php

class Auth {
    public string $state = "alive";

    public function authorize(int $action, ...$args): int {
        global $db;
        $db->setAuthorizer(null);
        echo $this->state;
        return SQLite3::OK;
    }
}

$db = new SQLite3(':memory:');
$auth = new Auth();
$db->setAuthorizer([$auth, 'authorize']);
unset($auth);

$db->exec('SELECT 1');

Command:

USE_ZEND_ALLOC=0 sapi/cli/php ../../Results/Findings/f2/poc.php

Output:

=================================================================
==34929==ERROR: AddressSanitizer: heap-use-after-free on address 0x606000024270 at pc 0x000105f1f928 bp 0x00016ba75990 sp 0x00016ba75988
READ of size 8 at 0x606000024270 thread T0
    #0 0x000105f1f924 in ZEND_FETCH_OBJ_R_SPEC_UNUSED_CONST_TAILCALL_INLINE_HANDLER zend_vm_execute.h:85603
    #1 0x000105b1a194 in execute_ex zend_vm_execute.h:110168
    #2 0x000105afac08 in zend_call_function zend_execute_API.c:1016
    #3 0x000105afcbfc in zend_call_known_function zend_execute_API.c:1114
    #4 0x000104676c4c in zend_call_known_fcc zend_API.h:863
    #5 0x00010465b474 in php_sqlite3_authorizer sqlite3.c:2200
    #6 0x000194ca7a50 in sqlite3Select+0x1a0 (libsqlite3.dylib:arm64e+0x1fa50)
    #7 0x000194c94310 in yy_reduce+0x141c (libsqlite3.dylib:arm64e+0xc310)
    #8 0x000194c91e8c in sqlite3RunParser+0x318 (libsqlite3.dylib:arm64e+0x9e8c)
    #9 0x000194c91348 in sqlite3Prepare+0x248 (libsqlite3.dylib:arm64e+0x9348)
    #10 0x000194c90f74 in sqlite3LockAndPrepare+0xdc (libsqlite3.dylib:arm64e+0x8f74)
    #11 0x000194c90c74 in sqlite3_exec+0x6e0 (libsqlite3.dylib:arm64e+0x8c74)
    #12 0x00010465c0d0 in zim_SQLite3_exec sqlite3.c:221
    #13 0x000105da2d34 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:54785
    #14 0x000105b1a194 in execute_ex zend_vm_execute.h:110168
    #15 0x000105b1ab28 in zend_execute zend_vm_execute.h:115586
    #16 0x0001060ed350 in zend_execute_script zend.c:1971
    #17 0x0001057464f4 in php_execute_script_ex main.c:2646
    #18 0x000105746a64 in php_execute_script main.c:2686
    #19 0x0001060f3b0c in do_cli php_cli.c:947
    #20 0x0001060f20cc in main php_cli.c:1370
    #21 0x00018c9cfdfc in start+0x1b4c (dyld:arm64e+0x1fdfc)

0x606000024270 is located 16 bytes inside of 56-byte region [0x606000024260,0x606000024298)
freed by thread T0 here:
    #0 0x000109338f10 in free+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x54f10)
    #1 0x0001059b523c in __zend_free zend_alloc.c:3571
    #2 0x0001059b8f50 in _efree zend_alloc.c:2788
    #3 0x00010606ce54 in zend_objects_store_del zend_objects_API.c:197
    #4 0x000104678b48 in zend_object_release zend_objects_API.h:76
    #5 0x000104664da8 in zend_fcc_dtor zend_API.h:807
    #6 0x0001046649ac in zim_SQLite3_setAuthorizer sqlite3.c:1302
    #7 0x000105da2d34 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:54785
    #8 0x000105b1a194 in execute_ex zend_vm_execute.h:110168
    #9 0x000105afac08 in zend_call_function zend_execute_API.c:1016
    #10 0x000105afcbfc in zend_call_known_function zend_execute_API.c:1114
    #11 0x000104676c4c in zend_call_known_fcc zend_API.h:863
    #12 0x00010465b474 in php_sqlite3_authorizer sqlite3.c:2200
    #13 0x000194ca7a50 in sqlite3Select+0x1a0 (libsqlite3.dylib:arm64e+0x1fa50)
    #14 0x000194c94310 in yy_reduce+0x141c (libsqlite3.dylib:arm64e+0xc310)
    #15 0x000194c91e8c in sqlite3RunParser+0x318 (libsqlite3.dylib:arm64e+0x9e8c)
    #16 0x000194c91348 in sqlite3Prepare+0x248 (libsqlite3.dylib:arm64e+0x9348)
    #17 0x000194c90f74 in sqlite3LockAndPrepare+0xdc (libsqlite3.dylib:arm64e+0x8f74)
    #18 0x000194c90c74 in sqlite3_exec+0x6e0 (libsqlite3.dylib:arm64e+0x8c74)
    #19 0x00010465c0d0 in zim_SQLite3_exec sqlite3.c:221
    #20 0x000105da2d34 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:54785
    #21 0x000105b1a194 in execute_ex zend_vm_execute.h:110168
    #22 0x000105b1ab28 in zend_execute zend_vm_execute.h:115586
    #23 0x0001060ed350 in zend_execute_script zend.c:1971
    #24 0x0001057464f4 in php_execute_script_ex main.c:2646
    #25 0x000105746a64 in php_execute_script main.c:2686
    #26 0x0001060f3b0c in do_cli php_cli.c:947
    #27 0x0001060f20cc in main php_cli.c:1370
    #28 0x00018c9cfdfc in start+0x1b4c (dyld:arm64e+0x1fdfc)

previously allocated by thread T0 here:
    #0 0x000109338e24 in malloc+0x70 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x54e24)
    #1 0x0001059b9560 in __zend_malloc zend_alloc.c:3543
    #2 0x0001059b8e24 in _emalloc zend_alloc.c:2778
    #3 0x00010606f580 in zend_objects_new zend_objects.c:190
    #4 0x0001059d2dbc in _object_and_properties_init zend_API.c:1819
    #5 0x0001059d2fd4 in object_init_ex zend_API.c:1842
    #6 0x000105db15e0 in ZEND_NEW_SPEC_CONST_UNUSED_TAILCALL_HANDLER zend_vm_execute.h:63944
    #7 0x000105b1a194 in execute_ex zend_vm_execute.h:110168
    #8 0x000105b1ab28 in zend_execute zend_vm_execute.h:115586
    #9 0x0001060ed350 in zend_execute_script zend.c:1971
    #10 0x0001057464f4 in php_execute_script_ex main.c:2646
    #11 0x000105746a64 in php_execute_script main.c:2686
    #12 0x0001060f3b0c in do_cli php_cli.c:947
    #13 0x0001060f20cc in main php_cli.c:1370
    #14 0x00018c9cfdfc in start+0x1b4c (dyld:arm64e+0x1fdfc)

SUMMARY: AddressSanitizer: heap-use-after-free zend_vm_execute.h:85603 in ZEND_FETCH_OBJ_R_SPEC_UNUSED_CONST_TAILCALL_INLINE_HANDLER
Shadow bytes around the buggy address:
  0x606000023f80: 00 00 00 01 fa fa fa fa 00 00 00 00 00 00 00 04
  0x606000024000: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
  0x606000024080: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
  0x606000024100: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00
  0x606000024180: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
=>0x606000024200: 00 00 00 00 00 00 00 00 fa fa fa fa fd fd[fd]fd
  0x606000024280: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fa
  0x606000024300: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
  0x606000024380: 00 00 00 00 00 00 00 00 fa fa fa fa fd fd fd fd
  0x606000024400: fd fd fd fd fa fa fa fa 00 00 00 00 00 00 00 fa
  0x606000024480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==34929==ABORTING
[1]    34929 abort      USE_ZEND_ALLOC=0 sapi/cli/php ../../Results/Findings/f2/poc.php

Note: Even though this could be used to execute arbitrary code or bypass disabled functions, GHSA-qq39-prv6-mxg7 is not part of PHP's threat model (which is wrong, but that's not my call).
"This is a bug, but not a security issue. Please report as a regular issue."

PHP Version

PHP 8.6.0-dev (cli) (built: May 16 2026 16:38:50) (NTS DEBUG)
Copyright © The PHP Group and Contributors
Zend Engine v4.6.0-dev, Copyright © Zend by Perforce
    with Zend OPcache v8.6.0-dev, Copyright ©, by Zend by Perforce

Operating System

No response

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions