Skip to content

Fix GH-11078: PHP Fatal error triggers pointer being freed was not allocated and malloc: double free for ptr errors #14524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

nielsdos
Copy link
Member

@nielsdos nielsdos commented Jun 9, 2024

Although the issue was demonstrated using Curl, the issue is purely in the streams layer of PHP.

Full analysis is written in GH-11078 [1], but here is the brief version:

  1. We're creating a FILE handle from a stream using the casting mechanism.
    This will create a cookie-based FILE handle using funopen.
  2. We're reading stream data using fread from the userspace stream. This will
    temporarily set a buffer into a field _bf.base [2]. This buffer is now equal
    to the upload buffer that Curl allocated and note that that buffer is owned
    by Curl.
  3. The fatal error occurs and we bail out from the fread function, notice how
    the reset code is never executed and so the buffer will still point to
    Curl's upload buffer instead of FILE's own buffer [3].
  4. The resources are destroyed, this includes our opened stream and because the
    FILE handle is cached, it gets destroyed as well.
    In fact, the stream code calls through fclose on purpose in this case.
  5. The fclose code frees the _bs.base buffer [4].
    However, this is not the buffer that FILE owns but the one that Curl owns
    because it isn't reset properly due to the bailout!
  6. The objects are getting destroyed, and so the curl free logic is invoked.
    When Curl tries to gracefully clean up, it tries to free the buffer.
    But that buffer is actually already freed mistakingly by the C library!

This also explains why we can't reproduce it on Linux: this bizarre buffer swapping only happens on macOS and BSD, not on Linux.

To solve this, we switch to an unbuffered mode for cookie-based FILEs. This avoids any stateful problems related to buffers especially when the bailout mechanism triggers. As streams have their own buffering mechanism, I don't expect this to impact performance.

[1] #11078 (comment)
[2] https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/stdio/FreeBSD/fread.c#L102-L103
[3] https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/stdio/FreeBSD/fread.c#L117
[4] https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/stdio/FreeBSD/fclose.c#L66-L67

@nielsdos nielsdos force-pushed the fix-GH-11078 branch 3 times, most recently from d2a425a to 632eb8d Compare June 9, 2024 15:12
@nielsdos nielsdos marked this pull request as ready for review June 9, 2024 15:12
@nielsdos nielsdos requested review from kocsismate and bukka as code owners June 9, 2024 15:12
@nielsdos nielsdos requested a review from iluuu1994 June 9, 2024 15:13
… allocated and malloc: double free for ptr errors

Although the issue was demonstrated using Curl, the issue is purely in
the streams layer of PHP.

Full analysis is written in phpGH-11078 [1], but here is the brief version:
Here's what actually happens:
1) We're creating a FILE handle from a stream using the casting mechanism.
   This will create a cookie-based FILE handle using funopen.
2) We're reading stream data using fread from the userspace stream. This will
   temporarily set a buffer into a field _bf.base [2]. This buffer is now equal
   to the upload buffer that Curl allocated and note that that buffer is owned
   by Curl.
3) The fatal error occurs and we bail out from the fread function, notice how
   the reset code is never executed and so the buffer will still point to
   Curl's upload buffer instead of FILE's own buffer [3].
4) The resources are destroyed, this includes our opened stream and because the
   FILE handle is cached, it gets destroyed as well.
   In fact, the stream code calls through fclose on purpose in this case.
5) The fclose code frees the _bs.base buffer [4].
   However, this is not the buffer that FILE owns but the one that Curl owns
   because it isn't reset properly due to the bailout!
6) The objects are getting destroyed, and so the curl free logic is invoked.
   When Curl tries to gracefully clean up, it tries to free the buffer.
   But that buffer is actually already freed mistakingly by the C library!

This also explains why we can't reproduce it on Linux: this bizarre buffer
swapping only happens on macOS and BSD, not on Linux.

To solve this, we switch to an unbuffered mode for cookie-based FILEs.
This avoids any stateful problems related to buffers especially when the
bailout mechanism triggers. As streams have their own buffering
mechanism, I don't expect this to impact performance.

[1] php#11078 (comment)
[2] https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/stdio/FreeBSD/fread.c#L102-L103
[3] https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/stdio/FreeBSD/fread.c#L117
[4] https://github.com/apple-open-source-mirror/Libc/blob/5e566be7a7047360adfb35ffc44c6a019a854bea/stdio/FreeBSD/fclose.c#L66-L67
Copy link
Member

@bukka bukka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice finding.

@nielsdos nielsdos closed this in bc558bf Jun 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PHP Fatal error triggers pointer being freed was not allocated and malloc: double free for ptr errors
3 participants