Android: Prevent ZIP path traversal in launcher

This commit is contained in:
kichikuou
2026-07-19 10:47:02 +09:00
parent efd00575f9
commit df6f1bac73
@@ -162,8 +162,8 @@ class Launcher private constructor(private val rootDir: File) {
// Process only files under save/
if (zipEntry.isDirectory || !zipEntry.name.startsWith("save/"))
return@forEachZipEntry
val path = resolveOutputPath(saveDir, zipEntry.name.removePrefix("save/"))
Log.i("importSaveData", zipEntry.name)
val path = File(rootDir, zipEntry.name)
path.parentFile?.mkdirs()
FileOutputStream(path).buffered().use {
zip.copyTo(it)
@@ -188,7 +188,7 @@ class Launcher private constructor(private val rootDir: File) {
if (zipEntry.isDirectory)
return@forEachZipEntry
progressCallback(entryName)
FileOutputStream(File(outDir, entryName)).buffered().use {
FileOutputStream(resolveOutputPath(outDir, entryName)).buffered().use {
zip.copyTo(it)
}
configWriter.maybeAdd(entryName)
@@ -227,6 +227,15 @@ class Launcher private constructor(private val rootDir: File) {
}
}
private fun resolveOutputPath(baseDir: File, relativePath: String): File {
val canonicalBase = baseDir.canonicalFile
val file = File(canonicalBase, relativePath).canonicalFile
if (!file.path.startsWith(canonicalBase.path + File.separator)) {
throw IOException("Output path is outside target directory: $relativePath")
}
return file
}
private fun forEachZipEntry(input: InputStream, action: (ZipEntry, ZipInputStream) -> Unit) {
val zip = if (Build.VERSION.SDK_INT >= 24) {
ZipInputStream(input.buffered(), Charset.forName("Shift_JIS"))