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:
Benjamin Dalsass
2025-08-06 08:54:56 +02:00
committed by GitHub
parent 603340b852
commit cdbcd14767
608 changed files with 5020 additions and 3793 deletions

View File

@@ -82,7 +82,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
continue;
}
if ($isRoot) {
if ($v->hasTag('container.excluded')) {
if ($v instanceof Definition && $v->hasTag('container.excluded')) {
continue;
}
$this->currentId = $k;
@@ -219,6 +219,10 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
return new \ReflectionMethod(static function (...$arguments) {}, '__invoke');
}
if ($r->hasMethod('__callStatic') && ($r = $r->getMethod('__callStatic')) && $r->isPublic()) {
return new \ReflectionMethod(static function (...$arguments) {}, '__invoke');
}
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}

View File

@@ -696,7 +696,7 @@ class AutowirePass extends AbstractRecursivePass
return null;
}
private function populateAutowiringAlias(string $id, string $target = null): void
private function populateAutowiringAlias(string $id, ?string $target = null): void
{
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) {
return;
@@ -716,7 +716,7 @@ class AutowirePass extends AbstractRecursivePass
}
}
private function getCombinedAlias(string $type, string $name = null): ?string
private function getCombinedAlias(string $type, ?string $name = null): ?string
{
if (str_contains($type, '&')) {
$types = explode('&', $type);

View File

@@ -28,6 +28,7 @@ class CheckCircularReferencesPass implements CompilerPassInterface
{
private array $currentPath;
private array $checkedNodes;
private array $checkedLazyNodes;
/**
* Checks the ContainerBuilder object for circular references.
@@ -59,22 +60,36 @@ class CheckCircularReferencesPass implements CompilerPassInterface
$node = $edge->getDestNode();
$id = $node->getId();
if (empty($this->checkedNodes[$id])) {
// Don't check circular references for lazy edges
if (!$node->getValue() || (!$edge->isLazy() && !$edge->isWeak())) {
$searchKey = array_search($id, $this->currentPath);
$this->currentPath[] = $id;
if (!empty($this->checkedNodes[$id])) {
continue;
}
if (false !== $searchKey) {
throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey));
}
$isLeaf = !!$node->getValue();
$isConcrete = !$edge->isLazy() && !$edge->isWeak();
$this->checkOutEdges($node->getOutEdges());
// Skip already checked lazy services if they are still lazy. Will not gain any new information.
if (!empty($this->checkedLazyNodes[$id]) && (!$isLeaf || !$isConcrete)) {
continue;
}
// Process concrete references, otherwise defer check circular references for lazy edges.
if (!$isLeaf || $isConcrete) {
$searchKey = array_search($id, $this->currentPath);
$this->currentPath[] = $id;
if (false !== $searchKey) {
throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey));
}
$this->checkOutEdges($node->getOutEdges());
$this->checkedNodes[$id] = true;
array_pop($this->currentPath);
unset($this->checkedLazyNodes[$id]);
} else {
$this->checkedLazyNodes[$id] = true;
}
array_pop($this->currentPath);
}
}
}

View File

@@ -60,15 +60,7 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
if (isset($this->serviceLocatorContextIds[$currentId])) {
$currentId = $this->serviceLocatorContextIds[$currentId];
$locator = $this->container->getDefinition($this->currentId)->getFactory()[0];
foreach ($locator->getArgument(0) as $k => $v) {
if ($v->getValues()[0] === $value) {
if ($k !== $id) {
$currentId = $k.'" in the container provided to "'.$currentId;
}
throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
}
}
$this->throwServiceNotFoundException($value, $currentId, $locator->getArgument(0));
}
if ('.' === $currentId[0] && $graph->hasNode($currentId)) {
@@ -82,14 +74,21 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
$currentId = $sourceId;
break;
}
if (isset($this->serviceLocatorContextIds[$sourceId])) {
$currentId = $this->serviceLocatorContextIds[$sourceId];
$locator = $this->container->getDefinition($this->currentId);
$this->throwServiceNotFoundException($value, $currentId, $locator->getArgument(0));
}
}
}
throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
$this->throwServiceNotFoundException($value, $currentId, $value);
}
private function getAlternatives(string $id): array
private function throwServiceNotFoundException(Reference $ref, string $sourceId, $value): void
{
$id = (string) $ref;
$alternatives = [];
foreach ($this->container->getServiceIds() as $knownId) {
if ('' === $knownId || '.' === $knownId[0] || $knownId === $this->currentId) {
@@ -102,6 +101,28 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
}
}
return $alternatives;
$pass = new class() extends AbstractRecursivePass {
public Reference $ref;
public string $sourceId;
public array $alternatives;
public function processValue(mixed $value, bool $isRoot = false): mixed
{
if ($this->ref !== $value) {
return parent::processValue($value, $isRoot);
}
$sourceId = $this->sourceId;
if (null !== $this->currentId && $this->currentId !== (string) $value) {
$sourceId = $this->currentId.'" in the container provided to "'.$sourceId;
}
throw new ServiceNotFoundException((string) $value, $sourceId, null, $this->alternatives);
}
};
$pass->ref = $ref;
$pass->sourceId = $sourceId;
$pass->alternatives = $alternatives;
$pass->processValue($value, true);
}
}

View File

@@ -163,7 +163,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass
/**
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
*/
private function checkType(Definition $checkedDefinition, mixed $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void
private function checkType(Definition $checkedDefinition, mixed $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, ?\ReflectionType $reflectionType = null): void
{
$reflectionType ??= $parameter->getType();

View File

@@ -65,7 +65,10 @@ class DefinitionErrorExceptionPass extends AbstractRecursivePass
}
if ($value instanceof Reference && $this->currentId !== $targetId = (string) $value) {
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
if (
ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()
|| ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()
) {
$this->sourceReferences[$targetId][$this->currentId] ??= true;
} else {
$this->sourceReferences[$targetId][$this->currentId] = false;

View File

@@ -34,7 +34,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass
private array $notInlinableIds = [];
private ?ServiceReferenceGraph $graph = null;
public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null)
public function __construct(?AnalyzeServiceReferencesPass $analyzingPass = null)
{
$this->analyzingPass = $analyzingPass;
}
@@ -73,6 +73,9 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass
if (!$this->graph->hasNode($id)) {
continue;
}
if ($definition->isPublic()) {
$this->connectedIds[$id] = true;
}
foreach ($this->graph->getNode($id)->getOutEdges() as $edge) {
if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) {
$this->currentId = $id;
@@ -189,17 +192,13 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass
return true;
}
if ($definition->isPublic()) {
if ($definition->isPublic()
|| $this->currentId === $id
|| !$this->graph->hasNode($id)
) {
return false;
}
if (!$this->graph->hasNode($id)) {
return true;
}
if ($this->currentId === $id) {
return false;
}
$this->connectedIds[$id] = true;
$srcIds = [];
@@ -224,6 +223,8 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass
return false;
}
return $this->container->getDefinition($srcId)->isShared();
$srcDefinition = $this->container->getDefinition($srcId);
return $srcDefinition->isShared() && !$srcDefinition->isLazy();
}
}

View File

@@ -156,7 +156,7 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder
{
private string $extensionClass;
public function __construct(ExtensionInterface $extension, ParameterBagInterface $parameterBag = null)
public function __construct(ExtensionInterface $extension, ?ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
@@ -178,7 +178,7 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder
throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass));
}
public function resolveEnvPlaceholders(mixed $value, string|bool $format = null, array &$usedEnvs = null): mixed
public function resolveEnvPlaceholders(mixed $value, string|bool|null $format = null, ?array &$usedEnvs = null): mixed
{
if (true !== $format || !\is_string($value)) {
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);

View File

@@ -85,7 +85,8 @@ trait PriorityTaggedServiceTrait
} elseif (null === $defaultIndex && $defaultPriorityMethod && $class) {
$defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem);
}
$index ??= $defaultIndex ??= $serviceId;
$decorated = $definition->getTag('container.decorator')[0]['id'] ?? null;
$index = $index ?? $defaultIndex ?? $defaultIndex = $decorated ?? $serviceId;
$services[] = [$priority, ++$i, $index, $serviceId, $class];
}
@@ -133,6 +134,10 @@ class PriorityTaggedServiceUtil
return null;
}
if ($r->isInterface()) {
return null;
}
if (null !== $indexAttribute) {
$service = $class !== $serviceId ? sprintf('service "%s"', $serviceId) : 'on the corresponding service';
$message = [sprintf('Either method "%s::%s()" should ', $class, $defaultMethod), sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)];

