diff --git a/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java b/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java index 59440ae2f46..41fc37b57a0 100644 --- a/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java +++ b/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java @@ -489,8 +489,8 @@ class ExtractCommand extends Command { public @Nullable File resolve(String originalName, String newName) throws IOException { String layer = this.layers.getLayer(originalName); if (shouldExtractLayer(layer)) { - File directory = getLayerDirectory(layer); - return assertFileIsContainedInDirectory(directory, new File(directory, newName), newName); + File layerDirectory = getLayerDirectory(layer); + return assertFileIsContainedInDirectory(layerDirectory, new File(layerDirectory, newName), newName); } return null; } @@ -499,21 +499,30 @@ class ExtractCommand extends Command { public @Nullable File resolveApplication() throws IOException { String layer = this.layers.getApplicationLayerName(); if (shouldExtractLayer(layer)) { - File directory = getLayerDirectory(layer); - return assertFileIsContainedInDirectory(directory, new File(directory, this.applicationFilename), - this.applicationFilename); + File layerDirectory = getLayerDirectory(layer); + return assertFileIsContainedInDirectory(layerDirectory, + new File(layerDirectory, this.applicationFilename), this.applicationFilename); } return null; } - private File getLayerDirectory(String layer) { - return new File(this.directory, layer); + private File getLayerDirectory(String layer) throws IOException { + return assertLayerDirectoryLocation(new File(this.directory, layer), layer); } private boolean shouldExtractLayer(String layer) { return this.layersToExtract.isEmpty() || this.layersToExtract.contains(layer); } + private File assertLayerDirectoryLocation(File layerDirectory, String layerName) throws IOException { + String canonicalOutputPath = this.directory.getCanonicalPath() + File.separator; + String canonicalLayerPath = layerDirectory.getCanonicalPath(); + Assert.state(canonicalLayerPath.startsWith(canonicalOutputPath), + () -> "Layer '%s' would be written to '%s'. This is outside the output location of '%s'. Verify the contents of your archive." + .formatted(layerName, canonicalLayerPath, canonicalOutputPath)); + return layerDirectory; + } + } } diff --git a/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractLayersCommandTests.java b/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractLayersCommandTests.java index b15eaace46c..4939a8a800a 100644 --- a/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractLayersCommandTests.java +++ b/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractLayersCommandTests.java @@ -174,6 +174,34 @@ class ExtractLayersCommandTests { .withMessageContaining("Entry 'e/../../e.jar' would be written"); } + @Test + void runWithLayerNameThatWouldWriteEntriesOutsideDestinationFails() throws Exception { + this.command = new ExtractLayersCommand(this.context, new Layers() { + + @Override + public Iterator iterator() { + return Arrays.asList("a", "b", "c").iterator(); + } + + @Override + public String getLayer(String entryName) { + return "../../outside-layer"; + } + + @Override + public String getApplicationLayerName() { + return "application"; + } + + }); + this.jarFile = createJarFile("test.jar"); + given(this.context.getArchiveFile()).willReturn(this.jarFile); + given(this.context.getWorkingDir()).willReturn(this.extract); + assertThatIllegalStateException() + .isThrownBy(() -> this.command.run(System.out, Collections.emptyMap(), Collections.emptyList())) + .withMessageContaining("Layer '../../outside-layer' would be written to"); + } + private File createJarFile(String name) throws Exception { return createJarFile(name, (out) -> { });