[klib] Validate paths when unpacking zip archives

This commit is contained in:
Sergej Jaskiewicz
2022-11-21 15:44:45 +01:00
committed by Space Team
parent 898626d055
commit d50c072af0
5 changed files with 146 additions and 42 deletions
@@ -13,9 +13,7 @@ import java.io.RandomAccessFile
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributeView
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.attribute.FileTime
data class File constructor(internal val javaPath: Path) {
constructor(parent: Path, child: String): this(parent.resolve(child))
@@ -64,12 +62,6 @@ data class File constructor(internal val javaPath: Path) {
Files.copy(javaPath, destination.javaPath, StandardCopyOption.REPLACE_EXISTING)
}
fun recursiveCopyTo(destination: File, resetTimeAttributes: Boolean = false) {
val sourcePath = javaPath
val destPath = destination.javaPath
sourcePath.recursiveCopyTo(destPath, resetTimeAttributes = resetTimeAttributes)
}
fun renameTo(destination: File) = javaPath.toFile().renameTo(destination.javaPath.toFile())
fun mkdirs() = Files.createDirectories(javaPath)
@@ -198,30 +190,6 @@ fun createTempFile(name: String, suffix: String? = null)
fun createTempDir(name: String): File
= Files.createTempDirectory(name).File()
fun Path.recursiveCopyTo(destPath: Path, resetTimeAttributes: Boolean = false) {
val sourcePath = this
Files.walk(sourcePath).forEach next@ { oldPath ->
val relative = sourcePath.relativize(oldPath)
val destFs = destPath.getFileSystem()
// We are copying files between file systems,
// so pass the relative path through the String.
val newPath = destFs.getPath(destPath.toString(), relative.toString())
// File systems don't allow replacing an existing root.
if (newPath == newPath.getRoot()) return@next
if (Files.isDirectory(newPath)) {
Files.createDirectories(newPath)
} else {
Files.copy(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING)
}
if (resetTimeAttributes) {
val zero = FileTime.fromMillis(0)
Files.getFileAttributeView(newPath, BasicFileAttributeView::class.java).setTimes(zero, zero, zero);
}
}
}
fun bufferedReader(errorStream: InputStream?) = BufferedReader(InputStreamReader(errorStream))
// stdlib `use` function adapted for AutoCloseable.
@@ -241,4 +209,4 @@ inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R {
this?.close()
}
}
}
}
@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.konan.file
import java.net.URI
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributeView
import java.nio.file.attribute.FileTime
import java.nio.file.spi.FileSystemProvider
import java.util.zip.ZipException
// Zip filesystem provider doesn't allow creating several instances of ZipFileSystem from the same URI,
// so newFileSystem(URI, ...) throws a FileSystemAlreadyExistsException in this case.
@@ -15,7 +17,7 @@ import java.nio.file.spi.FileSystemProvider
// See also:
// https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7001822
// https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6994161
fun File.zipFileSystem(create: Boolean = false): FileSystem {
private fun File.zipFileSystem(create: Boolean = false): FileSystem {
val attributes = hashMapOf("create" to create.toString())
// There is no FileSystems.newFileSystem overload accepting the attribute map.
@@ -47,16 +49,67 @@ fun File.zipDirAs(unixFile: File) {
}
}
fun Path.unzipTo(directory: Path) {
val zipUri = URI.create("jar:" + this.toUri())
FileSystems.newFileSystem(zipUri, emptyMap<String, Any?>(), null).use { zipfs ->
val zipPath = zipfs.getPath("/")
zipPath.recursiveCopyTo(directory)
/**
* Unpacks the contents of a zip archive located in [this] into the [destinationDirectory].
*
* @param destinationDirectory The directory to unpack the contents to.
* @param resetTimeAttributes Whether to set the newly created files' time attributes
* (creation time, last access time, and last modification time) to zero.
* @param fromSubdirectory A subdirectory inside the archive to unpack. Specify "/" if you need to unpack the whole archive.
*/
fun File.unzipTo(destinationDirectory: File, fromSubdirectory: File = File("/"), resetTimeAttributes: Boolean = false) {
withZipFileSystem {
it.file(fromSubdirectory).recursiveCopyTo(destinationDirectory, resetTimeAttributes)
}
}
/**
* Unpacks the contents of a zip archive located in [this] into the [destinationDirectory].
*
* @param destinationDirectory The directory to unpack the contents to.
* @param resetTimeAttributes Whether to set the newly created files' time attributes
* (creation time, last access time, and last modification time) to zero.
* @param fromSubdirectory A subdirectory inside the archive to unpack. Specify "/" if you need to unpack the whole archive.
*/
fun Path.unzipTo(destinationDirectory: Path, fromSubdirectory: Path = Paths.get("/"), resetTimeAttributes: Boolean = false) {
File(this).unzipTo(File(destinationDirectory), File(fromSubdirectory), resetTimeAttributes)
}
fun <T> File.withZipFileSystem(create: Boolean, action: (FileSystem) -> T): T {
return this.zipFileSystem(create).use(action)
}
fun <T> File.withZipFileSystem(action: (FileSystem) -> T): T = this.withZipFileSystem(false, action)
// TODO: Make this function private after boostrap advance
fun File.recursiveCopyTo(destination: File, resetTimeAttributes: Boolean = false) {
val sourcePath = javaPath
val destPath = destination.javaPath
val destFs = destPath.fileSystem
val normalizedDestPath = destPath.normalize()
Files.walk(sourcePath).forEach next@{ oldPath ->
val relative = sourcePath.relativize(oldPath)
// We are copying files between file systems,
// so pass the relative path through the String.
val newPath = destFs.getPath(destPath.toString(), relative.toString())
// NOTE: this check is important, it prevents a potential ZipSlip vulnerability
if (!newPath.normalize().startsWith(normalizedDestPath)) {
throw ZipException("$relative attempted to escape the destination directory $destination")
}
// File systems don't allow replacing an existing root.
if (newPath == newPath.root) return@next
if (Files.isDirectory(newPath)) {
Files.createDirectories(newPath)
} else {
Files.copy(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING)
}
if (resetTimeAttributes) {
val zero = FileTime.fromMillis(0)
Files.getFileAttributeView(newPath, BasicFileAttributeView::class.java).setTimes(zero, zero, zero);
}
}
}