remove corrupted snapshots when pruning

before a corrupted snapshot would DoS pruning
This commit is contained in:
Torsten Grote 2024-10-10 11:06:48 -03:00
parent 9339f9f0fb
commit 16c00be124
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
4 changed files with 38 additions and 3 deletions

View file

@ -96,6 +96,7 @@ internal class Loader(
}
} catch (e: Exception) {
log.error(e) { "Error writing cache file $cacheFile: " }
cacheFile?.delete()
}
// get associated data for version, used for authenticated decryption
val ad = crypto.getAdForVersion(version)

View file

@ -11,6 +11,7 @@ import com.stevesoltys.seedvault.proto.Snapshot
import io.github.oshai.kotlinlogging.KotlinLogging
import org.calyxos.seedvault.core.backends.AppBackupFileType
import org.calyxos.seedvault.core.backends.TopLevelFolder
import java.security.GeneralSecurityException
import java.time.LocalDate
import java.time.temporal.ChronoField
import java.time.temporal.TemporalAdjuster
@ -42,9 +43,14 @@ internal class Pruner(
val snapshotMap = mutableMapOf<Long, AppBackupFileType.Snapshot>()
val snapshots = mutableListOf<Snapshot>()
snapshotHandles.forEach { handle ->
val snapshot = snapshotManager.loadSnapshot(handle) // exception is allowed to bubble up
snapshotMap[snapshot.token] = handle
snapshots.add(snapshot)
try {
val snapshot = snapshotManager.loadSnapshot(handle)
snapshotMap[snapshot.token] = handle
snapshots.add(snapshot)
} catch (e: GeneralSecurityException) {
log.error(e) { "Error loading snapshot $handle, will remove: " }
snapshotManager.removeSnapshot(handle)
} // other exceptions (like IOException) are allowed to bubble up, so we try again
}
// find out which snapshots to keep
val toKeep = getTokenToKeep(snapshotMap.keys)

View file

@ -113,6 +113,7 @@ internal class ApkBackup(
val blobMap = chunkIds.associateWith { chunkId ->
latestSnapshot.blobsMap[chunkId] ?: error("Missing blob for $chunkId")
}
// TODO could also check if all blobs are (still) available in BlobCache
// important: add old APK to snapshot or it wouldn't be part of backup
snapshotCreator.onApkBackedUp(packageInfo, oldApk, blobMap)
return

View file

@ -23,6 +23,7 @@ import org.calyxos.seedvault.core.backends.FileInfo
import org.calyxos.seedvault.core.backends.TopLevelFolder
import org.calyxos.seedvault.core.toHexString
import org.junit.jupiter.api.Test
import java.security.GeneralSecurityException
import java.time.LocalDateTime
import java.time.ZoneOffset.UTC
import java.util.concurrent.TimeUnit
@ -61,6 +62,32 @@ internal class PrunerTest : TransportTest() {
pruner.removeOldSnapshotsAndPruneUnusedBlobs()
}
@Test
fun `corrupted snapshot gets removed`() = runBlocking {
val snapshot = snapshot.copy { token = System.currentTimeMillis() }
every { crypto.repoId } returns repoId
every { backendManager.backend } returns backend
coEvery {
backend.list(folder, AppBackupFileType.Snapshot::class, callback = captureLambda())
} answers {
val fileInfo = FileInfo(snapshotHandle1, Random.nextLong(Long.MAX_VALUE))
lambda<(FileInfo) -> Unit>().captured.invoke(fileInfo)
}
coEvery { snapshotManager.loadSnapshot(snapshotHandle1) } throws GeneralSecurityException()
coEvery { snapshotManager.removeSnapshot(snapshotHandle1) } just Runs
// we only find blobs that are in snapshots
val blobIds = snapshot.blobsMap.values.map { it.id.hexFromProto() }
expectLoadingBlobs(blobIds)
// but since all snapshots got removed, we remove all blobs :(
blobIds.forEach {
coEvery { backend.remove(AppBackupFileType.Blob(repoId, it)) } just Runs
}
pruner.removeOldSnapshotsAndPruneUnusedBlobs()
}
@Test
fun `three snapshots from same day don't remove blobs`() = runBlocking {
val snapshotMap = mapOf(