Skip to content
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

[Bug]: Segfault in dd_observe_fiber_switch #2923

Open
cleverhoods opened this issue Nov 5, 2024 · 2 comments
Open

[Bug]: Segfault in dd_observe_fiber_switch #2923

cleverhoods opened this issue Nov 5, 2024 · 2 comments
Labels
🐛 bug Something isn't working

Comments

@cleverhoods
Copy link

Bug report

dd_observe_fiber_switch function in /tmp/pear/temp/datadog_trace/ext/handlers_fiber.c:112 does not check for ZEND_FIBER_STATUS_DEAD thus it can lead to a Segmentation fault.

PHP version

8.1

Tracer or profiler version

0.99.1

Installed extensions

No response

Output of phpinfo()

No response

Upgrading from

No response

@cleverhoods cleverhoods added the 🐛 bug Something isn't working label Nov 5, 2024
@bwoebi
Copy link
Collaborator

bwoebi commented Nov 5, 2024

Hey @cleverhoods,

Are you able to reproduce that somehow? I've tried, but fail to. I'm also not sure what special handling would need to happen here, if the fiber is dead? I also don't quite know from your report where exactly the invalid pointer dereference for the segfault then will happen?

Thanks!

@cleverhoods
Copy link
Author

Hi @bwoebi,

The issue was originating from this method:

protected function replacePlaceholders(array &$elements) {
    if (!isset($elements['#attached']['placeholders']) || empty($elements['#attached']['placeholders'])) {
      return FALSE;
    }

    // The 'status messages' placeholder needs to be special cased, because it
    // depends on global state that can be modified when other placeholders are
    // being rendered: any code can add messages to render.
    // This violates the principle that each lazy builder must be able to render
    // itself in isolation, and therefore in any order. However, we cannot
    // change the way \Drupal\Core\Messenger\Messenger works in the Drupal 8
    // cycle. So we have to accommodate its special needs.
    // Allowing placeholders to be rendered in a particular order (in this case:
    // last) would violate this isolation principle. Thus a monopoly is granted
    // to this one special case, with this hard-coded solution.
    // @see \Drupal\Core\Render\Element\StatusMessages
    // @see https://www.drupal.org/node/2712935#comment-11368923

    // First render all placeholders except 'status messages' placeholders.
    $message_placeholders = [];
    $fibers = [];
    foreach ($elements['#attached']['placeholders'] as $placeholder => $placeholder_element) {
      if (isset($placeholder_element['#lazy_builder']) && $placeholder_element['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') {
        $message_placeholders[] = $placeholder;
      }
      else {
        // Get the render array for the given placeholder
        $fibers[$placeholder] = new \Fiber(function () use ($placeholder_element) {
          return [$this->doRenderPlaceholder($placeholder_element), $placeholder_element];
        });
      }
    }
    $iterations = 0;
    while (count($fibers) > 0) {
      foreach ($fibers as $placeholder => $fiber) {
        if (!$fiber->isStarted()) {
          $fiber->start();
        }
        elseif ($fiber->isSuspended()) {
          $fiber->resume();
        }
        // If the Fiber hasn't terminated by this point, move onto the next
        // placeholder, we'll resume this fiber again when we get back here.
        if (!$fiber->isTerminated()) {
          // If we've gone through the placeholders once already, and they're
          // still not finished, then start to allow code higher up the stack to
          // get on with something else.
          if ($iterations) {
            $fiber = \Fiber::getCurrent();
            if ($fiber !== NULL) {
              $fiber->suspend();
            }
          }
          continue;
        }
        [$markup, $placeholder_element] = $fiber->getReturn();

        $elements = $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element);
        unset($fibers[$placeholder]);
      }
      $iterations++;
    }

    // Then render 'status messages' placeholders.
    foreach ($message_placeholders as $message_placeholder) {
      $elements = $this->renderPlaceholder($message_placeholder, $elements);
    }

    return TRUE;
  }

The issue arises because the $placeholder variable from the initial foreach loop is reused in the subsequent foreach loop in the following while loop. This leads to corruption of the Fibers, as they may access inconsistent or overwritten state during execution.

I've created a ticket to have this fixed in Drupal (https://www.drupal.org/node/3490455) however the Datadog part is still valid IMO and checks for ZEND_FIBER_STATUS_DEAD would be great to be introduced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants