Skip to content

Commit 714fc51

Browse files
authored
Prevent infinite loop in RootLocator when .mvn directory exists in subdirectory (fixes #11321) (#11323)
This is a fix that adds validation to prevent reading parent POMs that are located above the discovered root directory. This prevents infinite loops when a .mvn directory exists in a subdirectory and Maven is invoked with -f pointing to that subdirectory. The fix includes: - Validation in doReadFileModel() to check parent POM location - Validation in getEnhancedProperties() to prevent infinite loops - Helper method isParentWithinRootDirectory() for path validation - Integration test to reproduce and verify the fix
1 parent cb5ee55 commit 714fc51

File tree

5 files changed

+196
-2
lines changed

5 files changed

+196
-2
lines changed

impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -678,8 +678,14 @@ private Map<String, String> getEnhancedProperties(Model model, Path rootDirector
678678
if (!Objects.equals(rootDirectory, model.getProjectDirectory())) {
679679
Path rootModelPath = modelProcessor.locateExistingPom(rootDirectory);
680680
if (rootModelPath != null) {
681-
Model rootModel = derive(Sources.buildSource(rootModelPath)).readFileModel();
682-
properties.putAll(getPropertiesWithProfiles(rootModel, properties));
681+
// Check if the root model path is within the root directory to prevent infinite loops
682+
// This can happen when a .mvn directory exists in a subdirectory and parent inference
683+
// tries to read models above the discovered root directory
684+
if (isParentWithinRootDirectory(rootModelPath, rootDirectory)) {
685+
Model rootModel =
686+
derive(Sources.buildSource(rootModelPath)).readFileModel();
687+
properties.putAll(getPropertiesWithProfiles(rootModel, properties));
688+
}
683689
}
684690
} else {
685691
properties.putAll(getPropertiesWithProfiles(model, properties));
@@ -1561,6 +1567,18 @@ Model doReadFileModel() throws ModelBuilderException {
15611567
pomPath = modelProcessor.locateExistingPom(pomPath);
15621568
}
15631569
if (pomPath != null && Files.isRegularFile(pomPath)) {
1570+
// Check if parent POM is above the root directory
1571+
if (!isParentWithinRootDirectory(pomPath, rootDirectory)) {
1572+
add(
1573+
Severity.FATAL,
1574+
Version.BASE,
1575+
"Parent POM " + pomPath + " is located above the root directory "
1576+
+ rootDirectory
1577+
+ ". This setup is invalid when a .mvn directory exists in a subdirectory.",
1578+
parent.getLocation("relativePath"));
1579+
throw newModelBuilderException();
1580+
}
1581+
15641582
Model parentModel =
15651583
derive(Sources.buildSource(pomPath)).readFileModel();
15661584
String parentGroupId = getGroupId(parentModel);
@@ -2588,4 +2606,29 @@ private static <T, A> List<T> map(List<T> resources, BiFunction<T, A, T> mapper,
25882606
}
25892607
return newResources;
25902608
}
2609+
2610+
/**
2611+
* Checks if the parent POM path is within the root directory.
2612+
* This prevents invalid setups where a parent POM is located above the root directory.
2613+
*
2614+
* @param parentPath the path to the parent POM
2615+
* @param rootDirectory the root directory
2616+
* @return true if the parent is within the root directory, false otherwise
2617+
*/
2618+
private static boolean isParentWithinRootDirectory(Path parentPath, Path rootDirectory) {
2619+
if (parentPath == null || rootDirectory == null) {
2620+
return true; // Allow if either is null (fallback behavior)
2621+
}
2622+
2623+
try {
2624+
Path normalizedParent = parentPath.toAbsolutePath().normalize();
2625+
Path normalizedRoot = rootDirectory.toAbsolutePath().normalize();
2626+
2627+
// Check if the parent path starts with the root directory path
2628+
return normalizedParent.startsWith(normalizedRoot);
2629+
} catch (Exception e) {
2630+
// If there's any issue with path resolution, allow it (fallback behavior)
2631+
return true;
2632+
}
2633+
}
25912634
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.it;
20+
21+
import java.io.File;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
27+
/**
28+
* This is a test set for <a href="https://github.com/apache/maven/issues/11321">GH-11321</a>.
29+
* Verify that Maven properly rejects setups where a parent POM is located above the root directory
30+
* when a .mvn directory exists in a subdirectory and Maven is invoked with -f pointing to that subdirectory.
31+
*
32+
* @since 4.0.0
33+
*/
34+
public class MavenITgh11321Test extends AbstractMavenIntegrationTestCase {
35+
36+
/**
37+
* Verify that Maven properly rejects setups where a parent POM is located above the root directory.
38+
* When Maven is invoked with -f deps/ where deps contains a .mvn directory, and the deps/pom.xml
39+
* uses parent inference to find a parent above the root directory, it should fail with a proper error message.
40+
*
41+
* @throws Exception in case of failure
42+
*/
43+
@Test
44+
public void testParentAboveRootDirectoryRejected() throws Exception {
45+
File testDir = extractResources("/gh-11321-parent-above-root");
46+
47+
// First, verify that normal build works from the actual root
48+
Verifier verifier = newVerifier(testDir.getAbsolutePath());
49+
verifier.addCliArgument("validate");
50+
verifier.execute();
51+
verifier.verifyErrorFreeLog();
52+
53+
// Now test with -f pointing to the subdirectory that contains .mvn
54+
// This should fail with a proper error message about parent being above root
55+
verifier = newVerifier(testDir.getAbsolutePath());
56+
verifier.addCliArgument("-f");
57+
verifier.addCliArgument("deps");
58+
verifier.addCliArgument("validate");
59+
assertThrows(
60+
VerificationException.class,
61+
verifier::execute,
62+
"Expected validation to fail when using invalid project structure");
63+
verifier.verifyTextInLog("Parent POM");
64+
verifier.verifyTextInLog("is located above the root directory");
65+
verifier.verifyTextInLog("This setup is invalid when a .mvn directory exists in a subdirectory");
66+
}
67+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 https://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
22+
<!-- Empty extensions file to make .mvn directory a valid root marker -->
23+
</extensions>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
<project xmlns="http://maven.apache.org/POM/4.1.0">
21+
<parent />
22+
<artifactId>deps</artifactId>
23+
<packaging>pom</packaging>
24+
25+
<name>Maven Integration Test :: gh-11321 :: Deps Module</name>
26+
<description>Module with .mvn directory that uses parent inference</description>
27+
</project>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
<project xmlns="http://maven.apache.org/POM/4.1.0" root="true">
21+
<groupId>org.apache.maven.its.gh11321</groupId>
22+
<artifactId>parent-above-root</artifactId>
23+
<version>1.0-SNAPSHOT</version>
24+
<packaging>pom</packaging>
25+
26+
<name>Maven Integration Test :: gh-11321 :: Parent Above Root</name>
27+
<description>Test that Maven rejects setups where parent POM is above root directory</description>
28+
29+
<properties>
30+
<maven.compiler.source>11</maven.compiler.source>
31+
<maven.compiler.target>11</maven.compiler.target>
32+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
33+
</properties>
34+
</project>

0 commit comments

Comments
 (0)