Skip to content

Commit 9e827e7

Browse files
committed
Additional test cases for quality gates to tar.bz2, tar.xz and tar.lzma archives
1 parent fde039a commit 9e827e7

8 files changed

Lines changed: 194 additions & 0 deletions

File tree

app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/SevenZipHelperCallableMaliciousTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ import org.junit.Assert.assertFalse
2525
import org.junit.Test
2626
import java.io.File
2727

28+
/**
29+
* Tests for [SevenZipHelperCallable].
30+
*/
2831
class SevenZipHelperCallableMaliciousTest : AbstractCompressedHelperCallableTest() {
32+
33+
/**
34+
* Test to ensure that the root of the archive does not expose parent folder entries
35+
* (e.g., ".." or "../").
36+
*/
2937
@Test
3038
fun testRootDoesNotExposeParentFolderEntries() {
3139
val archive = File(Environment.getExternalStorageDirectory(), "malicious.7z")

app/src/test/java/com/amaze/filemanager/asynchronous/services/ExtractServiceTest.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ class ExtractServiceTest {
9797
private val tarBz2file: File
9898
private val sevenZipfile: File
9999
private val maliciousTarGzFile: File
100+
private val maliciousTarBz2File: File
101+
private val maliciousTarXzFile: File
102+
private val maliciousTarLzmaFile: File
100103
private val maliciousSevenZipFile: File
101104
private val passwordProtectedZipfile: File
102105
private val passwordProtected7Zipfile: File
@@ -130,6 +133,9 @@ class ExtractServiceTest {
130133
tarBz2file = File(this, "test-archive.tar.bz2")
131134
sevenZipfile = File(this, "test-archive.7z")
132135
maliciousTarGzFile = File(this, "malicious.tar.gz")
136+
maliciousTarBz2File = File(this, "malicious.tar.bz2")
137+
maliciousTarXzFile = File(this, "malicious.tar.xz")
138+
maliciousTarLzmaFile = File(this, "malicious.tar.lzma")
133139
maliciousSevenZipFile = File(this, "malicious.7z")
134140
passwordProtectedZipfile = File(this, "test-archive-encrypted.zip")
135141
passwordProtected7Zipfile = File(this, "test-archive-encrypted.7z")
@@ -169,6 +175,9 @@ class ExtractServiceTest {
169175
getResourceAsStream("test-archive.tar.bz2").copyTo(FileOutputStream(tarBz2file))
170176
getResourceAsStream("test-archive.7z").copyTo(FileOutputStream(sevenZipfile))
171177
getResourceAsStream("malicious.tar.gz").copyTo(FileOutputStream(maliciousTarGzFile))
178+
getResourceAsStream("malicious.tar.bz2").copyTo(FileOutputStream(maliciousTarBz2File))
179+
getResourceAsStream("malicious.tar.xz").copyTo(FileOutputStream(maliciousTarXzFile))
180+
getResourceAsStream("malicious.tar.lzma").copyTo(FileOutputStream(maliciousTarLzmaFile))
172181
getResourceAsStream("malicious.7z").copyTo(FileOutputStream(maliciousSevenZipFile))
173182
getResourceAsStream("test-archive-encrypted.zip")
174183
.copyTo(FileOutputStream(passwordProtectedZipfile))
@@ -355,6 +364,39 @@ class ExtractServiceTest {
355364
assertErrorOrInvalidEntriesToast()
356365
}
357366

367+
/**
368+
* Test malicious tar.bz2 extraction exits with a regular error toast.
369+
*/
370+
@Test
371+
fun testExtractMaliciousTarBz2() {
372+
performTest(maliciousTarBz2File)
373+
ShadowLooper.idleMainLooper()
374+
await().atMost(10, TimeUnit.SECONDS).until { ShadowToast.getLatestToast() != null }
375+
assertErrorOrInvalidEntriesToast()
376+
}
377+
378+
/**
379+
* Test malicious tar.xz extraction exits with a regular error toast.
380+
*/
381+
@Test
382+
fun testExtractMaliciousTarXz() {
383+
performTest(maliciousTarXzFile)
384+
ShadowLooper.idleMainLooper()
385+
await().atMost(10, TimeUnit.SECONDS).until { ShadowToast.getLatestToast() != null }
386+
assertErrorOrInvalidEntriesToast()
387+
}
388+
389+
/**
390+
* Test malicious tar.lzma extraction exits with a regular error toast.
391+
*/
392+
@Test
393+
fun testExtractMaliciousTarLzma() {
394+
performTest(maliciousTarLzmaFile)
395+
ShadowLooper.idleMainLooper()
396+
await().atMost(10, TimeUnit.SECONDS).until { ShadowToast.getLatestToast() != null }
397+
assertErrorOrInvalidEntriesToast()
398+
}
399+
358400
private fun assertErrorOrInvalidEntriesToast() {
359401
val context = ApplicationProvider.getApplicationContext<Context>()
360402
val latestToastText = ShadowToast.getTextOfLatestToast()

app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/TarBzip2ExtractorTest.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,58 @@
2020

2121
package com.amaze.filemanager.filesystem.compressed.extractcontents
2222

23+
import android.os.Environment
24+
import androidx.test.core.app.ApplicationProvider
25+
import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil
2326
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarBzip2Extractor
27+
import org.junit.Assert.assertFalse
28+
import org.junit.Assert.fail
29+
import org.junit.Test
30+
import java.io.File
31+
import java.io.IOException
2432

2533
open class TarBzip2ExtractorTest : AbstractArchiveExtractorTest() {
2634
override val archiveType: String = "tar.bz2"
2735

2836
override fun extractorClass(): Class<out Extractor?> = TarBzip2Extractor::class.java
37+
38+
@Test
39+
fun testExtractMaliciousTarBzip2() {
40+
val maliciousArchive = File(Environment.getExternalStorageDirectory(), "malicious.tar.bz2")
41+
val outputDir = Environment.getExternalStorageDirectory()
42+
val extractor =
43+
TarBzip2Extractor(
44+
ApplicationProvider.getApplicationContext(),
45+
maliciousArchive.absolutePath,
46+
outputDir.absolutePath,
47+
object : Extractor.OnUpdate {
48+
override fun onStart(
49+
totalBytes: Long,
50+
firstEntryName: String,
51+
) = Unit
52+
53+
override fun onUpdate(entryPath: String) = Unit
54+
55+
override fun isCancelled(): Boolean = false
56+
57+
override fun onFinish() = Unit
58+
},
59+
ServiceWatcherUtil.UPDATE_POSITION,
60+
)
61+
62+
try {
63+
extractor.extractEverything()
64+
fail("Expected IOException: canonical-path guard must reject the traversal entry")
65+
} catch (e: IOException) {
66+
assertFalse(
67+
"Exception must not be a BadArchiveNotice",
68+
e is Extractor.BadArchiveNotice,
69+
)
70+
}
71+
72+
assertFalse(
73+
"Malicious file must not escape the output directory",
74+
File(outputDir.parentFile, "POC_ZIPSLIP_PROOF.txt").exists(),
75+
)
76+
}
2977
}

app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/TarLzmaExtractorTest.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,58 @@
2020

2121
package com.amaze.filemanager.filesystem.compressed.extractcontents
2222

23+
import android.os.Environment
24+
import androidx.test.core.app.ApplicationProvider
25+
import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil
2326
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarLzmaExtractor
27+
import org.junit.Assert.assertFalse
28+
import org.junit.Assert.fail
29+
import org.junit.Test
30+
import java.io.File
31+
import java.io.IOException
2432

2533
class TarLzmaExtractorTest : AbstractArchiveExtractorTest() {
2634
override val archiveType: String = "tar.lzma"
2735

2836
override fun extractorClass(): Class<out Extractor?> = TarLzmaExtractor::class.java
37+
38+
@Test
39+
fun testExtractMaliciousTarLzma() {
40+
val maliciousArchive = File(Environment.getExternalStorageDirectory(), "malicious.tar.lzma")
41+
val outputDir = Environment.getExternalStorageDirectory()
42+
val extractor =
43+
TarLzmaExtractor(
44+
ApplicationProvider.getApplicationContext(),
45+
maliciousArchive.absolutePath,
46+
outputDir.absolutePath,
47+
object : Extractor.OnUpdate {
48+
override fun onStart(
49+
totalBytes: Long,
50+
firstEntryName: String,
51+
) = Unit
52+
53+
override fun onUpdate(entryPath: String) = Unit
54+
55+
override fun isCancelled(): Boolean = false
56+
57+
override fun onFinish() = Unit
58+
},
59+
ServiceWatcherUtil.UPDATE_POSITION,
60+
)
61+
62+
try {
63+
extractor.extractEverything()
64+
fail("Expected IOException: canonical-path guard must reject the traversal entry")
65+
} catch (e: IOException) {
66+
assertFalse(
67+
"Exception must not be a BadArchiveNotice",
68+
e is Extractor.BadArchiveNotice,
69+
)
70+
}
71+
72+
assertFalse(
73+
"Malicious file must not escape the output directory",
74+
File(outputDir.parentFile, "POC_ZIPSLIP_PROOF.txt").exists(),
75+
)
76+
}
2977
}

app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/TarXzExtractorTest.kt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,58 @@
2020

2121
package com.amaze.filemanager.filesystem.compressed.extractcontents
2222

23+
import android.os.Environment
24+
import androidx.test.core.app.ApplicationProvider
25+
import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil
2326
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.TarXzExtractor
27+
import org.junit.Assert.assertFalse
28+
import org.junit.Assert.fail
29+
import org.junit.Test
30+
import java.io.File
31+
import java.io.IOException
2432

2533
class TarXzExtractorTest : AbstractArchiveExtractorTest() {
2634
override val archiveType: String = "tar.xz"
2735

2836
override fun extractorClass(): Class<out Extractor?> = TarXzExtractor::class.java
37+
38+
@Test
39+
fun testExtractMaliciousTarXz() {
40+
val maliciousArchive = File(Environment.getExternalStorageDirectory(), "malicious.tar.xz")
41+
val outputDir = Environment.getExternalStorageDirectory()
42+
val extractor =
43+
TarXzExtractor(
44+
ApplicationProvider.getApplicationContext(),
45+
maliciousArchive.absolutePath,
46+
outputDir.absolutePath,
47+
object : Extractor.OnUpdate {
48+
override fun onStart(
49+
totalBytes: Long,
50+
firstEntryName: String,
51+
) = Unit
52+
53+
override fun onUpdate(entryPath: String) = Unit
54+
55+
override fun isCancelled(): Boolean = false
56+
57+
override fun onFinish() = Unit
58+
},
59+
ServiceWatcherUtil.UPDATE_POSITION,
60+
)
61+
62+
try {
63+
extractor.extractEverything()
64+
fail("Expected IOException: canonical-path guard must reject the traversal entry")
65+
} catch (e: IOException) {
66+
assertFalse(
67+
"Exception must not be a BadArchiveNotice",
68+
e is Extractor.BadArchiveNotice,
69+
)
70+
}
71+
72+
assertFalse(
73+
"Malicious file must not escape the output directory",
74+
File(outputDir.parentFile, "POC_ZIPSLIP_PROOF.txt").exists(),
75+
)
76+
}
2977
}
218 Bytes
Binary file not shown.
217 Bytes
Binary file not shown.
264 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)