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
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 1ac6aff..4fb88d1 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -678,8 +678,14 @@ private Map<String, String> getEnhancedProperties(Model model, Path rootDirector
if (!Objects.equals(rootDirectory, model.getProjectDirectory())) {
Path rootModelPath = modelProcessor.locateExistingPom(rootDirectory);
if (rootModelPath != null) {
- Model rootModel = derive(Sources.buildSource(rootModelPath)).readFileModel();
- properties.putAll(getPropertiesWithProfiles(rootModel, properties));
+ // Check if the root model path is within the root directory to prevent infinite loops
+ // This can happen when a .mvn directory exists in a subdirectory and parent inference
+ // tries to read models above the discovered root directory
+ if (isParentWithinRootDirectory(rootModelPath, rootDirectory)) {
+ Model rootModel =
+ derive(Sources.buildSource(rootModelPath)).readFileModel();
+ properties.putAll(getPropertiesWithProfiles(rootModel, properties));
+ }
}
} else {
properties.putAll(getPropertiesWithProfiles(model, properties));
@@ -1561,6 +1567,18 @@ Model doReadFileModel() throws ModelBuilderException {
pomPath = modelProcessor.locateExistingPom(pomPath);
}
if (pomPath != null && Files.isRegularFile(pomPath)) {
+ // Check if parent POM is above the root directory
+ if (!isParentWithinRootDirectory(pomPath, rootDirectory)) {
+ add(
+ Severity.FATAL,
+ Version.BASE,
+ "Parent POM " + pomPath + " is located above the root directory "
+ + rootDirectory
+ + ". This setup is invalid when a .mvn directory exists in a subdirectory.",
+ parent.getLocation("relativePath"));
+ throw newModelBuilderException();
+ }
+
Model parentModel =
derive(Sources.buildSource(pomPath)).readFileModel();
String parentGroupId = getGroupId(parentModel);
@@ -2588,4 +2606,29 @@ private static <T, A> List<T> map(List<T> resources, BiFunction<T, A, T> mapper,
}
return newResources;
}
+
+ /**
+ * Checks if the parent POM path is within the root directory.
+ * This prevents invalid setups where a parent POM is located above the root directory.
+ *
+ * @param parentPath the path to the parent POM
+ * @param rootDirectory the root directory
+ * @return true if the parent is within the root directory, false otherwise
+ */
+ private static boolean isParentWithinRootDirectory(Path parentPath, Path rootDirectory) {
+ if (parentPath == null || rootDirectory == null) {
+ return true; // Allow if either is null (fallback behavior)
+ }
+
+ try {
+ Path normalizedParent = parentPath.toAbsolutePath().normalize();
+ Path normalizedRoot = rootDirectory.toAbsolutePath().normalize();
+
+ // Check if the parent path starts with the root directory path
+ return normalizedParent.startsWith(normalizedRoot);
+ } catch (Exception e) {
+ // If there's any issue with path resolution, allow it (fallback behavior)
+ return true;
+ }
+ }
}
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11321Test.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11321Test.java
new file mode 100644
index 0000000..60dbb49
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11321Test.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.it;
+
+import java.io.File;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * This is a test set for <a href="https://github.com/apache/maven/issues/11321">GH-11321</a>.
+ * Verify that Maven properly rejects setups where a parent POM is located above the root directory
+ * when a .mvn directory exists in a subdirectory and Maven is invoked with -f pointing to that subdirectory.
+ *
+ * @since 4.0.0
+ */
+public class MavenITgh11321Test extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verify that Maven properly rejects setups where a parent POM is located above the root directory.
+ * When Maven is invoked with -f deps/ where deps contains a .mvn directory, and the deps/pom.xml
+ * uses parent inference to find a parent above the root directory, it should fail with a proper error message.
+ *
+ * @throws Exception in case of failure
+ */
+ @Test
+ public void testParentAboveRootDirectoryRejected() throws Exception {
+ File testDir = extractResources("/gh-11321-parent-above-root");
+
+ // First, verify that normal build works from the actual root
+ Verifier verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument("validate");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ // Now test with -f pointing to the subdirectory that contains .mvn
+ // This should fail with a proper error message about parent being above root
+ verifier = newVerifier(testDir.getAbsolutePath());
+ verifier.addCliArgument("-f");
+ verifier.addCliArgument("deps");
+ verifier.addCliArgument("validate");
+ assertThrows(
+ VerificationException.class,
+ verifier::execute,
+ "Expected validation to fail when using invalid project structure");
+ verifier.verifyTextInLog("Parent POM");
+ verifier.verifyTextInLog("is located above the root directory");
+ verifier.verifyTextInLog("This setup is invalid when a .mvn directory exists in a subdirectory");
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/deps/.mvn/extensions.xml b/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/deps/.mvn/extensions.xml
new file mode 100644
index 0000000..91012b3
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/deps/.mvn/extensions.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 https://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
+ <!-- Empty extensions file to make .mvn directory a valid root marker -->
+</extensions>
diff --git a/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/deps/pom.xml b/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/deps/pom.xml
new file mode 100644
index 0000000..1a2ad35
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/deps/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <parent />
+ <artifactId>deps</artifactId>
+ <packaging>pom</packaging>
+
+ <name>Maven Integration Test :: gh-11321 :: Deps Module</name>
+ <description>Module with .mvn directory that uses parent inference</description>
+</project>
diff --git a/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/pom.xml b/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/pom.xml
new file mode 100644
index 0000000..8679dad
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11321-parent-above-root/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0" root="true">
+ <groupId>org.apache.maven.its.gh11321</groupId>
+ <artifactId>parent-above-root</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <name>Maven Integration Test :: gh-11321 :: Parent Above Root</name>
+ <description>Test that Maven rejects setups where parent POM is above root directory</description>
+
+ <properties>
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>