View File

@@ -11,9 +11,11 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
@@ -181,10 +183,17 @@ class ResolveBindingsPass extends AbstractRecursivePass
foreach ($reflectionMethod->getParameters() as $key => $parameter) {
$names[$key] = $parameter->name;
if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) {
if (\array_key_exists($key, $arguments) && '' !== $arguments[$key] && !$arguments[$key] instanceof AbstractArgument) {
continue;
}
if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) {
if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name] && !$arguments[$parameter->name] instanceof AbstractArgument) {
continue;
}
if (
$value->isAutowired()
&& !$value->hasTag('container.ignore_attributes')
&& $parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)
) {
continue;
}
@@ -222,7 +231,9 @@ class ResolveBindingsPass extends AbstractRecursivePass
foreach ($names as $key => $name) {
if (\array_key_exists($name, $arguments) && (0 === $key || \array_key_exists($key - 1, $arguments))) {
$arguments[$key] = $arguments[$name];
if (!array_key_exists($key, $arguments)) {
$arguments[$key] = $arguments[$name];
}
unset($arguments[$name]);
}
}

View File

@@ -102,7 +102,7 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass
return new Reference($id);
}
public static function register(ContainerBuilder $container, array $map, string $callerId = null): Reference
public static function register(ContainerBuilder $container, array $map, ?string $callerId = null): Reference
{
foreach ($map as $k => $v) {
$map[$k] = new ServiceClosureArgument($v);

View File

@@ -74,7 +74,7 @@ class ServiceReferenceGraph
/**
* Connects 2 nodes together in the Graph.
*/
public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void
public function connect(?string $sourceId, mixed $sourceValue, ?string $destId, mixed $destValue = null, ?Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false): void
{
if (null === $sourceId || null === $destId) {
return;

View File

@@ -49,17 +49,8 @@ class ValidateEnvPlaceholdersPass implements CompilerPassInterface
$defaultBag = new ParameterBag($resolvingBag->all());
$envTypes = $resolvingBag->getProvidedTypes();
foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) {
$values = [];
if (false === $i = strpos($env, ':')) {
$default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string'];
$defaultType = null !== $default ? get_debug_type($default) : 'string';
$values[$defaultType] = $default;
} else {
$prefix = substr($env, 0, $i);
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
}
}
$values = $this->getPlaceholderValues($env, $defaultBag, $envTypes);
foreach ($placeholders as $placeholder) {
BaseNode::setPlaceholder($placeholder, $values);
}
@@ -100,4 +91,50 @@ class ValidateEnvPlaceholdersPass implements CompilerPassInterface
$this->extensionConfig = [];
}
}
/**
* @param array<string, list<string>> $envTypes
*
* @return array<string, mixed>
*/
private function getPlaceholderValues(string $env, ParameterBag $defaultBag, array $envTypes): array
{
if (false === $i = strpos($env, ':')) {
[$default, $defaultType] = $this->getParameterDefaultAndDefaultType("env($env)", $defaultBag);
return [$defaultType => $default];
}
$prefix = substr($env, 0, $i);
if ('default' === $prefix) {
$parts = explode(':', $env);
array_shift($parts); // Remove 'default' prefix
$parameter = array_shift($parts); // Retrieve and remove parameter
[$defaultParameter, $defaultParameterType] = $this->getParameterDefaultAndDefaultType($parameter, $defaultBag);
return [
$defaultParameterType => $defaultParameter,
...$this->getPlaceholderValues(implode(':', $parts), $defaultBag, $envTypes),
];
}
$values = [];
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
}
return $values;
}
/**
* @return array{0: string, 1: string}
*/
private function getParameterDefaultAndDefaultType(string $name, ParameterBag $defaultBag): array
{
$default = $defaultBag->has($name) ? $defaultBag->get($name) : self::TYPE_FIXTURES['string'];
$defaultType = null !== $default ? get_debug_type($default) : 'string';
return [$default, $defaultType];
}
}