Skip to content

Commit 55eb9e5

Browse files
authored
fix(exec): don't treat shell_exec() null return as an error (#3723)
PHP's shell_exec() returns null both when a command produces no output AND when execution fails — the two cases are indistinguishable. Flagging null as an error causes false-positive error spans for any intentionally silent command (e.g. Symfony's `stty 2>/dev/null` called internally by Terminal::hasSttyAvailable()), which is especially harmful when DD_APM_FEATURES=error_rare_sample_tracer_drop is set, since those spans cannot be sampled out. The fix from cff2034 (v1.8.0) added hooks on two specific Symfony methods to suppress their spans, but the root defect in postHookShell() remained. Any other shell_exec() call returning null — from a Symfony bundle, third-party package, or application code — still produced an error span. Per PHP docs: "It is not possible to detect execution failures using this function." Remove the null check entirely; the false return check above it already handles the case where PHP itself cannot invoke the shell. Adds a regression test asserting that shell_exec('true') — which returns null due to no output — produces a clean span with no error. Fixes: APMS-18967
1 parent 3f9f490 commit 55eb9e5

2 files changed

Lines changed: 26 additions & 2 deletions

File tree

src/DDTrace/Integrations/Exec/ExecIntegration.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,6 @@ private static function postHookShell($variant)
255255
$span->exception = $hook->exception;
256256
} elseif ($hook->returned === false) {
257257
$span->meta[Tag::ERROR_MSG] = "$variant() returned false";
258-
} elseif ($hook->returned === null && $variant === 'shell_exec') {
259-
$span->meta[Tag::ERROR_MSG] = "shell_exec() returned null";
260258
} elseif (
261259
!empty($retCodeArg) &&
262260
isset($hook->args[$retCodeArg]) &&

tests/Integrations/Exec/ExecIntegrationTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,32 @@ public function testShellOtherBadArguments($sf)
490490
}
491491
}
492492

493+
/**
494+
* shell_exec() returning null must NOT produce an error span.
495+
*
496+
* PHP docs state: "It is not possible to detect execution failures using
497+
* this function." A null return means either no output was produced or the
498+
* command failed — the two cases are indistinguishable. Flagging null as an
499+
* error causes false-positive error spans for any intentionally silent
500+
* command (e.g. Symfony's `stty 2>/dev/null`).
501+
*/
502+
public function testShellExecNullReturnIsNotAnError()
503+
{
504+
$traces = $this->isolateTracer(function () {
505+
// 'true' exits 0 with no output → shell_exec returns null.
506+
$res = shell_exec('true');
507+
$this->assertNull($res);
508+
});
509+
510+
$this->assertSpans($traces, [
511+
SpanAssertion::build('command_execution', $traces[0][0]['service'], 'system', 'sh')
512+
->withExactTags([
513+
'cmd.shell' => 'true',
514+
'component' => 'subprocess',
515+
])
516+
]);
517+
}
518+
493519
/**
494520
* Test that the shell command is redacted.
495521
* @dataProvider allShellFunctionsProvider

0 commit comments

Comments
 (0)