Skip to content

Commit 1673ce1

Browse files
committed
Prevent infinite loop in RootLocator when .mvn directory exists in subdirectory (fixes apache#11321) (apache#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 (cherry picked from commit 714fc51)
1 parent 1c7f779 commit 1673ce1

5 files changed

Lines changed: 196 additions & 2 deletions

File tree

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
@@ -667,8 +667,14 @@ private Map<String, String> getEnhancedProperties(Model model, Path rootDirector
667667
if (!Objects.equals(rootDirectory, model.getProjectDirectory())) {
668668
Path rootModelPath = modelProcessor.locateExistingPom(rootDirectory);
669669
if (rootModelPath != null) {
670-
Model rootModel = derive(Sources.buildSource(rootModelPath)).readFileModel();
671-
properties.putAll(getPropertiesWithProfiles(rootModel, properties));
670+
// Check if the root model path is within the root directory to prevent infinite loops
671+
// This can happen when a .mvn directory exists in a subdirectory and parent inference
672+
// tries to read models above the discovered root directory
673+
if (isParentWithinRootDirectory(rootModelPath, rootDirectory)) {
674+
Model rootModel =
675+
derive(Sources.buildSource(rootModelPath)).readFileModel();
676+
properties.putAll(getPropertiesWithProfiles(rootModel, properties));
677+
}
672678
}
673679
} else {
674680
properties.putAll(getPropertiesWithProfiles(model, properties));
@@ -1534,6 +1540,18 @@ Model doReadFileModel() throws ModelBuilderException {
15341540
pomPath = modelProcessor.locateExistingPom(pomPath);
15351541
}
15361542
if (pomPath != null && Files.isRegularFile(pomPath)) {
1543+
// Check if parent POM is above the root directory
1544+
if (!isParentWithinRootDirectory(pomPath, rootDirectory)) {
1545+
add(
1546+
Severity.FATAL,
1547+
Version.BASE,
1548+
"Parent POM " + pomPath + " is located above the root directory "
1549+
+ rootDirectory
1550+
+ ". This setup is invalid when a .mvn directory exists in a subdirectory.",
1551+
parent.getLocation("relativePath"));
1552+
throw newModelBuilderException();
1553+
}
1554+
15371555
Model parentModel =
15381556
derive(Sources.buildSource(pomPath)).readFileModel();
15391557
String parentGroupId = getGroupId(parentModel);
@@ -2497,4 +2515,29 @@ private static <T, A> List<T> map(List<T> resources, BiFunction<T, A, T> mapper,
24972515
}
24982516
return newResources;
24992517
}
2518+
2519+
/**
2520+
* Checks if the parent POM path is within the root directory.
2521+
* This prevents invalid setups where a parent POM is located above the root directory.
2522+
*
2523+
* @param parentPath the path to the parent POM
2524+
* @param rootDirectory the root directory
2525+
* @return true if the parent is within the root directory, false otherwise
2526+
*/
2527+
private static boolean isParentWithinRootDirectory(Path parentPath, Path rootDirectory) {
2528+
if (parentPath == null || rootDirectory == null) {
2529+
return true; // Allow if either is null (fallback behavior)
2530+
}
2531+
2532+
try {
2533+
Path normalizedParent = parentPath.toAbsolutePath().normalize();
2534+
Path normalizedRoot = rootDirectory.toAbsolutePath().normalize();
2535+
2536+
// Check if the parent path starts with the root directory path
2537+
return normalizedParent.startsWith(normalizedRoot);
2538+
} catch (Exception e) {
2539+
// If there's any issue with path resolution, allow it (fallback behavior)
2540+
return true;
2541+
}
2542+
}
25002543
}
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)