php / php-src

The PHP Interpreter
https://www.php.net
Other
38.16k stars 7.75k forks source link

UAF in session_encode() #16590

Open chibinz opened 6 days ago

chibinz commented 6 days ago

Description

The following code:

<?php

class C {
    function __serialize() {
        $_SESSION = [];
        return [];
    }
}

session_start();

$_SESSION['Lz'] = new C;
for ($i = 0; $i < 100000; $i++) {
    $_SESSION[(string) $i] = $i;
}

session_encode();

Resulted in this output:

==955137==ERROR: AddressSanitizer: heap-use-after-free on address 0x60600002b6a8 at pc 0x56065667c667 bp 0x7ffc00fa4570 sp 0x7ffc00fa4568
READ of size 4 at 0x60600002b6a8 thread T0
    #0 0x56065667c666 in ps_srlzr_encode_php /tmp/php-asan/ext/session/session.c:1086:2
    #1 0x56065668a72d in php_session_encode /tmp/php-asan/ext/session/session.c:281:10
    #2 0x56065668bdfe in zif_session_encode /tmp/php-asan/ext/session/session.c:2586:8
    #3 0x56065714ebc2 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER /tmp/php-asan/Zend/zend_vm_execute.h:1299:2
    #4 0x560656fb183d in execute_ex /tmp/php-asan/Zend/zend_vm_execute.h:58565:7
    #5 0x560656fb2067 in zend_execute /tmp/php-asan/Zend/zend_vm_execute.h:64217:2
    #6 0x5606573e6860 in zend_execute_script /tmp/php-asan/Zend/zend.c:1932:3
    #7 0x560656c04d2b in php_execute_script_ex /tmp/php-asan/main/main.c:2574:13
    #8 0x560656c05228 in php_execute_script /tmp/php-asan/main/main.c:2614:9
    #9 0x5606573ee309 in do_cli /tmp/php-asan/sapi/cli/php_cli.c:935:5
    #10 0x5606573eb32c in main /tmp/php-asan/sapi/cli/php_cli.c:1310:18
    #11 0x7fe68da29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #12 0x7fe68da29e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #13 0x560655e02de4 in _start (/workspaces/TriFuzz/targets/php-asan/bin/php+0x402de4)

0x60600002b6a8 is located 8 bytes inside of 56-byte region [0x60600002b6a0,0x60600002b6d8)
freed by thread T0 here:
    #0 0x560655e87702 in free /opt/llvm-15-build/llvm-15.x/final/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:52:3
    #1 0x560656e418a3 in __zend_free /tmp/php-asan/Zend/zend_alloc.c:3308:2
    #2 0x560656e45774 in _efree /tmp/php-asan/Zend/zend_alloc.c:2747:3
    #3 0x56065726154d in zend_array_destroy /tmp/php-asan/Zend/zend_hash.c:1866:2
    #4 0x5606573c75b6 in rc_dtor_func /tmp/php-asan/Zend/zend_variables.c:57:2
    #5 0x5606571f6a84 in zend_assign_to_variable /tmp/php-asan/Zend/zend_execute.h:179:4
    #6 0x560656feb12d in ZEND_ASSIGN_SPEC_VAR_CONST_RETVAL_UNUSED_HANDLER /tmp/php-asan/Zend/zend_vm_execute.h:25239:11
    #7 0x560656fb183d in execute_ex /tmp/php-asan/Zend/zend_vm_execute.h:58565:7
    #8 0x560656f8d1ac in zend_call_function /tmp/php-asan/Zend/zend_execute_API.c:996:3
    #9 0x560656f8f3b2 in zend_call_known_function /tmp/php-asan/Zend/zend_execute_API.c:1090:23
    #10 0x560656b7219a in zend_call_known_instance_method /tmp/php-asan/Zend/zend_API.h:860:2
    #11 0x560656b7210b in zend_call_known_instance_method_with_0_params /tmp/php-asan/Zend/zend_API.h:866:2
    #12 0x560656b70778 in php_var_serialize_call_magic_serialize /tmp/php-asan/ext/standard/var.c:850:2
    #13 0x560656b6833d in php_var_serialize_intern /tmp/php-asan/ext/standard/var.c:1147:10
    #14 0x560656b67501 in php_var_serialize /tmp/php-asan/ext/standard/var.c:1321:2
    #15 0x56065667c910 in ps_srlzr_encode_php /tmp/php-asan/ext/session/session.c:1086:2
    #16 0x56065668a72d in php_session_encode /tmp/php-asan/ext/session/session.c:281:10
    #17 0x56065668bdfe in zif_session_encode /tmp/php-asan/ext/session/session.c:2586:8
    #18 0x56065714ebc2 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER /tmp/php-asan/Zend/zend_vm_execute.h:1299:2
    #19 0x560656fb183d in execute_ex /tmp/php-asan/Zend/zend_vm_execute.h:58565:7
    #20 0x560656fb2067 in zend_execute /tmp/php-asan/Zend/zend_vm_execute.h:64217:2
    #21 0x5606573e6860 in zend_execute_script /tmp/php-asan/Zend/zend.c:1932:3
    #22 0x560656c04d2b in php_execute_script_ex /tmp/php-asan/main/main.c:2574:13
    #23 0x560656c05228 in php_execute_script /tmp/php-asan/main/main.c:2614:9
    #24 0x5606573ee309 in do_cli /tmp/php-asan/sapi/cli/php_cli.c:935:5
    #25 0x5606573eb32c in main /tmp/php-asan/sapi/cli/php_cli.c:1310:18
    #26 0x7fe68da29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 here:
    #0 0x560655e879ae in malloc /opt/llvm-15-build/llvm-15.x/final/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:69:3
    #1 0x560656e45ce3 in __zend_malloc /tmp/php-asan/Zend/zend_alloc.c:3280:14
    #2 0x560656e45670 in _emalloc /tmp/php-asan/Zend/zend_alloc.c:2737:10
    #3 0x56065724a027 in _zend_new_array /tmp/php-asan/Zend/zend_hash.c:291:18
    #4 0x560656691fe3 in php_session_track_init /tmp/php-asan/ext/session/session.c:269:2
    #5 0x56065667f824 in php_session_initialize /tmp/php-asan/ext/session/session.c:489:2
    #6 0x56065667efd4 in php_session_start /tmp/php-asan/ext/session/session.c:1704:6
    #7 0x56065668d0de in zif_session_start /tmp/php-asan/ext/session/session.c:2687:2
    #8 0x56065714ebc2 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER /tmp/php-asan/Zend/zend_vm_execute.h:1299:2
    #9 0x560656fb183d in execute_ex /tmp/php-asan/Zend/zend_vm_execute.h:58565:7
    #10 0x560656fb2067 in zend_execute /tmp/php-asan/Zend/zend_vm_execute.h:64217:2
    #11 0x5606573e6860 in zend_execute_script /tmp/php-asan/Zend/zend.c:1932:3
    #12 0x560656c04d2b in php_execute_script_ex /tmp/php-asan/main/main.c:2574:13
    #13 0x560656c05228 in php_execute_script /tmp/php-asan/main/main.c:2614:9
    #14 0x5606573ee309 in do_cli /tmp/php-asan/sapi/cli/php_cli.c:935:5
    #15 0x5606573eb32c in main /tmp/php-asan/sapi/cli/php_cli.c:1310:18
    #16 0x7fe68da29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

