From df6f1bac73a390f86b537babeb9eca34f70cc32f Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sun, 19 Jul 2026 10:47:02 +0900 Subject: [PATCH] Android: Prevent ZIP path traversal in launcher --- .../java/io/github/kichikuou/system3/Launcher.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/io/github/kichikuou/system3/Launcher.kt b/android/app/src/main/java/io/github/kichikuou/system3/Launcher.kt index d0eaf99..6412142 100644 --- a/android/app/src/main/java/io/github/kichikuou/system3/Launcher.kt +++ b/android/app/src/main/java/io/github/kichikuou/system3/Launcher.kt @@ -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"))