diff --git a/Zend/tests/function_arguments/sensitive_parameter.phpt b/Zend/tests/function_arguments/sensitive_parameter.phpt new file mode 100644 index 0000000000000..8a76db6626de0 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter.phpt @@ -0,0 +1,51 @@ +--TEST-- +The SensitiveParameter attribute suppresses the single sensitive argument. +--FILE-- +getTrace()); +} + +test('sensitive'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter.php(10): test(Object(SensitiveParameterValue)) +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter.php" + ["line"]=> + int(10) + ["function"]=> + string(4) "test" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter.php" + ["line"]=> + int(10) + ["function"]=> + string(4) "test" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_arrow_function.phpt b/Zend/tests/function_arguments/sensitive_parameter_arrow_function.phpt new file mode 100644 index 0000000000000..da57bc55e7f46 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_arrow_function.phpt @@ -0,0 +1,28 @@ +--TEST-- +The SensitiveParameter attribute suppresses the single sensitive argument for arrow functions. +--FILE-- + (new Exception)->getTrace(); + +var_dump($test('sensitive')); + +?> +--EXPECTF-- +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_arrow_function.php" + ["line"]=> + int(5) + ["function"]=> + string(9) "{closure}" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_closure.phpt b/Zend/tests/function_arguments/sensitive_parameter_closure.phpt new file mode 100644 index 0000000000000..f2b038ceab953 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_closure.phpt @@ -0,0 +1,51 @@ +--TEST-- +The SensitiveParameter attribute suppresses the single sensitive argument for closures. +--FILE-- +getTrace()); +}; + +$test('sensitive'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_closure.php(10): {closure}(Object(SensitiveParameterValue)) +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_closure.php" + ["line"]=> + int(10) + ["function"]=> + string(9) "{closure}" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_closure.php" + ["line"]=> + int(10) + ["function"]=> + string(9) "{closure}" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_correctly_captures_original.phpt b/Zend/tests/function_arguments/sensitive_parameter_correctly_captures_original.phpt new file mode 100644 index 0000000000000..1a6dc97dd2e43 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_correctly_captures_original.phpt @@ -0,0 +1,70 @@ +--TEST-- +The SensitiveParameterValue replacement value correctly captures the original value. +--FILE-- +getMessage(), PHP_EOL; + $testFrame = $e->getTrace()[0]; + var_dump($testFrame['function']); + var_dump(count($testFrame['args'])); + var_dump($testFrame['args'][0]); + assert($testFrame['args'][1] instanceof SensitiveParameterValue); + var_dump($testFrame['args'][1]->getValue()); + var_dump($testFrame['args'][2]); + echo "Success", PHP_EOL; +} + +function test2( + $foo, + #[SensitiveParameter] ...$variadic, +) { + throw new Exception('Error 2'); +} + +try { + test2('foo', 'variadic1', 'variadic2', 'variadic3'); + echo 'Not reached'; +} catch (Exception $e) { + echo $e->getMessage(), PHP_EOL; + $testFrame = $e->getTrace()[0]; + var_dump($testFrame['function']); + var_dump(count($testFrame['args'])); + var_dump($testFrame['args'][0]); + assert($testFrame['args'][1] instanceof SensitiveParameterValue); + var_dump($testFrame['args'][1]->getValue()); + assert($testFrame['args'][2] instanceof SensitiveParameterValue); + var_dump($testFrame['args'][2]->getValue()); + assert($testFrame['args'][3] instanceof SensitiveParameterValue); + var_dump($testFrame['args'][3]->getValue()); + echo "Success", PHP_EOL; +} + +?> +--EXPECTF-- +Error +string(4) "test" +int(3) +string(3) "foo" +string(3) "bar" +string(3) "baz" +Success +Error 2 +string(5) "test2" +int(4) +string(3) "foo" +string(9) "variadic1" +string(9) "variadic2" +string(9) "variadic3" +Success diff --git a/Zend/tests/function_arguments/sensitive_parameter_eval_call.phpt b/Zend/tests/function_arguments/sensitive_parameter_eval_call.phpt new file mode 100644 index 0000000000000..8bb8bc736f477 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_eval_call.phpt @@ -0,0 +1,72 @@ +--TEST-- +The SensitiveParameter attribute suppresses the single sensitive argument in a function called in eval(). +--FILE-- +getTrace()); +} + +eval(<<<'EOT' +test('sensitive'); +EOT); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_eval_call.php(11) : eval()'d code(1): test(Object(SensitiveParameterValue)) +#1 %ssensitive_parameter_eval_call.php(11): eval() +array(2) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_eval_call.php(11) : eval()'d code" + ["line"]=> + int(1) + ["function"]=> + string(4) "test" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } + [1]=> + array(3) { + ["file"]=> + string(%d) "%ssensitive_parameter_eval_call.php" + ["line"]=> + int(11) + ["function"]=> + string(4) "eval" + } +} +array(2) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_eval_call.php(11) : eval()'d code" + ["line"]=> + int(1) + ["function"]=> + string(4) "test" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } + [1]=> + array(3) { + ["file"]=> + string(%d) "%ssensitive_parameter_eval_call.php" + ["line"]=> + int(11) + ["function"]=> + string(4) "eval" + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_eval_define.phpt b/Zend/tests/function_arguments/sensitive_parameter_eval_define.phpt new file mode 100644 index 0000000000000..29fdbc95a9ca9 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_eval_define.phpt @@ -0,0 +1,53 @@ +--TEST-- +The SensitiveParameter attribute suppresses the single sensitive argument in a function created in eval(). +--FILE-- +getTrace()); +} +EOT); + +test('sensitive'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_eval_define.php(12): test(Object(SensitiveParameterValue)) +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_eval_define.php" + ["line"]=> + int(12) + ["function"]=> + string(4) "test" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_eval_define.php" + ["line"]=> + int(12) + ["function"]=> + string(4) "test" + ["args"]=> + array(1) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_extra_arguments.phpt b/Zend/tests/function_arguments/sensitive_parameter_extra_arguments.phpt new file mode 100644 index 0000000000000..823e13f2ea790 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_extra_arguments.phpt @@ -0,0 +1,62 @@ +--TEST-- +The SensitiveParameter attribute does not suppress superfluous arguments if the last parameter is sensitive. +--FILE-- +getTrace()); +} + +test('foo', 'bar', 'baz'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_extra_arguments.php(13): test('foo', Object(SensitiveParameterValue), 'baz') +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_extra_arguments.php" + ["line"]=> + int(13) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + string(3) "foo" + [1]=> + object(SensitiveParameterValue)#%d (0) { + } + [2]=> + string(3) "baz" + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_extra_arguments.php" + ["line"]=> + int(13) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + string(3) "foo" + [1]=> + object(SensitiveParameterValue)#%d (0) { + } + [2]=> + string(3) "baz" + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_multiple_arguments.phpt b/Zend/tests/function_arguments/sensitive_parameter_multiple_arguments.phpt new file mode 100644 index 0000000000000..f0962d88829f8 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_multiple_arguments.phpt @@ -0,0 +1,65 @@ +--TEST-- +The SensitiveParameter attribute suppresses the correct sensitive arguments. +--FILE-- +getTrace()); +} + +test('sensitive1', 'non_sensitive', 'sensitive2'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_multiple_arguments.php(14): test(Object(SensitiveParameterValue), 'non_sensitive', Object(SensitiveParameterValue)) +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_multiple_arguments.php" + ["line"]=> + int(14) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + string(13) "non_sensitive" + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_multiple_arguments.php" + ["line"]=> + int(14) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + string(13) "non_sensitive" + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_named_arguments.phpt b/Zend/tests/function_arguments/sensitive_parameter_named_arguments.phpt new file mode 100644 index 0000000000000..24e2268e8d956 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_named_arguments.phpt @@ -0,0 +1,65 @@ +--TEST-- +The SensitiveParameter attribute handles named arguments. +--FILE-- +getTrace()); +} + +test(non_sensitive: 'non_sensitive', sensitive2: 'sensitive2'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_named_arguments.php(14): test(Object(SensitiveParameterValue), 'non_sensitive', Object(SensitiveParameterValue)) +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_named_arguments.php" + ["line"]=> + int(14) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + string(13) "non_sensitive" + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_named_arguments.php" + ["line"]=> + int(14) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + string(13) "non_sensitive" + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_nested_calls.phpt b/Zend/tests/function_arguments/sensitive_parameter_nested_calls.phpt new file mode 100644 index 0000000000000..6f481b72cbe71 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_nested_calls.phpt @@ -0,0 +1,115 @@ +--TEST-- +The SensitiveParameter attribute handles nested function calls correctly. +--FILE-- +getTrace()); +} + +function wrapper( + $non_sensitive = null, + #[SensitiveParameter] $sensitive1 = null, + #[SensitiveParameter] $sensitive2 = null, +) +{ + test($non_sensitive, $sensitive1, $sensitive2); +} + +wrapper('foo', 'bar', 'baz'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_nested_calls.php(20): test(Object(SensitiveParameterValue), 'bar', Object(SensitiveParameterValue)) +#1 %ssensitive_parameter_nested_calls.php(23): wrapper('foo', Object(SensitiveParameterValue), Object(SensitiveParameterValue)) +array(2) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_nested_calls.php" + ["line"]=> + int(20) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + string(3) "bar" + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } + [1]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_nested_calls.php" + ["line"]=> + int(23) + ["function"]=> + string(7) "wrapper" + ["args"]=> + array(3) { + [0]=> + string(3) "foo" + [1]=> + object(SensitiveParameterValue)#%d (0) { + } + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(2) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_nested_calls.php" + ["line"]=> + int(20) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + string(3) "bar" + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } + [1]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_nested_calls.php" + ["line"]=> + int(23) + ["function"]=> + string(7) "wrapper" + ["args"]=> + array(3) { + [0]=> + string(3) "foo" + [1]=> + object(SensitiveParameterValue)#%d (0) { + } + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/tests/function_arguments/sensitive_parameter_value.phpt b/Zend/tests/function_arguments/sensitive_parameter_value.phpt new file mode 100644 index 0000000000000..2949b0b18de61 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value.phpt @@ -0,0 +1,43 @@ +--TEST-- +A SensitiveParameterValue keeps the inner value secret. +--FILE-- +getValue()", PHP_EOL; +var_dump($v->getValue()); + +?> +--EXPECTF-- +# var_dump() / debug_zval_dump() +object(SensitiveParameterValue)#%d (0) { +} +object(SensitiveParameterValue)#%d (%d) refcount(%d){ +} + +# var_export() +SensitiveParameterValue::__set_state(array( +)) + +# (array) / json_encode() +array(0) { +} +string(2) "{}" + +# ->getValue() +string(6) "secret" diff --git a/Zend/tests/function_arguments/sensitive_parameter_value_clone.phpt b/Zend/tests/function_arguments/sensitive_parameter_value_clone.phpt new file mode 100644 index 0000000000000..5170e62ebe3d4 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value_clone.phpt @@ -0,0 +1,15 @@ +--TEST-- +A SensitiveParameterValue is clonable. +--FILE-- +getValue()); +var_dump($v2->getValue()); + +?> +--EXPECTF-- +string(6) "secret" +string(6) "secret" diff --git a/Zend/tests/function_arguments/sensitive_parameter_value_keeps_object_alive.phpt b/Zend/tests/function_arguments/sensitive_parameter_value_keeps_object_alive.phpt new file mode 100644 index 0000000000000..e7b0de51b969e --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value_keeps_object_alive.phpt @@ -0,0 +1,74 @@ +--TEST-- +A SensitiveParameterValue keeps inner objects alive. +--FILE-- +id, PHP_EOL; + } + + public function __destruct() + { + echo __METHOD__, " - ", $this->id, PHP_EOL; + } + + public function getId(): int + { + return $this->id; + } +} + +function test(#[SensitiveParameter] CustomDestructor $o, bool $throw) +{ + if ($throw) { + throw new Exception('Error'); + } +} + +function wrapper(int $id, bool $throw) +{ + $o = new CustomDestructor($id); + test($o, $throw); +} + +function main(): SensitiveParameterValue +{ + try { + echo "Before 1", PHP_EOL; + wrapper(1, false); + echo "After 1", PHP_EOL; + echo "Before 2", PHP_EOL; + wrapper(2, true); + echo "Not Reached: After 2", PHP_EOL; + } catch (Exception $e) { + echo "catch", PHP_EOL; + return $e->getTrace()[0]['args'][0]; + } +} + +$v = main(); + +var_dump($v->getValue()->getId()); + +echo "Before unset", PHP_EOL; + +unset($v); + +echo "After unset", PHP_EOL; + +?> +--EXPECT-- +Before 1 +CustomDestructor::__construct - 1 +CustomDestructor::__destruct - 1 +After 1 +Before 2 +CustomDestructor::__construct - 2 +catch +int(2) +Before unset +CustomDestructor::__destruct - 2 +After unset diff --git a/Zend/tests/function_arguments/sensitive_parameter_value_no_dynamic_property.phpt b/Zend/tests/function_arguments/sensitive_parameter_value_no_dynamic_property.phpt new file mode 100644 index 0000000000000..86cbc2a1ae3e5 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value_no_dynamic_property.phpt @@ -0,0 +1,15 @@ +--TEST-- +A SensitiveParameterValue does not allow dynamic properties. +--FILE-- +foo = 'bar'; + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot create dynamic property SensitiveParameterValue::$foo in %ssensitive_parameter_value_no_dynamic_property.php:5 +Stack trace: +#0 {main} + thrown in %ssensitive_parameter_value_no_dynamic_property.php on line 5 diff --git a/Zend/tests/function_arguments/sensitive_parameter_value_reflection.phpt b/Zend/tests/function_arguments/sensitive_parameter_value_reflection.phpt new file mode 100644 index 0000000000000..020cbef38d269 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value_reflection.phpt @@ -0,0 +1,15 @@ +--TEST-- +A SensitiveParameterValue's value is accessible using reflection. +--FILE-- +getProperty('value'); + +var_dump($p->getValue($v)); + +?> +--EXPECTF-- +string(6) "secret" diff --git a/Zend/tests/function_arguments/sensitive_parameter_value_serialize.phpt b/Zend/tests/function_arguments/sensitive_parameter_value_serialize.phpt new file mode 100644 index 0000000000000..79a2e359cd8cb --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value_serialize.phpt @@ -0,0 +1,16 @@ +--TEST-- +A SensitiveParameterValue may not be serialized. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: Serialization of 'SensitiveParameterValue' is not allowed in %ssensitive_parameter_value_serialize.php:5 +Stack trace: +#0 %ssensitive_parameter_value_serialize.php(5): serialize(Object(SensitiveParameterValue)) +#1 {main} + thrown in %ssensitive_parameter_value_serialize.php on line 5 diff --git a/Zend/tests/function_arguments/sensitive_parameter_value_to_string.phpt b/Zend/tests/function_arguments/sensitive_parameter_value_to_string.phpt new file mode 100644 index 0000000000000..5945259224ab8 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_value_to_string.phpt @@ -0,0 +1,15 @@ +--TEST-- +A SensitiveParameterValue may not be converted to a string. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Object of class SensitiveParameterValue could not be converted to string in %ssensitive_parameter_value_to_string.php:5 +Stack trace: +#0 {main} + thrown in %ssensitive_parameter_value_to_string.php on line 5 diff --git a/Zend/tests/function_arguments/sensitive_parameter_variadic_arguments.phpt b/Zend/tests/function_arguments/sensitive_parameter_variadic_arguments.phpt new file mode 100644 index 0000000000000..3d9bf20a978c2 --- /dev/null +++ b/Zend/tests/function_arguments/sensitive_parameter_variadic_arguments.phpt @@ -0,0 +1,65 @@ +--TEST-- +The SensitiveParameter attribute suppresses all variadic arguments. +--FILE-- +getTrace()); +} + +test('foo', 'bar', 'baz'); + +?> +--EXPECTF-- +#0 %ssensitive_parameter_variadic_arguments.php(12): test(Object(SensitiveParameterValue), Object(SensitiveParameterValue), Object(SensitiveParameterValue)) +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_variadic_arguments.php" + ["line"]=> + int(12) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + object(SensitiveParameterValue)#%d (0) { + } + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} +array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%ssensitive_parameter_variadic_arguments.php" + ["line"]=> + int(12) + ["function"]=> + string(4) "test" + ["args"]=> + array(3) { + [0]=> + object(SensitiveParameterValue)#%d (0) { + } + [1]=> + object(SensitiveParameterValue)#%d (0) { + } + [2]=> + object(SensitiveParameterValue)#%d (0) { + } + } + } +} diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 0d27d1bcaa216..9f7b8f01448b5 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -21,11 +21,16 @@ #include "zend_API.h" #include "zend_attributes.h" #include "zend_attributes_arginfo.h" +#include "zend_exceptions.h" #include "zend_smart_str.h" ZEND_API zend_class_entry *zend_ce_attribute; ZEND_API zend_class_entry *zend_ce_return_type_will_change_attribute; ZEND_API zend_class_entry *zend_ce_allow_dynamic_properties; +ZEND_API zend_class_entry *zend_ce_sensitive_parameter; +ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; + +static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; static HashTable internal_attributes; @@ -91,6 +96,53 @@ ZEND_METHOD(AllowDynamicProperties, __construct) ZEND_PARSE_PARAMETERS_NONE(); } +ZEND_METHOD(SensitiveParameter, __construct) +{ + ZEND_PARSE_PARAMETERS_NONE(); +} + +ZEND_METHOD(SensitiveParameterValue, __construct) +{ + zval *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), value); +} + +ZEND_METHOD(SensitiveParameterValue, getValue) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + ZVAL_COPY(return_value, OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0)); +} + +ZEND_METHOD(SensitiveParameterValue, __debugInfo) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_EMPTY_ARRAY(); +} + +static zend_object *attributes_sensitive_parameter_value_new(zend_class_entry *ce) +{ + zend_object *object; + + object = zend_objects_new(ce); + object->handlers = &attributes_object_handlers_sensitive_parameter_value; + + object_properties_init(object, ce); + + return object; +} + +static HashTable *attributes_sensitive_parameter_value_get_properties_for(zend_object *zobj, zend_prop_purpose purpose) +{ + return NULL; +} + static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) { if (attributes) { @@ -313,6 +365,16 @@ void zend_register_attribute_ce(void) zend_ce_allow_dynamic_properties = register_class_AllowDynamicProperties(); attr = zend_internal_attribute_register(zend_ce_allow_dynamic_properties, ZEND_ATTRIBUTE_TARGET_CLASS); attr->validator = validate_allow_dynamic_properties; + + zend_ce_sensitive_parameter = register_class_SensitiveParameter(); + attr = zend_internal_attribute_register(zend_ce_sensitive_parameter, ZEND_ATTRIBUTE_TARGET_PARAMETER); + + memcpy(&attributes_object_handlers_sensitive_parameter_value, &std_object_handlers, sizeof(zend_object_handlers)); + attributes_object_handlers_sensitive_parameter_value.get_properties_for = attributes_sensitive_parameter_value_get_properties_for; + + /* This is not an actual attribute, thus the zend_internal_attribute_register() call is missing. */ + zend_ce_sensitive_parameter_value = register_class_SensitiveParameterValue(); + zend_ce_sensitive_parameter_value->create_object = attributes_sensitive_parameter_value_new; } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index a55dc562450d2..7b6c919527d34 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -41,6 +41,8 @@ BEGIN_EXTERN_C() extern ZEND_API zend_class_entry *zend_ce_attribute; extern ZEND_API zend_class_entry *zend_ce_allow_dynamic_properties; +extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter; +extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; typedef struct { zend_string *name; diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index 0469016704b16..842ed9229cd6e 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -18,3 +18,26 @@ final class AllowDynamicProperties { public function __construct() {} } + +/** + * @strict-properties + */ +final class SensitiveParameter +{ + public function __construct() {} +} + +/** + * @strict-properties + * @not-serializable + */ +final class SensitiveParameterValue +{ + private readonly mixed $value; + + public function __construct(mixed $value) {} + + public function getValue(): mixed {} + + public function __debugInfo(): array {} +} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index 657ab924c745a..7c624949bf24b 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 024e849a9dfa8789f13dd1d2ac222a48e4b017f1 */ + * Stub hash: 5d9a092c1f0da5f32d9a161cc5166ed794ffe8e9 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -10,10 +10,26 @@ ZEND_END_ARG_INFO() #define arginfo_class_AllowDynamicProperties___construct arginfo_class_ReturnTypeWillChange___construct +#define arginfo_class_SensitiveParameter___construct arginfo_class_ReturnTypeWillChange___construct + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SensitiveParameterValue___construct, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SensitiveParameterValue_getValue, 0, 0, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SensitiveParameterValue___debugInfo, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + ZEND_METHOD(Attribute, __construct); ZEND_METHOD(ReturnTypeWillChange, __construct); ZEND_METHOD(AllowDynamicProperties, __construct); +ZEND_METHOD(SensitiveParameter, __construct); +ZEND_METHOD(SensitiveParameterValue, __construct); +ZEND_METHOD(SensitiveParameterValue, getValue); +ZEND_METHOD(SensitiveParameterValue, __debugInfo); static const zend_function_entry class_Attribute_methods[] = { @@ -33,6 +49,20 @@ static const zend_function_entry class_AllowDynamicProperties_methods[] = { ZEND_FE_END }; + +static const zend_function_entry class_SensitiveParameter_methods[] = { + ZEND_ME(SensitiveParameter, __construct, arginfo_class_SensitiveParameter___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_SensitiveParameterValue_methods[] = { + ZEND_ME(SensitiveParameterValue, __construct, arginfo_class_SensitiveParameterValue___construct, ZEND_ACC_PUBLIC) + ZEND_ME(SensitiveParameterValue, getValue, arginfo_class_SensitiveParameterValue_getValue, ZEND_ACC_PUBLIC) + ZEND_ME(SensitiveParameterValue, __debugInfo, arginfo_class_SensitiveParameterValue___debugInfo, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_Attribute(void) { zend_class_entry ce, *class_entry; @@ -71,3 +101,31 @@ static zend_class_entry *register_class_AllowDynamicProperties(void) return class_entry; } + +static zend_class_entry *register_class_SensitiveParameter(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "SensitiveParameter", class_SensitiveParameter_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + return class_entry; +} + +static zend_class_entry *register_class_SensitiveParameterValue(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "SensitiveParameterValue", class_SensitiveParameterValue_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; + + zval property_value_default_value; + ZVAL_UNDEF(&property_value_default_value); + zend_string *property_value_name = zend_string_init("value", sizeof("value") - 1, 1); + zend_declare_typed_property(class_entry, property_value_name, &property_value_default_value, ZEND_ACC_PRIVATE|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ANY)); + zend_string_release(property_value_name); + + return class_entry; +} diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 529ade072aa24..963332e0f7ed8 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -24,6 +24,7 @@ #include "zend_builtin_functions.h" #include "zend_constants.h" #include "zend_ini.h" +#include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_extensions.h" #include "zend_closures.h" @@ -1557,27 +1558,66 @@ static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) / */ while (i < first_extra_arg) { zend_string *arg_name = call->func->op_array.vars[i]; + zval original_arg; zval *arg = zend_hash_find_ex_ind(call->symbol_table, arg_name, 1); + zend_attribute *attribute = zend_get_parameter_attribute_str( + call->func->common.attributes, + "sensitiveparameter", + sizeof("sensitiveparameter") - 1, + i + ); + + bool is_sensitive = attribute != NULL; + if (arg) { ZVAL_DEREF(arg); - Z_TRY_ADDREF_P(arg); - ZEND_HASH_FILL_SET(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + + if (is_sensitive) { + zval redacted_arg; + object_init_ex(&redacted_arg, zend_ce_sensitive_parameter_value); + zend_call_method_with_1_params(Z_OBJ_P(&redacted_arg), zend_ce_sensitive_parameter_value, &zend_ce_sensitive_parameter_value->constructor, "__construct", NULL, &original_arg); + ZEND_HASH_FILL_SET(&redacted_arg); } else { - ZEND_HASH_FILL_SET_NULL(); + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); } + ZEND_HASH_FILL_NEXT(); i++; } } else { while (i < first_extra_arg) { + zval original_arg; + zend_attribute *attribute = zend_get_parameter_attribute_str( + call->func->common.attributes, + "sensitiveparameter", + sizeof("sensitiveparameter") - 1, + i + ); + bool is_sensitive = attribute != NULL; + if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { zval *arg = p; ZVAL_DEREF(arg); - Z_TRY_ADDREF_P(arg); - ZEND_HASH_FILL_SET(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + + if (is_sensitive) { + zval redacted_arg; + object_init_ex(&redacted_arg, zend_ce_sensitive_parameter_value); + zend_call_method_with_1_params(Z_OBJ_P(&redacted_arg), zend_ce_sensitive_parameter_value, &zend_ce_sensitive_parameter_value->constructor, "__construct", NULL, &original_arg); + ZEND_HASH_FILL_SET(&redacted_arg); } else { - ZEND_HASH_FILL_SET_NULL(); + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); } + ZEND_HASH_FILL_NEXT(); p++; i++; @@ -1587,14 +1627,37 @@ static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) / } while (i < num_args) { + zval original_arg; + bool is_sensitive = 0; + + if (i < call->func->common.num_args || call->func->common.fn_flags & ZEND_ACC_VARIADIC) { + zend_attribute *attribute = zend_get_parameter_attribute_str( + call->func->common.attributes, + "sensitiveparameter", + sizeof("sensitiveparameter") - 1, + MIN(i, call->func->common.num_args) + ); + is_sensitive = attribute != NULL; + } + if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { zval *arg = p; ZVAL_DEREF(arg); - Z_TRY_ADDREF_P(arg); - ZEND_HASH_FILL_SET(arg); + ZVAL_COPY_VALUE(&original_arg, arg); } else { - ZEND_HASH_FILL_SET_NULL(); + ZVAL_NULL(&original_arg); } + + if (is_sensitive) { + zval redacted_arg; + object_init_ex(&redacted_arg, zend_ce_sensitive_parameter_value); + zend_call_method_with_1_params(Z_OBJ_P(&redacted_arg), zend_ce_sensitive_parameter_value, &zend_ce_sensitive_parameter_value->constructor, "__construct", NULL, &original_arg); + ZEND_HASH_FILL_SET(&redacted_arg); + } else { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } + ZEND_HASH_FILL_NEXT(); p++; i++;