Repository navigation
PHP Fatal error triggers pointer being freed was not allocated and malloc: double free for ptr errors #11078
Description
Description
The following code:
https://gist.github.com/lucasnetau/244ba31f321307e06177f48582273e86
Resulted in this output:
php(95590,0x7ff847fe5680) malloc: double free for ptr 0x7fd2c0198000
php(95560,0x7ff847fe5680) malloc: *** error for object 0x7fc2e801800a: pointer being freed was not allocated
SIGABRT
To trigger the issue a PHP Fatal error (out of memory) needs to occur in a write to a custom streamWrapper, the test case forces this quickly (pointer issue occurs on each run, double free occurs on most runs but not all). Notably when CURLOPT_HTTPHEADER is set the double free occurs in libcurl, when CURLOPT_HTTPHEADER is not set then the point being freed error occurs.
Double Free backtrace:
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007ff80467022a libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`:
-> 0x7ff80467022a <+10>: jae 0x7ff804670234 ; <+20>
0x7ff80467022c <+12>: movq %rax, %rdi
0x7ff80467022f <+15>: jmp 0x7ff804669ce2 ; cerror_nocancel
0x7ff804670234 <+20>: retq
Target 0: (php) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
* frame #0: 0x00007ff80467022a libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007ff8046a7f7b libsystem_pthread.dylib`pthread_kill + 263
frame #2: 0x00007ff8045f1ca5 libsystem_c.dylib`abort + 123
frame #3: 0x00007ff804507637 libsystem_malloc.dylib`malloc_vreport + 888
frame #4: 0x00007ff80451c897 libsystem_malloc.dylib`malloc_zone_error + 178
frame #5: 0x0000000101b920ce libcurl.4.dylib`Curl_close(datap=0x00007ff7bfefe9d0) at url.c:410:3 [opt]
frame #6: 0x0000000101b565e0 libcurl.4.dylib`curl_easy_cleanup(data=0x0000000000000000) at easy.c:804:3 [opt]
frame #7: 0x00000001000735dd php`curl_free_obj(object=0x0000000103bcd798) at interface.c:2839:2 [opt]
frame #8: 0x000000010047b31e php`zend_objects_store_del(object=0x0000000103bcd798) at zend_objects_API.c:200:4 [opt]
frame #9: 0x00000001003d26af php`zend_llist_destroy(l=0x0000000103bc70a8) at zend_llist.c:109:4 [opt]
frame #10: 0x00000001003d2703 php`zend_llist_clean(l=0x0000000103bc70a8) at zend_llist.c:123:2 [opt]
frame #11: 0x000000010007ab71 php`curl_multi_free_obj(object=<unavailable>) at multi.c:555:2 [opt]
frame #12: 0x000000010047b167 php`zend_objects_store_free_object_storage(objects=<unavailable>, fast_shutdown=<unavailable>) at zend_objects_API.c:122:6 [opt]
frame #13: 0x00000001003cf2f2 php`zend_shutdown_executor_values(fast_shutdown=<unavailable>) at zend_execute_API.c:399:2 [opt]
frame #14: 0x00000001003cf3d8 php`shutdown_executor at zend_execute_API.c:416:2 [opt]
frame #15: 0x00000001003df2bc php`zend_deactivate at zend.c:1258:2 [opt]
frame #16: 0x000000010037a1e5 php`php_request_shutdown(dummy=0x0000000000000000) at main.c:1863:2 [opt]
frame #17: 0x00000001004c41dc php`do_cli(argc=4, argv=0x0000600000c08c90) at php_cli.c:1135:3 [opt]
frame #18: 0x00000001004c1e81 php`main(argc=4, argv=0x0000600000c08c90) at php_cli.c:1333:18 [opt]
frame #19: 0x00007ff804375310 dyld`start + 2432
(lldb) f 5
warning: libcurl.4.dylib was compiled with optimization - stepping may behave oddly; variables may not be available.
frame #5: 0x0000000101b920ce libcurl.4.dylib`Curl_close(datap=0x00007ff7bfefe9d0) at url.c:410:3 [opt]
407 up_free(data);
408 Curl_safefree(data->state.buffer);
409 Curl_dyn_free(&data->state.headerb);
-> 410 Curl_safefree(data->state.ulbuf);
411 Curl_flush_cookies(data, TRUE);
412 Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]);
413 Curl_altsvc_cleanup(&data->asi);
(lldb) f 6
frame #6: 0x0000000101b565e0 libcurl.4.dylib`curl_easy_cleanup(data=0x0000000000000000) at easy.c:804:3 [opt]
801 return;
802
803 sigpipe_ignore(data, &pipe_st);
-> 804 Curl_close(&data);
805 sigpipe_restore(&pipe_st);
806 }
807
(lldb) f 7
warning: php was compiled with optimization - stepping may behave oddly; variables may not be available.
frame #7: 0x00000001000735dd php`curl_free_obj(object=0x0000000103bcd798) at interface.c:2839:2 [opt]
2836 curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION, curl_write_nothing);
2837 curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION, curl_write_nothing);
2838
-> 2839 curl_easy_cleanup(ch->cp);
2840
2841 /* cURL destructors should be invoked only by last curl handle */
2842 if (--(*ch->clone) == 0) {
Pointer being freed was not allocated:
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007ff80467022a libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`:
-> 0x7ff80467022a <+10>: jae 0x7ff804670234 ; <+20>
0x7ff80467022c <+12>: movq %rax, %rdi
0x7ff80467022f <+15>: jmp 0x7ff804669ce2 ; cerror_nocancel
0x7ff804670234 <+20>: retq
Target 0: (php) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
* frame #0: 0x00007ff80467022a libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007ff8046a7f7b libsystem_pthread.dylib`pthread_kill + 263
frame #2: 0x00007ff8045f1ca5 libsystem_c.dylib`abort + 123
frame #3: 0x00007ff804507637 libsystem_malloc.dylib`malloc_vreport + 888
frame #4: 0x00007ff80450a951 libsystem_malloc.dylib`malloc_report + 151
frame #5: 0x00007ff804595860 libsystem_c.dylib`fclose + 140
frame #6: 0x0000000100391f0b php`stream_resource_regular_dtor(rsrc=<unavailable>) at streams.c:1761:19 [opt]
frame #7: 0x00000001003f539a php`zend_resource_dtor(res=<unavailable>) at zend_list.c:73:3 [opt]
frame #8: 0x00000001003f56df php`zend_close_rsrc_list(ht=0x0000000100ed5c30) at zend_list.c:225:5 [opt]
frame #9: 0x00000001003cf5dc php`zend_shutdown_executor_values(fast_shutdown=<unavailable>) at zend_execute_API.c:277:3 [opt]
frame #10: 0x00000001003cfcd8 php`shutdown_executor at zend_execute_API.c:416:2 [opt]
frame #11: 0x00000001003dfbbc php`zend_deactivate at zend.c:1258:2 [opt]
frame #12: 0x000000010037aae5 php`php_request_shutdown(dummy=0x0000000000000000) at main.c:1863:2 [opt]
frame #13: 0x00000001004c4adc php`do_cli(argc=2, argv=0x00006000002080e0) at php_cli.c:1135:3 [opt]
frame #14: 0x00000001004c2781 php`main(argc=2, argv=0x00006000002080e0) at php_cli.c:1333:18 [opt]
frame #15: 0x00007ff804375310 dyld`start + 2432
(lldb) f 6
warning: php was compiled with optimization - stepping may behave oddly; variables may not be available.
frame #6: 0x0000000100391f0b php`stream_resource_regular_dtor(rsrc=<unavailable>) at streams.c:1761:19 [opt]
1758 {
1759 php_stream *stream = (php_stream*)rsrc->ptr;
1760 /* set the return value for pclose */
-> 1761 FG(pclose_ret) = php_stream_free(stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
1762 }
1763
1764 static void stream_resource_persistent_dtor(zend_resource *rsrc)
(lldb) f 7
frame #7: 0x00000001003f539a php`zend_resource_dtor(res=<unavailable>) at zend_list.c:73:3 [opt]
70 ZEND_ASSERT(ld && "Unknown list entry type");
71
72 if (ld->list_dtor_ex) {
-> 73 ld->list_dtor_ex(&r);
74 }
75 }
76
Double free was reported to curl via curl/curl#10964, however since a subtle change in settings (CURLOPT_HTTPHEADER) triggers a crash in PHP I am posting both here.
With ext-curl compiled with PHP_CURL_DEBUG=0 I only see the DTOR being called once (DTOR CALLED, ch = *)
The comments in interface.c before where the crash occurs for Curl are interesting
https://github.com/php/php-src/blob/master/ext/curl/interface.c#L2829-2838
PHP Version
PHP 8.2.5
Operating System
MacOS / Linux
Activity
cgzones commentedon Apr 17, 2023
Might be a duplicate of #10670
iluuu1994 commentedon Apr 17, 2023
This isn't trying to use custom allocators.
I'm unable to trigger a crash, ASAN is also not showing anything. What CURL version are you using? I'm trying this on Linux.
lucasnetau commentedon Apr 17, 2023
Latest Curl 8.0.1 is used. So far I've triggered the crash on both MacOS and Linux (Docker). The crash is timing dependant so the rate of filling the buffer may need to change depending on your network, it does appear that Curl needs to be past a certain point in the transfer stage for the issue to occur.
I'm away from my main computer for a week, I'll come back the. with a code dump from Linux.
Is there any other flags I should use for building or running php to help?
iluuu1994 commentedon Apr 18, 2023
That would be great, thank you!
You could try the
--enable-address-sanitizerand--enable-undefined-sanitizerflags.lucasnetau commentedon Apr 24, 2023
Additional address sanitiser output on MacOS
Double free output
pointer being freed was not allocated output
lucasnetau commentedon Apr 25, 2023
Alpine Linux produces a different error:
From Debian leaks are detected
lucasnetau commentedon Apr 26, 2023
Depending on where the out of memory error occurs it appears a different order of shutdown. With STREAM_DEBUG enabled.
No crash occurs when the Curl handle is closed first (DTOR CALLED before stream_free):
Crash occurs if the input file stream is closed before the curl connection is closed (DTOR CALLED after stream_free)
lucasnetau commentedon Apr 26, 2023
@iluuu1994 please let me know if you need anything additional. I haven't been able to crash reliably on Linux after compiling with debug enabled (Alpine Linux SIGSEGV, Debian I get leaks but no crash). MacOS version crashes reliably every 2-3 invocations of the script with SIGABRT.
41 remaining items
ndossche commentedon Jun 9, 2024
Thanks, I've managed to create an isolated test and opened a PR, let's hope CI agrees too.
lucasnetau commentedon Jun 10, 2024
In doing some benchmark testing with curl uploads via the stream I also discovered that this patch also fixed an issue I was seeing where cURL stopped uploading after stream_read() returned
falseon MacOS (Since returning an empty string causes cURL to think the upload is complete so you need to instead send a read error) [Note: I also use curl pause to stop and resume uploads as a flow control method if the buffer drains], now the upload works correctly even if the upload buffer gets drained.In the testing I was able to do, cURL would resume however it never called the stream_read() leading to no data being transferred.
Performance wise, I don't see an variance between old and patched version POSTing a large file to a localhost server, both sat at around 1350 MB/s throughput
Fix GH-11078: PHP Fatal error triggers pointer being freed was not al…
Merge branch 'PHP-8.2' into PHP-8.3
Merge branch 'PHP-8.3'
ndossche commentedon Jun 10, 2024
@lucasnetau Thanks for the results, fix will be in the next release.