Skip to content

Fix stream_wrapper_unregister() resource leak #8587

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

iluuu1994
Copy link
Member

Closes GH-8548

The changes are bigger than I would've liked to. Let me know if you'd like me to change anything, I'm open to suggestions. The approach of this PR:

  • Make php_user_stream_wrapper.wrapper the first member so that we can cast between php_user_stream_wrapper and php_stream_wrapper
  • Add a discriminator to php_stream_wrapper (INTERNAL or USER) so that we know which is which
  • Store a pointer to the reference in php_user_stream_wrapper so that we can deallocate it when the user stream wrapper is removed

To actually make this testable, I had to introduce a AUTO_CLOSE_RESOURCE_LIST env variable that circumvents automatically closing all open resources at the end of the request, as that hides resource leaks. This way the leak is reported both with zmm and asan.

This PR is not ABI compatible, so it's targeting master.

@iluuu1994 iluuu1994 requested review from Girgias, cmb69 and derickr May 20, 2022 08:07
@mvorisek
Copy link
Contributor

To actually make this testable, I had to introduce a AUTO_CLOSE_RESOURCE_LIST env variable that circumvents automatically closing all open resources at the end of the request, as that hides resource leaks.

in zend_test ext any internal state can be returned and tested, or you can use memory_get_usage

Copy link
Member

@cmb69 cmb69 left a comment

Choose a reason for hiding this comment

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

Thank you for the PR!

Indeed, we cannot do this for any of the stable branches, and given that we want to migrate to objects hopefully with PHP 9.0, I have doubts that this change is worth to be done; after all, there are many external extensions which have custom stream wrappers, and all these would need to be updated (possibly in non-trivial ways for BC reasons) for PHP 8.2, and then again for PHP 9.0.

I am not sure, though, whether the API break is necessary at all. Instead of introducing the php_stream_wrapper.type member, can't we just check whether .wops == &user_stream_wops? If that is given, we could cast (which requires the restructuring of the internal struct _php_user_stream_wrapper, but that "only" constitutes an ABI break, so extensions would not be affected at all).

@iluuu1994
Copy link
Member Author

Instead of introducing the php_stream_wrapper.type member, can't we just check whether .wops == &user_stream_wops?

@cmb69 Oh, indeed! I'll make the necessary changes. Do you think the AUTO_CLOSE_RESOURCE_LIST makes sense? I'd also be happy with memory_get_usage() too (although we probably need more than one iteration then, as the first one will allocate certain things like the resource hashmap which will increase memory).

@iluuu1994 iluuu1994 force-pushed the gh-8548 branch 2 times, most recently from a7f2604 to e0b2ce5 Compare May 20, 2022 11:28
@cmb69
Copy link
Member

cmb69 commented May 20, 2022

The AUTO_CLOSE_RESOURCE_LIST might make sense if there are more similar issues to check, but for now I'd go with a test checking memory_get_usage(); even if that requires multiple (un)registrations, the test could be treated as slow test (SKIP_SLOW_TESTS).

@iluuu1994
Copy link
Member Author

iluuu1994 commented May 20, 2022

@cmb69 Thanks. I will investigate if AUTO_CLOSE_RESOURCE_LIST is useful in a separate PR. Unfortunately, 5 iterations were needed before memory stopped increasing. Why that particular number, I'm not sure. The test is very fast, so I don't think we need SKIP_SLOW_TESTS here.

Copy link
Member

@cmb69 cmb69 left a comment

Choose a reason for hiding this comment

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

Unfortunately, 5 iterations were needed before memory stopped increasing. Why that particular number, I'm not sure.

It might even make sense to increase this number, just to be sure that the test works in all circumstances.

Anyhow, the PR looks good to me now. The only issue would be get_resources(), but that is already documented as debuging tool, with respective caveats.

@iluuu1994
Copy link
Member Author

The only issue would be get_resources(), but that is already documented as debuging tool, with respective caveats.

Can you clarify? Do you mean get_resources() no longer containing the unregistered stream wrappers?

@cmb69
Copy link
Member

cmb69 commented May 20, 2022

The general problem with get_resources() is that you can access internal resources, and manipulate them at will. In this case, someone could store a stream factory resource in a variable, and when stream_wrapper_unregister() is called, the refcount assertion would fail (and maybe more serious issues could happen; see #1318). But as I said, this is nothing we should be really concerned about.

@iluuu1994
Copy link
Member Author

iluuu1994 commented May 20, 2022

Ah, of course. We can also delay the freeing until any reference to it goes away, that should work fine. I added the assert because I didn't think there was a way the refcount could increase.

@cmb69
Copy link
Member

cmb69 commented May 20, 2022

I added the assert because I didn't think there was a way the refcount could increase.

In my opinion, there shouldn't be a way. :)

@iluuu1994
Copy link
Member Author

If you're ok with this I would still like to change it, as this could lead to memory corruption otherwise (even if get_resources() is essentially unsupported).

@cmb69
Copy link
Member

cmb69 commented May 20, 2022

Yeah, sure, feel free to improve. :)

// uwrap will be released by resource destructor
rc_dtor_func((zend_refcounted *)resource);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can't you use something more standard like zend_list_delete here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I didn't realize EG(regular_list) had a destructor attached to it that will free the resource. Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

phpitnernalsbook.com has a good chapter about resources.

@iluuu1994 iluuu1994 closed this in a5a89cc May 21, 2022
iluuu1994 added a commit to iluuu1994/php-src that referenced this pull request May 23, 2022
Streams hold a reference to the stream wrapper. User stream wrappers
must not be released until the streams themselves are closed.
iluuu1994 added a commit to iluuu1994/php-src that referenced this pull request May 23, 2022
Streams hold a reference to the stream wrapper. User stream wrappers
must not be released until the streams themselves are closed.
iluuu1994 added a commit to iluuu1994/php-src that referenced this pull request May 23, 2022
Streams hold a reference to the stream wrapper. User stream wrappers
must not be released until the streams themselves are closed.
iluuu1994 added a commit to iluuu1994/php-src that referenced this pull request Jun 9, 2022
Streams hold a reference to the stream wrapper. User stream wrappers
must not be released until the streams themselves are closed.
iluuu1994 added a commit that referenced this pull request Jun 9, 2022
* Fix regression from GH-8587

Streams hold a reference to the stream wrapper. User stream wrappers
must not be released until the streams themselves are closed.

* Add test for directories
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

memory leak when using fopen with a stream wrapper
4 participants