Skip to content

Commit

Permalink
Auto-disable apps that cancel the entire backup
Browse files Browse the repository at this point in the history
This can happen when the app process gets killed while its BackupAgent is running. There are several qcom apps in the wild that have this issue. These are DoSing our backups and are non-free, so we are defending ourselves against them.
  • Loading branch information
grote authored and chirayudesai committed Apr 16, 2024
1 parent 499126c commit c8d21fc
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 1 deletion.
4 changes: 3 additions & 1 deletion app/src/main/java/com/stevesoltys/seedvault/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stevesoltys.seedvault

import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
import android.app.Application
import android.app.backup.BackupManager
import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL
import android.app.backup.IBackupManager
import android.content.Context
Expand Down Expand Up @@ -147,9 +148,10 @@ open class App : Application() {

}

const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
const val MAGIC_PACKAGE_MANAGER: String = PACKAGE_MANAGER_SENTINEL
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
const val GLOBAL_METADATA_KEY = "@meta@"
const val ERROR_BACKUP_CANCELLED: Int = BackupManager.ERROR_BACKUP_CANCELLED

// TODO this doesn't work for LineageOS as they do public debug builds
fun isDebugBuild() = Build.TYPE == "userdebug"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ class SettingsManager(private val context: Context) {

fun isBackupEnabled(packageName: String) = !blacklistedApps.contains(packageName)

/**
* Disables backup for an app. Similar to [onAppBackupStatusChanged].
*/
fun disableBackup(packageName: String) {
if (blacklistedApps.add(packageName)) {
prefs.edit().putStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, blacklistedApps).apply()
}
}

fun isStorageBackupEnabled() = prefs.getBoolean(PREF_KEY_BACKUP_STORAGE, false)

@UiThread
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package com.stevesoltys.seedvault.ui.notification

import android.app.backup.BackupProgress
import android.app.backup.BackupTransport.AGENT_ERROR
import android.app.backup.IBackupObserver
import android.content.Context
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.PackageManager.NameNotFoundException
import android.util.Log
import android.util.Log.INFO
import android.util.Log.isLoggable
import com.stevesoltys.seedvault.ERROR_BACKUP_CANCELLED
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.worker.BackupRequester
import org.koin.core.component.KoinComponent
Expand All @@ -27,11 +30,14 @@ internal class NotificationBackupObserver(
private val nm: BackupNotificationManager by inject()
private val metadataManager: MetadataManager by inject()
private val packageService: PackageService by inject()
private val settingsManager: SettingsManager by inject()
private var currentPackage: String? = null
private var numPackages: Int = 0
private var numPackagesToReport: Int = 0
private var pmCounted: Boolean = false

private var errorPackageName: String? = null

init {
// Inform the notification manager that a backup has started
// and inform about the expected numbers of apps.
Expand Down Expand Up @@ -86,6 +92,17 @@ internal class NotificationBackupObserver(
// should only happen for MAGIC_PACKAGE_MANAGER, but better save than sorry
Log.e(TAG, "Error getting ApplicationInfo: ", e)
}

// Apps that get killed while interacting with their [BackupAgent] cancel the entire backup.
// In order to prevent them from DoSing us, we remember them here to auto-disable them.
// We noticed that the same app behavior can cause a status of
// either AGENT_ERROR or ERROR_BACKUP_CANCELLED, so we need to handle both.
errorPackageName = if (status == AGENT_ERROR || status == ERROR_BACKUP_CANCELLED) {
target
} else {
null // To not disable apps by mistake, we reset it when getting a new non-error result.
}

// often [onResult] gets called right away without any [onUpdate] call
showProgressNotification(target)
}
Expand All @@ -98,6 +115,15 @@ internal class NotificationBackupObserver(
* as a whole failed.
*/
override fun backupFinished(status: Int) {
if (status == ERROR_BACKUP_CANCELLED) {
val packageName = errorPackageName
if (packageName == null) {
Log.e(TAG, "Backup got cancelled, but there we have no culprit :(")
} else {
Log.w(TAG, "App $packageName misbehaved, will disable backup for it...")
settingsManager.disableBackup(packageName)
}
}
if (backupRequester.requestNext()) {
if (isLoggable(TAG, INFO)) {
Log.i(TAG, "Backup finished $numPackages/$requestedPackages. Status: $status")
Expand Down

0 comments on commit c8d21fc

Please sign in to comment.