Skip to content

Commit 040d50d

Browse files
committed
[SECURITY] Properly evaluate .form.yaml file extension
Resolves: #110015 Releases: main, 14.3, 13.4 Change-Id: Ia889469a7bf0c8311368dfda15f4e7437e0180ca Security-Bulletin: TYPO3-CORE-SA-2026-019 Security-References: CVE-2026-11607 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/94431 Tested-by: Oliver Hader <oliver.hader@typo3.org> Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
1 parent 87cd7c5 commit 040d50d

6 files changed

Lines changed: 39 additions & 6 deletions

File tree

typo3/sysext/form/Classes/Mvc/Persistence/FormPersistenceManager.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function load(string $persistenceIdentifier, array $formSettings, array $
8989
}
9090
try {
9191
$formDefinition = $this->yamlSource->load([$file]);
92-
$this->generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($formDefinition, $persistenceIdentifier);
92+
$this->generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension($formDefinition, $persistenceIdentifier);
9393
} catch (\Exception $e) {
9494
$formDefinition = [
9595
'type' => 'Form',
@@ -648,7 +648,7 @@ protected function loadMetaData(string|File $persistenceIdentifier, array $formS
648648
throw new NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684462);
649649
}
650650
$yaml = $this->extractMetaDataFromCouldBeFormDefinition($rawYamlContent);
651-
$this->generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
651+
$this->generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension($yaml, $persistenceIdentifier);
652652
if ($file !== null) {
653653
$yaml['fileUid'] = $file->getUid();
654654
}
@@ -694,9 +694,9 @@ protected function extractMetaDataFromCouldBeFormDefinition(string $maybeRawForm
694694
/**
695695
* @throws PersistenceManagerException
696696
*/
697-
protected function generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array $formDefinition, string $persistenceIdentifier): void
697+
protected function generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension(array $formDefinition, string $persistenceIdentifier): void
698698
{
699-
if ($this->looksLikeAFormDefinition($formDefinition) && !$this->hasValidFileExtension($persistenceIdentifier)) {
699+
if (!$this->looksLikeAFormDefinition($formDefinition) || !$this->hasValidFileExtension($persistenceIdentifier)) {
700700
throw new PersistenceManagerException(sprintf('Form definition "%s" does not end with ".form.yaml".', $persistenceIdentifier), 1531160649);
701701
}
702702
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
identifier: contact-form
2+
type: Form
3+
label: 'Contact Form'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
identifier: contact-form-with-type
2+
type: Form
3+
label: 'Contact Form'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
identifier: contact-form-without-type
2+
label: 'Contact Form'

typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/Simple.form.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
identifier: ext-form-identifier
2+
type: Form
23
prototypeName: standard
34
label: 'Label'
45
renderables:

typo3/sysext/form/Tests/Functional/Mvc/Persistence/FormPersistenceManagerTest.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@
3737

3838
final class FormPersistenceManagerTest extends FunctionalTestCase
3939
{
40-
protected bool $initializeDatabase = false;
41-
4240
protected array $coreExtensionsToLoad = [
4341
'form',
4442
];
4543

44+
protected array $pathsToProvideInTestInstance = [
45+
'typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactForm.form.yaml' => 'fileadmin/form_definitions/ContactForm.form.yaml',
46+
'typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactFormWithoutType.yaml' => 'fileadmin/form_definitions/ContactFormWithoutType.yaml',
47+
'typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactFormWithType.yaml' => 'fileadmin/form_definitions/ContactFormWithType.yaml',
48+
];
49+
4650
#[Test]
4751
public function loadThrowsExceptionIfPersistenceIdentifierHasNoYamlExtension(): void
4852
{
@@ -92,6 +96,7 @@ public function loadReturnsFormArray(): void
9296
];
9397
$expected = [
9498
'identifier' => 'ext-form-identifier',
99+
'type' => 'Form',
95100
'prototypeName' => 'standard',
96101
'label' => 'Label',
97102
'renderables' => [
@@ -140,6 +145,7 @@ public function loadReturnsOverriddenConfigurationIfTypoScriptOverridesExists():
140145
];
141146
$expected = [
142147
'identifier' => 'ext-form-identifier',
148+
'type' => 'Form',
143149
'prototypeName' => 'standard',
144150
'label' => 'Label override',
145151
'renderables' => [
@@ -190,6 +196,7 @@ public function overrideFormDefinitionDoesNotEvaluateTypoScriptLookalikeInstruct
190196
];
191197
$formDefinitionYaml = [
192198
'identifier' => 'ext-form-identifier',
199+
'type' => 'Form',
193200
'prototypeName' => 'standard',
194201
'label' => [
195202
'value' => 'Label override',
@@ -212,6 +219,7 @@ public function overrideFormDefinitionDoesNotEvaluateTypoScriptLookalikeInstruct
212219
];
213220
$expected = [
214221
'identifier' => 'ext-form-identifier',
222+
'type' => 'Form',
215223
'prototypeName' => 'standard',
216224
'label' => [
217225
'value' => 'Label override',
@@ -1032,4 +1040,20 @@ public function isAllowedPersistencePathReturnsProperValues(string $persistenceP
10321040
$subjectMock->method('getStorageByUid')->willReturn($storageMock);
10331041
self::assertEquals($expected, $subjectMock->isAllowedPersistencePath($persistencePath, $formSettings));
10341042
}
1043+
1044+
public static function loadThrowsExceptionWhenFormDefinitionHasInvalidFileExtensionDataProvider(): iterable
1045+
{
1046+
yield ['1:/form_definitions/ContactForm.form.yaml', null];
1047+
yield ['1:/form_definitions/ContactFormWithType.yaml', true];
1048+
yield ['1:/form_definitions/ContactFormWithoutType.yaml', true];
1049+
}
1050+
1051+
#[Test]
1052+
#[DataProvider('loadThrowsExceptionWhenFormDefinitionHasInvalidFileExtensionDataProvider')]
1053+
public function loadThrowsExceptionWhenFormDefinitionHasInvalidFileExtension(string $identifier, ?bool $expectedInvalid): void
1054+
{
1055+
$subject = $this->get(FormPersistenceManager::class);
1056+
$formDefinition = $subject->load($identifier, [], []);
1057+
self::assertSame($expectedInvalid, $formDefinition['invalid'] ?? null);
1058+
}
10351059
}

0 commit comments

Comments
 (0)