mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-01 06:28:46 +02:00
N°8017 - Security - dependabot - Symfony's VarDumper vulnerable to un… (#731)
Upgrade all Symfony components to last security fix (~6.4.0)
This commit is contained in:
@@ -28,13 +28,13 @@ final class ProxyHelper
|
||||
public static function generateLazyGhost(\ReflectionClass $class): string
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class->isReadOnly()) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is readonly.', $class->name));
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name));
|
||||
}
|
||||
if ($class->isFinal()) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name));
|
||||
}
|
||||
if ($class->isInterface() || $class->isAbstract()) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name));
|
||||
if ($class->isInterface() || $class->isAbstract() || $class->isTrait()) {
|
||||
throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name));
|
||||
}
|
||||
if (\stdClass::class !== $class->name && $class->isInternal()) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name));
|
||||
@@ -58,7 +58,48 @@ final class ProxyHelper
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name));
|
||||
}
|
||||
}
|
||||
$propertyScopes = self::exportPropertyScopes($class->name);
|
||||
|
||||
$hooks = '';
|
||||
$propertyScopes = Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name);
|
||||
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
|
||||
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
|
||||
$flags = $access >> 2;
|
||||
|
||||
if ($k !== $key || !($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: property "%s::$%s" is final or private(set).', $class->name, $name));
|
||||
}
|
||||
|
||||
$p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
|
||||
|
||||
$type = self::exportType($p);
|
||||
$hooks .= "\n "
|
||||
.($p->isProtected() ? 'protected' : 'public')
|
||||
.($p->isProtectedSet() ? ' protected(set)' : '')
|
||||
." {$type} \${$name}"
|
||||
.($p->hasDefaultValue() ? ' = '.VarExporter::export($p->getDefaultValue()) : '')
|
||||
." {\n";
|
||||
|
||||
foreach ($p->getHooks() as $hook => $method) {
|
||||
if ('get' === $hook) {
|
||||
$ref = ($method->returnsReference() ? '&' : '');
|
||||
$hooks .= " {$ref}get { \$this->initializeLazyObject(); return parent::\${$name}::get(); }\n";
|
||||
} elseif ('set' === $hook) {
|
||||
$parameters = self::exportParameters($method, true);
|
||||
$arg = '$'.$method->getParameters()[0]->name;
|
||||
$hooks .= " set({$parameters}) { \$this->initializeLazyObject(); parent::\${$name}::set({$arg}); }\n";
|
||||
} else {
|
||||
throw new LogicException(sprintf('Cannot generate lazy ghost: hook "%s::%s()" is not supported.', $class->name, $method->name));
|
||||
}
|
||||
}
|
||||
|
||||
$hooks .= " }\n";
|
||||
}
|
||||
|
||||
$propertyScopes = self::exportPropertyScopes($class->name, $propertyScopes);
|
||||
|
||||
return <<<EOPHP
|
||||
extends \\{$class->name} implements \Symfony\Component\VarExporter\LazyObjectInterface
|
||||
@@ -66,7 +107,7 @@ final class ProxyHelper
|
||||
use \Symfony\Component\VarExporter\LazyGhostTrait;
|
||||
|
||||
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
|
||||
}
|
||||
{$hooks}}
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
|
||||
@@ -92,7 +133,38 @@ final class ProxyHelper
|
||||
throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name));
|
||||
}
|
||||
if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class?->isReadOnly()) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is readonly.', $class->name));
|
||||
throw new LogicException(sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name));
|
||||
}
|
||||
|
||||
$propertyScopes = $class ? Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name) : [];
|
||||
$abstractProperties = [];
|
||||
$hookedProperties = [];
|
||||
if (\PHP_VERSION_ID >= 80400 && $class) {
|
||||
foreach ($propertyScopes as $key => [$scope, $name, , $access]) {
|
||||
$propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
|
||||
$flags = $access >> 2;
|
||||
|
||||
if ($k !== $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($flags & \ReflectionProperty::IS_ABSTRACT) {
|
||||
$abstractProperties[$name] = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
|
||||
continue;
|
||||
}
|
||||
$abstractProperties[$name] = false;
|
||||
|
||||
if (!($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) {
|
||||
throw new LogicException(sprintf('Cannot generate lazy proxy: property "%s::$%s" is final or private(set).', $class->name, $name));
|
||||
}
|
||||
|
||||
$p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name);
|
||||
$hookedProperties[$name] = [$p, $p->getHooks()];
|
||||
}
|
||||
}
|
||||
|
||||
$methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []];
|
||||
@@ -101,8 +173,67 @@ final class ProxyHelper
|
||||
throw new LogicException(sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name));
|
||||
}
|
||||
$methodReflectors[] = $interface->getMethods();
|
||||
|
||||
if (\PHP_VERSION_ID >= 80400) {
|
||||
foreach ($interface->getProperties() as $p) {
|
||||
$abstractProperties[$p->name] ??= $p;
|
||||
$hookedProperties[$p->name] ??= [$p, []];
|
||||
$hookedProperties[$p->name][1] += $p->getHooks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hooks = '';
|
||||
|
||||
foreach (array_filter($abstractProperties) as $name => $p) {
|
||||
$type = self::exportType($p);
|
||||
$hooks .= "\n "
|
||||
.($p->isProtected() ? 'protected' : 'public')
|
||||
.($p->isProtectedSet() ? ' protected(set)' : '')
|
||||
." {$type} \${$name};\n";
|
||||
}
|
||||
|
||||
foreach ($hookedProperties as $name => [$p, $methods]) {
|
||||
$type = self::exportType($p);
|
||||
$hooks .= "\n "
|
||||
.($p->isProtected() ? 'protected' : 'public')
|
||||
.($p->isProtectedSet() ? ' protected(set)' : '')
|
||||
." {$type} \${$name} {\n";
|
||||
|
||||
foreach ($methods as $hook => $method) {
|
||||
if ('get' === $hook) {
|
||||
$ref = ($method->returnsReference() ? '&' : '');
|
||||
$hooks .= <<<EOPHP
|
||||
{$ref}get {
|
||||
if (isset(\$this->lazyObjectState)) {
|
||||
return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$p->name};
|
||||
}
|
||||
|
||||
return parent::\${$p->name}::get();
|
||||
}
|
||||
|
||||
EOPHP;
|
||||
} elseif ('set' === $hook) {
|
||||
$parameters = self::exportParameters($method, true);
|
||||
$arg = '$'.$method->getParameters()[0]->name;
|
||||
$hooks .= <<<EOPHP
|
||||
set({$parameters}) {
|
||||
if (isset(\$this->lazyObjectState)) {
|
||||
\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)();
|
||||
\$this->lazyObjectState->realInstance->{$p->name} = {$arg};
|
||||
}
|
||||
|
||||
parent::\${$p->name}::set({$arg});
|
||||
}
|
||||
|
||||
EOPHP;
|
||||
} else {
|
||||
throw new LogicException(sprintf('Cannot generate lazy proxy: hook "%s::%s()" is not supported.', $class->name, $method->name));
|
||||
}
|
||||
}
|
||||
|
||||
$hooks .= " }\n";
|
||||
}
|
||||
$methodReflectors = array_merge(...$methodReflectors);
|
||||
|
||||
$extendsInternalClass = false;
|
||||
if ($parent = $class) {
|
||||
@@ -112,6 +243,7 @@ final class ProxyHelper
|
||||
}
|
||||
$methodsHaveToBeProxied = $extendsInternalClass;
|
||||
$methods = [];
|
||||
$methodReflectors = array_merge(...$methodReflectors);
|
||||
|
||||
foreach ($methodReflectors as $method) {
|
||||
if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) {
|
||||
@@ -195,15 +327,40 @@ final class ProxyHelper
|
||||
$methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods;
|
||||
}
|
||||
$body = $methods ? "\n".implode("\n\n", $methods)."\n" : '';
|
||||
$propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]';
|
||||
$propertyScopes = $class ? self::exportPropertyScopes($class->name, $propertyScopes) : '[]';
|
||||
|
||||
if (
|
||||
$class?->hasMethod('__unserialize')
|
||||
&& !$class->getMethod('__unserialize')->getParameters()[0]->getType()
|
||||
) {
|
||||
// fix contravariance type problem when $class declares a `__unserialize()` method without typehint.
|
||||
$lazyProxyTraitStatement = <<<EOPHP
|
||||
use \Symfony\Component\VarExporter\LazyProxyTrait {
|
||||
__unserialize as private __doUnserialize;
|
||||
}
|
||||
EOPHP;
|
||||
|
||||
$body .= <<<EOPHP
|
||||
|
||||
public function __unserialize(\$data): void
|
||||
{
|
||||
\$this->__doUnserialize(\$data);
|
||||
}
|
||||
|
||||
EOPHP;
|
||||
} else {
|
||||
$lazyProxyTraitStatement = <<<EOPHP
|
||||
use \Symfony\Component\VarExporter\LazyProxyTrait;
|
||||
EOPHP;
|
||||
}
|
||||
|
||||
return <<<EOPHP
|
||||
{$parent} implements \\{$interfaces}
|
||||
{
|
||||
use \Symfony\Component\VarExporter\LazyProxyTrait;
|
||||
{$lazyProxyTraitStatement}
|
||||
|
||||
private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
|
||||
{$body}}
|
||||
{$hooks}{$body}}
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
|
||||
@@ -213,18 +370,20 @@ final class ProxyHelper
|
||||
EOPHP;
|
||||
}
|
||||
|
||||
public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, string &$args = null): string
|
||||
public static function exportParameters(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string
|
||||
{
|
||||
$byRefIndex = 0;
|
||||
$args = '';
|
||||
$param = null;
|
||||
$parameters = [];
|
||||
$namespace = $function instanceof \ReflectionMethod ? $function->class : $function->getNamespaceName().'\\';
|
||||
$namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0);
|
||||
foreach ($function->getParameters() as $param) {
|
||||
$parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '')
|
||||
.($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '')
|
||||
.($param->isPassedByReference() ? '&' : '')
|
||||
.($param->isVariadic() ? '...' : '').'$'.$param->name
|
||||
.($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param) : '');
|
||||
.($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : '');
|
||||
if ($param->isPassedByReference()) {
|
||||
$byRefIndex = 1 + $param->getPosition();
|
||||
}
|
||||
@@ -241,8 +400,15 @@ final class ProxyHelper
|
||||
$args = implode(', ', $args);
|
||||
}
|
||||
|
||||
return implode(', ', $parameters);
|
||||
}
|
||||
|
||||
public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string
|
||||
{
|
||||
$parameters = self::exportParameters($function, $withParameterTypes, $args);
|
||||
|
||||
$signature = 'function '.($function->returnsReference() ? '&' : '')
|
||||
.($function->isClosure() ? '' : $function->name).'('.implode(', ', $parameters).')';
|
||||
.($function->isClosure() ? '' : $function->name).'('.$parameters.')';
|
||||
|
||||
if ($function instanceof \ReflectionMethod) {
|
||||
$signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private '))
|
||||
@@ -270,7 +436,7 @@ final class ProxyHelper
|
||||
return $signature;
|
||||
}
|
||||
|
||||
public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, \ReflectionType $type = null): ?string
|
||||
public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string
|
||||
{
|
||||
if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) {
|
||||
return null;
|
||||
@@ -311,19 +477,18 @@ final class ProxyHelper
|
||||
return '';
|
||||
}
|
||||
if (null === $glue) {
|
||||
return (!$noBuiltin && $type->allowsNull() && 'mixed' !== $name ? '?' : '').$types[0];
|
||||
return (!$noBuiltin && $type->allowsNull() && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0];
|
||||
}
|
||||
sort($types);
|
||||
|
||||
return implode($glue, $types);
|
||||
}
|
||||
|
||||
private static function exportPropertyScopes(string $parent): string
|
||||
private static function exportPropertyScopes(string $parent, array $propertyScopes): string
|
||||
{
|
||||
$propertyScopes = Hydrator::$propertyScopes[$parent] ??= Hydrator::getPropertyScopes($parent);
|
||||
uksort($propertyScopes, 'strnatcmp');
|
||||
foreach ($propertyScopes as $k => $v) {
|
||||
unset($propertyScopes[$k][3]);
|
||||
unset($propertyScopes[$k][4]);
|
||||
}
|
||||
$propertyScopes = VarExporter::export($propertyScopes);
|
||||
$propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes);
|
||||
@@ -333,7 +498,7 @@ final class ProxyHelper
|
||||
return $propertyScopes;
|
||||
}
|
||||
|
||||
private static function exportDefault(\ReflectionParameter $param): string
|
||||
private static function exportDefault(\ReflectionParameter $param, $namespace): string
|
||||
{
|
||||
$default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2));
|
||||
|
||||
@@ -347,7 +512,7 @@ final class ProxyHelper
|
||||
$regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/";
|
||||
$parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
$regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(?!: )/';
|
||||
$regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/';
|
||||
$callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass())
|
||||
? fn ($m) => $m[1].match ($m[2]) {
|
||||
'new', 'false', 'true', 'null' => $m[2],
|
||||
@@ -355,13 +520,13 @@ final class ProxyHelper
|
||||
'self' => '\\'.$class->name,
|
||||
'namespace\\parent',
|
||||
'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent',
|
||||
default => '\\'.$m[2],
|
||||
}
|
||||
default => self::exportSymbol($m[2], '(' !== $m[3], $namespace),
|
||||
}.$m[3]
|
||||
: fn ($m) => $m[1].match ($m[2]) {
|
||||
'new', 'false', 'true', 'null', 'self', 'parent' => $m[2],
|
||||
'NULL' => 'null',
|
||||
default => '\\'.$m[2],
|
||||
};
|
||||
default => self::exportSymbol($m[2], '(' !== $m[3], $namespace),
|
||||
}.$m[3];
|
||||
|
||||
return implode('', array_map(fn ($part) => match ($part[0]) {
|
||||
'"' => $part, // for internal classes only
|
||||
@@ -369,4 +534,18 @@ final class ProxyHelper
|
||||
default => preg_replace_callback($regexp, $callback, $part),
|
||||
}, $parts));
|
||||
}
|
||||
|
||||
private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string
|
||||
{
|
||||
if (!$mightBeRootConst
|
||||
|| false === ($ns = strrpos($symbol, '\\'))
|
||||
|| substr($symbol, 0, $ns) !== $namespace
|
||||
|| \defined($symbol)
|
||||
|| !\defined(substr($symbol, $ns + 1))
|
||||
) {
|
||||
return '\\'.$symbol;
|
||||
}
|
||||
|
||||
return '\\'.substr($symbol, $ns + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user