remove corrupted snapshots when pruning
before a corrupted snapshot would DoS pruning
This commit is contained in:
parent
9339f9f0fb
commit
16c00be124
4 changed files with 38 additions and 3 deletions
|
@ -96,6 +96,7 @@ internal class Loader(
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error(e) { "Error writing cache file $cacheFile: " }
|
log.error(e) { "Error writing cache file $cacheFile: " }
|
||||||
|
cacheFile?.delete()
|
||||||
}
|
}
|
||||||
// get associated data for version, used for authenticated decryption
|
// get associated data for version, used for authenticated decryption
|
||||||
val ad = crypto.getAdForVersion(version)
|
val ad = crypto.getAdForVersion(version)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.stevesoltys.seedvault.proto.Snapshot
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
||||||
import org.calyxos.seedvault.core.backends.TopLevelFolder
|
import org.calyxos.seedvault.core.backends.TopLevelFolder
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.temporal.ChronoField
|
import java.time.temporal.ChronoField
|
||||||
import java.time.temporal.TemporalAdjuster
|
import java.time.temporal.TemporalAdjuster
|
||||||
|
@ -42,9 +43,14 @@ internal class Pruner(
|
||||||
val snapshotMap = mutableMapOf<Long, AppBackupFileType.Snapshot>()
|
val snapshotMap = mutableMapOf<Long, AppBackupFileType.Snapshot>()
|
||||||
val snapshots = mutableListOf<Snapshot>()
|
val snapshots = mutableListOf<Snapshot>()
|
||||||
snapshotHandles.forEach { handle ->
|
snapshotHandles.forEach { handle ->
|
||||||
val snapshot = snapshotManager.loadSnapshot(handle) // exception is allowed to bubble up
|
try {
|
||||||
|
val snapshot = snapshotManager.loadSnapshot(handle)
|
||||||
snapshotMap[snapshot.token] = handle
|
snapshotMap[snapshot.token] = handle
|
||||||
snapshots.add(snapshot)
|
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
|
// find out which snapshots to keep
|
||||||
val toKeep = getTokenToKeep(snapshotMap.keys)
|
val toKeep = getTokenToKeep(snapshotMap.keys)
|
||||||
|
|
|
@ -113,6 +113,7 @@ internal class ApkBackup(
|
||||||
val blobMap = chunkIds.associateWith { chunkId ->
|
val blobMap = chunkIds.associateWith { chunkId ->
|
||||||
latestSnapshot.blobsMap[chunkId] ?: error("Missing blob for $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
|
// important: add old APK to snapshot or it wouldn't be part of backup
|
||||||
snapshotCreator.onApkBackedUp(packageInfo, oldApk, blobMap)
|
snapshotCreator.onApkBackedUp(packageInfo, oldApk, blobMap)
|
||||||
return
|
return
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.calyxos.seedvault.core.backends.FileInfo
|
||||||
import org.calyxos.seedvault.core.backends.TopLevelFolder
|
import org.calyxos.seedvault.core.backends.TopLevelFolder
|
||||||
import org.calyxos.seedvault.core.toHexString
|
import org.calyxos.seedvault.core.toHexString
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset.UTC
|
import java.time.ZoneOffset.UTC
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -61,6 +62,32 @@ internal class PrunerTest : TransportTest() {
|
||||||
pruner.removeOldSnapshotsAndPruneUnusedBlobs()
|
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
|
@Test
|
||||||
fun `three snapshots from same day don't remove blobs`() = runBlocking {
|
fun `three snapshots from same day don't remove blobs`() = runBlocking {
|
||||||
val snapshotMap = mapOf(
|
val snapshotMap = mapOf(
|
||||||
|
|
Loading…
Reference in a new issue