Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/JIT/Builtin/Type/Object_.php
Original file line number Diff line number Diff line change
Expand Up @@ -3228,7 +3228,9 @@ private function registerExternalClass(string $lcname, string $displayName): voi
}
if ('sensitiveparametervalue' === $lcname) {
// Trace redaction marker — store wrapped arg for getValue() (#3351, #4621, #22487).
// Private like Zend zend_exceptions.stub.php — json_encode must not leak (#23042).
$this->defineProperty($id, 'value', Variable::TYPE_VALUE);
$this->definePropertyVisibility($id, 'value', \PHPCfg\Func::FLAG_PRIVATE);
$pub = \PHPCfg\Func::FLAG_PUBLIC;
foreach (['__construct', 'getvalue', '__debuginfo'] as $method) {
$this->defineMethodVisibility($id, $method, $pub);
Expand Down
5 changes: 5 additions & 0 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -2288,6 +2288,11 @@ public function collectVarExportPropertiesForBuiltin(ObjectEntry $object, Frame
if (DatePeriodSupport::CLASS_DATEPERIOD === $lc) {
return DatePeriodSupport::varExportPropertyMap($object);
}
// Zend zend_exceptions.c — SensitiveParameterValue get_properties_for(VAR_EXPORT) is empty (#23042).
if (VM\SensitiveParamSupport::CLASS_NAME === $object->class->name
|| strtolower(VM\SensitiveParamSupport::CLASS_NAME) === $lc) {
return [];
}

return $this->collectObjectPropertiesForBuiltin($object, $frame, true);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/VM/CastSupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ private static function objectToArray(ObjectEntry $obj, HashTable $ht, array $cl

return;
}
// Zend SensitiveParameterValue — (array) cast yields empty (get_properties handler, #23042).
if (strtolower($obj->class->name) === strtolower(SensitiveParamSupport::CLASS_NAME)) {
return;
}

$declared = [];
foreach ($obj->class->properties as $meta) {
Expand Down
17 changes: 16 additions & 1 deletion lib/VM/SensitiveParamSupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,23 @@ private static function buildClassEntry(): ClassEntry
{
$mixedProto = new Variable();
$pub = \PHPCfg\Func::FLAG_PUBLIC;
$priv = \PHPCfg\Func::FLAG_PRIVATE;
$entry = new ClassEntry(self::CLASS_NAME);
$entry->properties[] = new ClassProperty(self::PROP_VALUE, null, $mixedProto);
// Zend zend_exceptions.stub.php — final internal class; private readonly $value.
$entry->isFinal = true;
$entry->isInternal = true;
// Private + readonly: json_encode / get_object_vars see no public props (#23042).
// var_export uses a dedicated empty bag in VM::collectVarExportPropertiesForBuiltin
// (Zend get_properties_for VAR_EXPORT returns empty — not just privacy).
$valueProp = new ClassProperty(
self::PROP_VALUE,
null,
$mixedProto,
true,
$priv,
strtolower(self::CLASS_NAME)
);
$entry->properties[] = $valueProp;
$entry->constructor = new SensitiveParameterValueConstruct();
$entry->methods['__construct'] = $entry->constructor;
$entry->methodVisibility['__construct'] = $pub;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Language: SensitiveParameterValue json_encode/var_export hide secret (#23042, Zend/zend_exceptions.c)
--FILE--
<?php
function f(#[\SensitiveParameter] string $password, string $user) {
return debug_backtrace();
}
$sp = f('secret', 'bob')[0]['args'][0];
echo 'json=', json_encode($sp), "\n";
$export = str_replace("\n", ' ', var_export($sp, true));
echo 'export_has_secret=', (str_contains($export, 'secret') ? 'yes' : 'no'), "\n";
echo 'set_state_empty=', (str_contains($export, 'array( )') || str_contains($export, 'array()') ? 'yes' : 'no'), "\n";
echo 'getvalue=', $sp->getValue(), "\n";
ob_start();
var_dump($sp);
$dump = ob_get_clean();
echo 'vardump_has_secret=', (str_contains($dump, 'secret') ? 'yes' : 'no'), "\n";
--EXPECT--
json={}
export_has_secret=no
set_state_empty=yes
getvalue=secret
vardump_has_secret=no
15 changes: 15 additions & 0 deletions test/repro/sensitive_parameter_value_json_var_export.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
// Issue #23042 — SensitiveParameterValue must not leak via json_encode / var_export.
function f(#[\SensitiveParameter] string $password, string $user) {
return debug_backtrace();
}
$sp = f('secret', 'bob')[0]['args'][0];
echo 'json=', json_encode($sp), "\n";
$export = str_replace("\n", ' ', var_export($sp, true));
echo 'var_export=', $export, "\n";
echo 'getvalue=', $sp->getValue(), "\n";
ob_start();
var_dump($sp);
$dump = ob_get_clean();
echo 'vardump_has_secret=', (str_contains($dump, 'secret') ? 'yes' : 'no'), "\n";
echo 'export_has_secret=', (str_contains($export, 'secret') ? 'yes' : 'no'), "\n";
1 change: 1 addition & 0 deletions test/unit/SensitiveParameterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static function providePHPTests(): \Generator
[
'sensitive_parameter_backtrace.phpt',
'sensitive_parameter_trace_string.phpt',
'sensitive_parameter_value_json_var_export.phpt',
] as $file
) {
yield $file => self::parsePHPT(
Expand Down