bug #4918 Check that the use tag is allowed before resolving trait templates (fabpot)

This PR was merged into the 3.x branch.

Discussion
----------

Check that the use tag is allowed before resolving trait templates

Commits
-------

18863f0371 Check that the use tag is allowed before resolving trait templates
This commit is contained in:
Fabien Potencier
2026-09-07 19:06:37 +02:00
8 changed files with 140 additions and 3 deletions
+4 -1
View File
@@ -15,6 +15,9 @@ use Twig\Attribute\YieldReady;
use Twig\Compiler;
/**
* Wires the security checker at the very top of the template constructor, as
* the constructor resolves `use` traits before the sandbox could check them.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
#[YieldReady]
@@ -26,7 +29,7 @@ class CheckSecurityCallNode extends Node
public function compile(Compiler $compiler)
{
$compiler
->write("\$this->sandbox = \$this->extensions[SandboxExtension::class]->getChecker();\n")
->write("\$this->sandbox = \$env->getExtension(SandboxExtension::class)->getChecker();\n")
;
}
}
+26
View File
@@ -47,6 +47,32 @@ class CheckSecurityNode extends Node
public function compile(Compiler $compiler): void
{
if (isset($this->usedTags['use'])) {
$compiler
->write("\n")
->write("protected function checkTraitsAllowed(): void\n")
->write("{\n")
->indent()
->write("if (!\$this->sandbox->isSandboxed(\$this->source)) {\n")
->indent()
->write("return;\n")
->outdent()
->write("}\n\n")
->write("try {\n")
->indent()
->write("\$this->sandbox->checkSecurity(['use'], [], [], [], \$this->source);\n")
->outdent()
->write("} catch (SecurityNotAllowedTagError \$e) {\n")
->indent()
->write('$e->setTemplateLine(')->repr($this->usedTags['use'])->raw(");\n\n")
->write("throw \$e;\n")
->outdent()
->write("}\n")
->outdent()
->write("}\n")
;
}
$compiler
->write("\n")
->write("public function ensureSecurityChecked(): void\n")
+2 -1
View File
@@ -218,7 +218,8 @@ final class ModuleNode extends Node implements CoercesChildrenToStringInterface
$countTraits = \count($this->getNode('traits'));
if ($countTraits) {
// traits
$compiler->write("\$this->ensureTraitsAllowed();\n\n");
foreach ($this->getNode('traits') as $i => $trait) {
$node = $trait->getNode('template');
+1 -1
View File
@@ -125,7 +125,7 @@ final class SandboxNodeVisitor implements NodeVisitorInterface
if ($node instanceof ModuleNode) {
$this->inAModule = false;
$node->setNode('constructor_end', new Nodes([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));
$node->setNode('constructor_start', new Nodes([new CheckSecurityCallNode(), $node->getNode('constructor_start')]));
$node->setNode('class_end', new Nodes([new CheckSecurityNode($this->filters, $this->tags, $this->functions, $this->tests), $node->getNode('class_end')]));
}
+25
View File
@@ -508,6 +508,31 @@ abstract class Template
{
}
/**
* Checks the "use" tag against the sandbox policy.
*
* The constructor resolves "use" traits eagerly, which reaches the loader,
* so that tag alone is checked here; the rest of the policy still runs at
* render time.
*
* @internal
*/
public function ensureTraitsAllowed(): void
{
try {
$this->checkTraitsAllowed();
} catch (\Throwable $e) {
$this->handleException($e);
}
}
/**
* @internal
*/
protected function checkTraitsAllowed(): void
{
}
/**
* Auto-generated method to display the template with the given context.
*