Skip to content

Commit

Permalink
Fix user-after-free with debugger
Browse files Browse the repository at this point in the history
  • Loading branch information
iamluc committed Dec 11, 2024
1 parent b928df9 commit acd6c7f
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 1 deletion.
16 changes: 15 additions & 1 deletion ext/exception_serialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, con
zend_get_properties_for(zv, ZEND_PROP_PURPOSE_DEBUG)
#endif
: Z_OBJPROP_P(zv);

if (!ht) {
break;
}
ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(ht, key, val) {
if (!key) {
continue;
Expand Down Expand Up @@ -258,6 +262,16 @@ void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, con
ddtrace_snapshot_redacted_name(&value_capture, fieldname);
ZVAL_DEINDIRECT(val);
ddtrace_create_capture_value(val, &value_capture, config, remaining_nesting - 1);

// zend_get_properties_for can create ephemeral values that are released just after this loop
// We must persist them in the arena.
#if PHP_VERSION_ID >= 70400
if (ce->type == ZEND_INTERNAL_CLASS) {
char *persisted_val = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), value_capture.value.len);
memcpy(persisted_val, value_capture.value.ptr, value_capture.value.len);
value_capture.value.ptr = persisted_val;
}
#endif
ddog_capture_value_add_field(value, fieldname, value_capture);
} ZEND_HASH_FOREACH_END();
if (ce->type == ZEND_INTERNAL_CLASS) {
Expand Down Expand Up @@ -392,7 +406,7 @@ static void ddtrace_collect_exception_debug_data(zend_object *exception, zend_st
LOG(TRACE, "Skipping exception replay capture due to hash %.*s already recently hit", hash_len, exception_hash);
return;
}

char *exception_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), uuid_len);
ddog_snapshot_format_new_uuid((uint8_t(*)[uuid_len])exception_id);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
--TEST--
Non regression test for use-after-free segfault in exception replay
--SKIPIF--
<?php if (!extension_loaded('mysqli')) die('skip: mysqli extension required'); ?>
<?php include __DIR__ . '/../includes/skipif_no_dev_env.inc'; ?>
<?php if (getenv('PHP_PEAR_RUNTESTS') === '1') die("skip: pecl run-tests does not support %A in EXPECTF"); ?>
--ENV--
DD_AGENT_HOST=request-replayer
DD_TRACE_AGENT_PORT=80
DD_TRACE_GENERATE_ROOT_SPAN=0
DD_EXCEPTION_REPLAY_ENABLED=1
DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1
--INI--
datadog.trace.agent_test_session_token=live-debugger/non_regression_2989_mysqli
--FILE--
<?php

require __DIR__ . "/live_debugger.inc";

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

try {
$mysqli = new mysqli("@@_INVALID_@@", "foo", "bar");
} catch (Exception $e) {
$span = \DDTrace\start_span();
$span->exception = $e;
\DDTrace\close_span();
}

$dlr = new DebuggerLogReplayer;
$log = $dlr->waitForDebuggerDataAndReplay();
$log = json_decode($log["body"], true);

function recursive_ksort(&$arr) {
if (is_array($arr)) {
ksort($arr);
array_walk($arr, 'recursive_ksort');
}
}

recursive_ksort($log[0]["debugger"]["snapshot"]["captures"]);
var_dump($log[0]);

?>
--CLEAN--
<?php
require __DIR__ . "/live_debugger.inc";
reset_request_replayer();
?>
--EXPECTF--
Warning: mysqli::__construct(): php_network_getaddresses: getaddrinfo %s
%Aarray(5) {
["service"]=>
string(47) "exception-replay_non_regression_2989_mysqli.php"
["ddsource"]=>
string(11) "dd_debugger"
["timestamp"]=>
int(%d)
["debugger"]=>
array(1) {
["snapshot"]=>
array(8) {
["language"]=>
string(3) "php"
["id"]=>
string(36) "%s"
["timestamp"]=>
int(%d)
["exceptionCaptureId"]=>
string(36) "%s"
["exceptionHash"]=>
string(16) "%s"
["frameIndex"]=>
int(0)
["captures"]=>
array(1) {
["return"]=>
array(1) {
["arguments"]=>
array(4) {
["hos%s"]=>
array(2) {
["type"]=>
string(6) "string"
["value"]=>
string(13) "@@_INVALID_@@"
}
["password"]=>
array(%d) {
%A
}
["this"]=>
array(2) {
["fields"]=>
array(%d) {
%A
}
["type"]=>
string(6) "mysqli"
}
["use%s"]=>
array(2) {
["type"]=>
string(6) "string"
["value"]=>
string(3) "foo"
}
}
}
}
["probe"]=>
array(2) {
["id"]=>
string(0) ""
["location"]=>
array(2) {
["method"]=>
string(11) "__construct"
["type"]=>
string(6) "mysqli"
}
}
}
}
["message"]=>
NULL
}

0 comments on commit acd6c7f

Please sign in to comment.