php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81068 Double free in realpath_cache_clean()
Submitted: 2021-05-21 16:53 UTC Modified: -
From: dimitry at andric dot com Assigned:
Status: Closed Package: Apache2 related
PHP Version: master-Git-2021-05-21 (Git) OS: Windows
Private report: No CVE-ID: None
 [2021-05-21 16:53 UTC] dimitry at andric dot com
Description:
------------
Using php-src master 77ce2537f7 (ZTS enabled), with Apache 2.4.46 x64 from Apache Lounge, on Windows 10 [Version 10.0.19042.985]. I noticed an access violation upon stopping the Apache service. As this is rather hard to debug, I built php with address sanitizer, and used "httpd.exe -X" to run it within the Visual Studio debugger.

If you let it start up, then do at least one HTTP request (for example, just http://localhost/), and then control-C the httpd.exe process, it will attempt to shutdown, and it will effectively call the realpath_cache_clean() function more than once. This is because TSRM calls cwd_globals_ctor() more than once, so the destructor, cwd_globals_dtor() is also called more than once.

The second time through realpath_cache_clean(), ASan gives:

=================================================================
==19272==ERROR: AddressSanitizer: heap-use-after-free on address 0x12d32c603e08 at pc 0x7ffacc5493a5 bp 0x0098272cf5d0 sp 0x0098272cf5d8
READ of size 8 at 0x12d32c603e08 thread T0
    #0 0x7ffacc5493a4 in realpath_cache_clean C:\Users\Dim\Source\php-src\Zend\zend_virtual_cwd.c:346
    #1 0x7ffacc54aa1d in cwd_globals_dtor C:\Users\Dim\Source\php-src\Zend\zend_virtual_cwd.c:155
    #2 0x7ffacc745b7f in tsrm_shutdown C:\Users\Dim\Source\php-src\TSRM\TSRM.c:192
    #3 0x7ffb0350ce21 in php_apache_server_shutdown C:\Users\Dim\Source\php-src\sapi\apache2handler\sapi_apache2.c:419
    #4 0x61bde68d in apr_pool_destroy+0x6d (C:\Apache24\bin\libapr-1.dll+0x6eece68d)
    #5 0x61bde66e in apr_pool_destroy+0x4e (C:\Apache24\bin\libapr-1.dll+0x6eece66e)
    #6 0x7ff7c211127f in OPENSSL_Applink+0x27f (C:\Apache24\bin\httpd.exe+0x14000127f)
    #7 0x7ff7c2111eeb in OPENSSL_Applink+0xeeb (C:\Apache24\bin\httpd.exe+0x140001eeb)
    #8 0x7ff7c2112fa3 in OPENSSL_Applink+0x1fa3 (C:\Apache24\bin\httpd.exe+0x140002fa3)
    #9 0x7ffb1cd57033 in BaseThreadInitThunk+0x13 (C:\Windows\System32\KERNEL32.DLL+0x180017033)
    #10 0x7ffb1eaa2650 in RtlUserThreadStart+0x20 (C:\Windows\SYSTEM32\ntdll.dll+0x180052650)

0x12d32c603e08 is located 1288 bytes inside of 12320-byte region [0x12d32c603900,0x12d32c606920)
freed by thread T0 here:
    #0 0x7fface90c2c2 in _asan_wrap_GlobalSize+0x49607 (C:\Users\Dim\Source\php-src\x64\Debug_TS\clang_rt.asan_dbg_dynamic-x86_64.dll+0x18004c2c2)
    #1 0x7ffacc745c42 in tsrm_shutdown C:\Users\Dim\Source\php-src\TSRM\TSRM.c:200
    #2 0x7ffb0350ce21 in php_apache_server_shutdown C:\Users\Dim\Source\php-src\sapi\apache2handler\sapi_apache2.c:419
    #3 0x61bde68d in apr_pool_destroy+0x6d (C:\Apache24\bin\libapr-1.dll+0x6eece68d)
    #4 0x61bde66e in apr_pool_destroy+0x4e (C:\Apache24\bin\libapr-1.dll+0x6eece66e)
    #5 0x7ff7c211127f in OPENSSL_Applink+0x27f (C:\Apache24\bin\httpd.exe+0x14000127f)
    #6 0x7ff7c2111eeb in OPENSSL_Applink+0xeeb (C:\Apache24\bin\httpd.exe+0x140001eeb)
    #7 0x7ff7c2112fa3 in OPENSSL_Applink+0x1fa3 (C:\Apache24\bin\httpd.exe+0x140002fa3)
    #8 0x7ffb1cd57033 in BaseThreadInitThunk+0x13 (C:\Windows\System32\KERNEL32.DLL+0x180017033)
    #9 0x7ffb1eaa2650 in RtlUserThreadStart+0x20 (C:\Windows\SYSTEM32\ntdll.dll+0x180052650)

previously allocated by thread T0 here:
    #0 0x7fface90c432 in _asan_wrap_GlobalSize+0x49777 (C:\Users\Dim\Source\php-src\x64\Debug_TS\clang_rt.asan_dbg_dynamic-x86_64.dll+0x18004c432)
    #1 0x7ffacc749005 in allocate_new_resource C:\Users\Dim\Source\php-src\TSRM\TSRM.c:371
    #2 0x7ffacc74717c in ts_resource_ex C:\Users\Dim\Source\php-src\TSRM\TSRM.c:445
    #3 0x7ffacc67a97a in php_tsrm_startup C:\Users\Dim\Source\php-src\main\main.c:2680
    #4 0x7ffb0350d3ad in php_apache_server_startup C:\Users\Dim\Source\php-src\sapi\apache2handler\sapi_apache2.c:481
    #5 0x7ffb032bf0fd in ap_run_post_config+0x4d (C:\Apache24\bin\libhttpd.dll+0x18000f0fd)
    #6 0x7ff7c2111e50 in OPENSSL_Applink+0xe50 (C:\Apache24\bin\httpd.exe+0x140001e50)
    #7 0x7ff7c2112fa3 in OPENSSL_Applink+0x1fa3 (C:\Apache24\bin\httpd.exe+0x140002fa3)
    #8 0x7ffb1cd57033 in BaseThreadInitThunk+0x13 (C:\Windows\System32\KERNEL32.DLL+0x180017033)
    #9 0x7ffb1eaa2650 in RtlUserThreadStart+0x20 (C:\Windows\SYSTEM32\ntdll.dll+0x180052650)

SUMMARY: AddressSanitizer: heap-use-after-free C:\Users\Dim\Source\php-src\Zend\zend_virtual_cwd.c:346 in realpath_cache_clean
Shadow bytes around the buggy address:
  0x04df91ec0770: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec0780: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec0790: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec07a0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec07b0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x04df91ec07c0: fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec07d0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec07e0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec07f0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec0800: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x04df91ec0810: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
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
  Shadow gap:              cc
Address Sanitizer Error: Use of deallocated memory

It appears that each time cwd_globals_ctor() is called, a fresh realpath_cache is created, using virtual_cwd_globals *cwd_g as storage, so it is logical to use the same pointer in cwd_globals_dtor() to cleanup that storage.

What fixed it for me was to copy the code in realpath_cache_clean() into cwd_globals_dtor(), but use cwd_g->xxx instead of CWDG(xxx), e.g.:

static void cwd_globals_dtor(virtual_cwd_globals *cwd_g) /* {{{ */
{
	uint32_t i;

	for (i = 0; i < sizeof(cwd_g->realpath_cache)/sizeof(cwd_g->realpath_cache[0]); i++) {
		realpath_cache_bucket *p = cwd_g->realpath_cache[i];
		while (p != NULL) {
			realpath_cache_bucket *r = p;
			p = p->next;
			free(r);
		}
		cwd_g->realpath_cache[i] = NULL;
	}
	cwd_g->realpath_cache_size = 0;
}
/* }}} */

However, this duplicates code so it is not really nice. Unfortunately you cannot use the CWDG() macro to get to the actual virtual_cwd_globals pointer, since zend.h does not have a ZEND_TSRMG_FAST_BULK() macro.

So I think the second best is to have a helper function that takes the maximum number of entries, a pointer to the start of the realpath_cache array, and a pointer to the realpath_cache_size member.

This helper function can then be called from both cwd_globals_dtor() and the externally available realpath_cache_clean(), e.g.:

static void realpath_cache_clean_helper(uint32_t max_entries, realpath_cache_bucket **cache, zend_long *cache_size)
{
	uint32_t i;

	for (i = 0; i < max_entries; i++) {
		realpath_cache_bucket *p = cache[i];
		while (p != NULL) {
			realpath_cache_bucket *r = p;
			p = p->next;
			free(r);
		}
		cache[i] = NULL;
	}
	*cache_size = 0;
}

static void cwd_globals_dtor(virtual_cwd_globals *cwd_g) /* {{{ */
{
	realpath_cache_clean_helper(sizeof(cwd_g->realpath_cache)/sizeof(cwd_g->realpath_cache[0]), cwd_g->realpath_cache, &cwd_g->realpath_cache_size);
}
/* }}} */

[...]

CWD_API void realpath_cache_clean(void) /* {{{ */
{
	realpath_cache_clean_helper(sizeof(CWDG(realpath_cache))/sizeof(CWDG(realpath_cache)[0]), CWDG(realpath_cache), &CWDG(realpath_cache_size));
}
/* }}} */

See also the attached patch. There is probably a nicer way, but it would involve adding something like ZEND_TSRMG_FAST_BULK() to zend.h.


Test script:
---------------
See description above. Effectively:
- Unzip https://windows.php.net/downloads/releases/php-8.0.6-Win32-vs16-x64.zip to C:\PHP80
- Unzip https://www.apachelounge.com/download/VS16/binaries/httpd-2.4.47-win64-VS16.zip to C:\Apache24
- Add lines to C:\Apache24\conf\httpd.conf:

LoadModule php_module "C:/PHP80/php8apache2_4.dll"
AddHandler application/x-httpd-php .php
PHPIniDir "C:/PHP80"

- Run C:\Apache24\bin\httpd.exe -X (in a separate terminal)
- Run curl http://localhost/
- Control-C the httpd.exe
- httpd.exe segfaults
- usually this is visible in Event Viewer, but if you have Visual Studio installed you can start the just-in-time debugger)



