diff --git a/ajde/src/main/java/org/aspectj/ajde/internal/LstBuildConfigFileParser.java b/ajde/src/main/java/org/aspectj/ajde/internal/LstBuildConfigFileParser.java index 88b7bec47a..21a43734d8 100644 --- a/ajde/src/main/java/org/aspectj/ajde/internal/LstBuildConfigFileParser.java +++ b/ajde/src/main/java/org/aspectj/ajde/internal/LstBuildConfigFileParser.java @@ -14,6 +14,7 @@ package org.aspectj.ajde.internal; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -24,13 +25,20 @@ */ public class LstBuildConfigFileParser extends ConfigParser { + private final File topLevelConfigDirectory; private List importedFiles = new ArrayList<>(); private List problemEntries = new ArrayList<>(); // private String currFilePath; public LstBuildConfigFileParser(String currFilePath) { - // this.currFilePath = currFilePath; + File canonicalConfigFile; + try { + canonicalConfigFile = new File(currFilePath).getCanonicalFile(); + } catch (IOException e) { + canonicalConfigFile = new File(currFilePath).getAbsoluteFile(); + } + this.topLevelConfigDirectory = canonicalConfigFile.getParentFile(); } protected void showWarning(String message) { @@ -38,11 +46,35 @@ protected void showWarning(String message) { } protected void parseImportedConfigFile(String relativeFilePath) { - importedFiles.add(makeFile(relativeFilePath)); - super.files.add(new File(relativeFilePath)); + File importedFile = makeFile(relativeFilePath); + if (!isSafeImportedConfigFile(importedFile)) { + showError("imported config file outside project root directory: " + relativeFilePath); + return; + } + importedFiles.add(importedFile); + super.files.add(importedFile); super.parseImportedConfigFile(relativeFilePath); } + private boolean isSafeImportedConfigFile(File importedFile) { + if (topLevelConfigDirectory == null) { + return false; + } + try { + File safeRoot = topLevelConfigDirectory.getCanonicalFile(); + File candidate = importedFile.getCanonicalFile(); + while (candidate != null) { + if (safeRoot.equals(candidate)) { + return true; + } + candidate = candidate.getParentFile(); + } + } catch (IOException e) { + // If canonicalization fails, fall back to rejecting the import. + } + return false; + } + protected void showError(String message) { problemEntries.add(message); } diff --git a/ajde/src/test/java/org/aspectj/ajde/internal/LstBuildConfigManagerTest.java b/ajde/src/test/java/org/aspectj/ajde/internal/LstBuildConfigManagerTest.java index 280c11e771..3df321bc2c 100644 --- a/ajde/src/test/java/org/aspectj/ajde/internal/LstBuildConfigManagerTest.java +++ b/ajde/src/test/java/org/aspectj/ajde/internal/LstBuildConfigManagerTest.java @@ -15,6 +15,7 @@ package org.aspectj.ajde.internal; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.util.List; @@ -76,5 +77,26 @@ public void testFileRelativePathSameDir() throws IOException { assertTrue("single file", true); } + public void testRejectImportedConfigFileOutsideProjectRoot() throws IOException { + File outsideConfig = new File(getWorkingDir(), "outside.lst"); + try (FileOutputStream out = new FileOutputStream(outsideConfig)) { + out.write("A.java\n".getBytes("UTF-8")); + } + + File configFile = openFile("path-traversal.lst"); + try (FileOutputStream out = new FileOutputStream(configFile)) { + out.write("@../outside.lst\n".getBytes("UTF-8")); + } + + BuildConfigModel model = buildConfigManager.buildModel(configFile.getCanonicalPath()); + assertNotNull("expected model root to be created", model.getRoot()); + + List messages = getErrorMessages("path-traversal.lst"); + assertFalse("expected an error message for imports outside project root", messages.isEmpty()); + + TestMessage message = (TestMessage) messages.get(0); + assertTrue(message.getContainedMessage().getMessage(), message.getContainedMessage().getMessage().contains("outside project root directory")); + } + }