Skip to content

Fix heap corruption from repeated bypass_printr() rename - #32

Merged
andrewdalpino merged 1 commit into
RubixML:mainfrom
foppelfb:fix/bypass-printr-double-rename
Sep 1, 2026
Merged

andrewdalpino merged 1 commit into
RubixML:mainfrom
foppelfb:fix/bypass-printr-double-rename

Conversation

@foppelfb

Copy link
Copy Markdown
Contributor

EG(function_table) is process-wide and persists across requests, so bypass_printr() was re-finding and re-renaming the same print_r entry on every request after the first. On request 2+, releasing the already-renamed (persistent, malloc-backed) function_name string via the per-request memory manager corrupted the heap. Guard the rename with a static flag so it only runs once per process.

This happened with long-running processes or the php builtin server. It resulted in an segfault with zend_mm_heap corrupted, even if no method of numpower was executed. The issue lay in the overloading of the print_r function, which could happen a second time. I assume this could happen in a FrankenPHP environment as well.

The Patch was partially done with the help of AI.

Submission Checklist:

Due to the inherent complexity of this library, we created this checklist to remind everyone of the essential steps to have an MR approved depending on the type of change that is made. You can delete this.

  • [ X ] Have you followed the guidelines in our Contributing document?
  • [ X ] Have you checked to ensure there aren't other open Pull Requests for the same update/change?
  • [ X ] Does your submission pass tests with ZEND_ALLOC enabled? export USE_ZEND_ALLOC=1 && make test
  • [ X ] Does your submission pass tests with ZEND_ALLOC disabled? export USE_ZEND_ALLOC=0 && make test

Change to methods and operations

  • [ X ] Have you verified that your change does not break backwards compatibility?
  • [ - ] Have you updated the operation(s) tests for your changes, as applicable?
  • [ - ] Optional: Have your changes also been tested on the GPU? If you don't have a GPU available, you'll need to wait for a community member to perform the approval with a GPU. This only applies to changes that can affect GPU functionality.
  • [ - ] Optional: Have your changes also been tested on the GPU with the NDARRAY_VCHECK option enabled and no VRAM memory leaks were displayed? export NDARRAY_VCHECK=1 && make test

Changes to Core Components:

This include changes to: buffer.c, gpu_alloc.c, ndarray.c, iterators.c and their associated header files.

  • [ X ] Have you added an explanation of what your changes do and why you'd like us to include them?
  • [ - ] Have you written new tests for your core changes, as applicable?
  • [ - ] Your change does not affect the GPU, or if it does, it was tested with the GPU and the NDARRAY_VCHECK environment variable set and with no VRAM memory leaks warnings?

EG(function_table) is process-wide and persists across requests, so
bypass_printr() was re-finding and re-renaming the same print_r entry
on every request after the first. On request 2+, releasing the
already-renamed (persistent, malloc-backed) function_name string via
the per-request memory manager corrupted the heap. Guard the rename
with a static flag so it only runs once per process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@andrewdalpino

Copy link
Copy Markdown
Member

Thank you @foppelfb!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a long-running-process crash caused by re-entering bypass_printr() on subsequent requests and re-releasing a persistent zend_string via the request allocator, corrupting the Zend heap. This PR hardens the print_r interception so the function-table mutation is intended to happen only once per process.

Changes:

  • Adds a per-process done guard in bypass_printr() to prevent repeated mutation across requests.
  • Expands the in-code rationale documenting why repeated renaming corrupts the heap (and notes the related once-per-process leak behavior).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread numpower.c
Comment on lines +1352 to +1356
static int done = 0;
if (done) {
return;
}
done = 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Will address in a followup

@andrewdalpino

Copy link
Copy Markdown
Member

Merging as this is a partial fix, will follow up with improved concurrency guard.

@andrewdalpino
andrewdalpino merged commit 16c73a4 into RubixML:main Sep 1, 2026
45 of 50 checks passed
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.

3 participants