SUMMARY: AddressSanitizer: heap-use-after-free /tmp/php-asan/ext/session/session.c:1086:2 in ps_srlzr_encode_php
Shadow bytes around the buggy address:
  0x0c0c7fffd680: 00 00 00 00 00 00 00 fa fa fa fa fa fd fd fd fd
  0x0c0c7fffd690: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fffd6a0: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
  0x0c0c7fffd6b0: fd fd fd fd fd fd fd fd fa fa fa fa fd fd fd fd
  0x0c0c7fffd6c0: fd fd fd fd fa fa fa fa fd fd fd fd fd fd fd fd
=>0x0c0c7fffd6d0: fa fa fa fa fd[fd]fd fd fd fd fd fa fa fa fa fa
  0x0c0c7fffd6e0: fd fd fd fd fd fd fd fa fa fa fa fa 00 00 00 00
  0x0c0c7fffd6f0: 00 00 00 00 fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fffd700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fffd710: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fffd720: 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
==955137==ABORTING

PHP Version

PHP 8.5.0-dev

Operating System

No response

cmb69 commented 6 days ago

That's basically the same issue as #16591 (although a completely different code path).

nielsdos commented 6 days ago

We could do something like this, but sadly the affected macro is in a public header :-(

diff --git a/ext/session/php_session.h b/ext/session/php_session.h
index 31b96340e82..862b79867b1 100644
--- a/ext/session/php_session.h
+++ b/ext/session/php_session.h
@@ -289,7 +289,10 @@ PHPAPI zend_result php_session_reset_id(void);
    zval *struc;

 #define PS_ENCODE_LOOP(code) do {                                  \
-   HashTable *_ht = Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars)));   \
+   zval _zv;                                                       \
+   /* protect against user interference */                         \
+   ZVAL_COPY(&_zv, Z_REFVAL(PS(http_session_vars)));               \
+   HashTable *_ht = Z_ARRVAL(_zv);                                 \
    ZEND_HASH_FOREACH_KEY(_ht, num_key, key) {                      \
        if (key == NULL) {                                          \
            php_error_docref(NULL, E_WARNING,                       \
@@ -300,6 +303,7 @@ PHPAPI zend_result php_session_reset_id(void);
            code;                                                   \
        }                                                           \
    } ZEND_HASH_FOREACH_END();                                      \
+   zval_ptr_dtor(&_zv);                                            \
 } while(0)

 PHPAPI ZEND_EXTERN_MODULE_GLOBALS(ps)
diff --git a/ext/session/session.c b/ext/session/session.c
index dd780f4afd4..0f4042b735e 100644
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -966,6 +966,7 @@ PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */
    smart_str buf = {0};
    php_serialize_data_t var_hash;
    PS_ENCODE_VARS;
+   bool fail = false;

    PHP_VAR_SERIALIZE_INIT(var_hash);

@@ -974,12 +975,17 @@ PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */
        if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key))) {
            PHP_VAR_SERIALIZE_DESTROY(var_hash);
            smart_str_free(&buf);
-           return NULL;
+           fail = true;
+           break;
        }
        smart_str_appendc(&buf, PS_DELIMITER);
        php_var_serialize(&buf, struc, &var_hash);
    );

+   if (fail) {
+       return NULL;
+   }
+
    smart_str_0(&buf);

    PHP_VAR_SERIALIZE_DESTROY(var_hash);
cmb69 commented 2 days ago

We could do something like this, but sadly the affected macro is in a public header :-(

Might still be okay for PHP-8.4, though.