mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-04 00:09:01 +00:00
Handle root output directory when extracting layers
Use canonical Path containment checks so layer directories under the filesystem root are accepted while entries and layer names that escape the output directory remain rejected. See gh-50501 Signed-off-by: Dongliang Xie <dragonfsky@gmail.com>
This commit is contained in:
committed by
Moritz Halbritter
parent
bfd1b8c096
commit
f7343398d4
+12
-6
@@ -26,6 +26,7 @@ import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
@@ -55,6 +56,7 @@ import org.springframework.util.StringUtils;
|
||||
* The {@code 'extract'} tools command.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Dongliang Xie
|
||||
*/
|
||||
class ExtractCommand extends Command {
|
||||
|
||||
@@ -363,14 +365,18 @@ class ExtractCommand extends Command {
|
||||
}
|
||||
|
||||
private static File assertFileIsContainedInDirectory(File directory, File file, String name) throws IOException {
|
||||
String canonicalOutputPath = directory.getCanonicalPath() + File.separator;
|
||||
String canonicalEntryPath = file.getCanonicalPath();
|
||||
Assert.state(canonicalEntryPath.startsWith(canonicalOutputPath),
|
||||
Path canonicalOutputPath = directory.getCanonicalFile().toPath();
|
||||
Path canonicalEntryPath = file.getCanonicalFile().toPath();
|
||||
Assert.state(isFileContainedInDirectory(canonicalOutputPath, canonicalEntryPath),
|
||||
() -> "Entry '%s' would be written to '%s'. This is outside the output location of '%s'. Verify the contents of your archive."
|
||||
.formatted(name, canonicalEntryPath, canonicalOutputPath));
|
||||
return file;
|
||||
}
|
||||
|
||||
private static boolean isFileContainedInDirectory(Path canonicalOutputPath, Path canonicalFilePath) {
|
||||
return !canonicalFilePath.equals(canonicalOutputPath) && canonicalFilePath.startsWith(canonicalOutputPath);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface EntryNameTransformer {
|
||||
|
||||
@@ -515,9 +521,9 @@ class ExtractCommand extends Command {
|
||||
}
|
||||
|
||||
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),
|
||||
Path canonicalOutputPath = this.directory.getCanonicalFile().toPath();
|
||||
Path canonicalLayerPath = layerDirectory.getCanonicalFile().toPath();
|
||||
Assert.state(isFileContainedInDirectory(canonicalOutputPath, canonicalLayerPath),
|
||||
() -> "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;
|
||||
|
||||
+35
@@ -20,11 +20,13 @@ import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Manifest;
|
||||
@@ -45,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* Tests for {@link ExtractCommand}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Dongliang Xie
|
||||
*/
|
||||
class ExtractCommandTests extends AbstractJarModeTests {
|
||||
|
||||
@@ -370,6 +373,38 @@ class ExtractCommandTests extends AbstractJarModeTests {
|
||||
.doesNotContain("test/spring-boot-loader/org/springframework/boot/loader/launch/JarLauncher.class");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractWhenDestinationIsFileSystemRoot() throws IOException {
|
||||
Path layerDirectory = ExtractCommandTests.this.tempDir.toPath()
|
||||
.resolve("root-output")
|
||||
.resolve("dependencies")
|
||||
.toAbsolutePath()
|
||||
.normalize();
|
||||
Path outputRoot = layerDirectory.getRoot();
|
||||
String layerName = outputRoot.relativize(layerDirectory).toString().replace(File.separatorChar, '/');
|
||||
Layers layers = new Layers() {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return List.of(layerName).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLayer(String entryName) {
|
||||
return layerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApplicationLayerName() {
|
||||
return layerName;
|
||||
}
|
||||
|
||||
};
|
||||
runCommand((context) -> new ExtractCommand(context, layers), ExtractCommandTests.this.archive,
|
||||
"--destination", outputRoot.toString(), "--force", "--launcher", "--layers");
|
||||
assertThat(layerDirectory.resolve("BOOT-INF/lib/dependency-1.jar")).exists();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user