Patches

fix-realpath-cache-clean-segfault-1 (last revision 2021-05-21 16:55 UTC by dimitry at andric dot com)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-05-21 16:55 UTC] dimitry at andric dot com
The following patch has been added/updated:

Patch Name: fix-realpath-cache-clean-segfault-1
Revision:   1621616117
URL:        https://bugs.php.net/patch-display.php?bug=81068&patch=fix-realpath-cache-clean-segfault-1&revision=1621616117
 [2021-05-21 18:17 UTC] girgias@php.net
Please provide the patch as a Pull Request to the github repo.
 [2021-05-21 18:53 UTC] dimitry at andric dot com
Pull request added: https://github.com/php/php-src/pull/7028

Alternative approach: https://github.com/DimitryAndric/php-src/commit/9d38056f935bc8995657d4d07438467e9e00e957
 [2021-05-25 09:41 UTC] git@php.net
Automatic comment on behalf of DimitryAndric (author) and nikic (committer)
Revision: https://github.com/php/php-src/commit/99a208566adfa32bf3a759c8731755f5e2458fdd
Log: Fix bug #81068: Fix possible use-after-free in realpath_cache_clean()
 [2021-05-25 09:41 UTC] git@php.net
-Status: Open +Status: Closed
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 17:01:58 2024 UTC