Skip to content

Conversation

@kasparsd
Copy link

@kasparsd kasparsd commented Jun 28, 2025

Fixes #84, #90.

kasparsd added 8 commits June 28, 2025 15:28
- The 1 (third parameter) means "destroy the original zval after copying"

- This transfers ownership completely to the return value

- XHPROF_G(stats_count) becomes IS_UNDEF

- During cleanup, the check if (Z_TYPE(XHPROF_G(stats_count)) != IS_UNDEF) fails

- No double-free occurs
1. Ignored functions in PHP 8.0+ create entries with is_trace = 0

2. These entries skip hp_mode_common_beginfn() so the hash counter is never incremented

3. But hp_mode_hier_endfn_cb() was always decrementing the hash counter

4. This caused hash counter underflow, which could lead to memory corruption and segfaults
If a callback is found and executed, it calls zend_string_release(function_name) and returns trace_name. However, if no callback is found, it returns function_name directly without releasing it.
Fixes segmentation fault when the first function call in a request is an
ignored function. Previously, the code assumed *(entries) was always
non-NULL when creating entries for ignored functions, but this caused a NULL pointer dereference when profiling started with an ignored function.

The fix adds a NULL check and uses the actual function name when entries is NULL, ensuring proper initialization of the entry stack even when the first call is ignored.
Sets p->name_hprof to NULL after releasing the string in hp_fast_free_hprof_entry() to prevent potential double-free errors if the same entry is processed multiple times or reused from the free list with stale pointer data
callback = (hp_trace_callback*)zend_hash_find_ptr(XHPROF_G(trace_callbacks), function_name);
if (callback) {
trace_name = (*callback)(function_name, data);
} else {
Copy link
Author

Choose a reason for hiding this comment

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

Previously:

  • If a callback is found, release function_name and return trace_name
  • If no callback is found, return function_name directly without releasing it

#if PHP_VERSION_ID >= 80000
hp_entry_t *cur_entry = hp_fast_alloc_hprof_entry();
(cur_entry)->name_hprof = zend_string_copy((*(entries))->name_hprof);
/* Check if entries is not NULL before dereferencing */
Copy link
Author

Choose a reason for hiding this comment

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

Previously assumed *(entries) is always non-NULL when creating entries for ignored functions.

#if PHP_VERSION_ID >= 80000
if (top->is_trace == 0) {
XHPROF_G(func_hash_counters[top->hash_code])--;
/* For ignored functions, don't decrement hash counter since it was never incremented */
Copy link
Author

@kasparsd kasparsd Jun 28, 2025

Choose a reason for hiding this comment

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

Previously:

  • hp_mode_common_beginfn() was never called -> hash counter never incremented
  • hp_mode_hier_endfn_cb() was always decrementing the hash counter -> counter underflow

Copy link
Owner

Choose a reason for hiding this comment

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

Hello, this should only affect PHP 8.4?

@kasparsd kasparsd changed the title Prevent hp_clean_profiler_state from clearing already freed memory Prevent func_hash_counters underflow Jun 28, 2025
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.

SEGV with xhprof 2.3.9 enabled, php8.2.10

2 participants