mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 12:00:03 +03:00
Merge pull request #4498 from vector-im/yostyle/fix_strandhogg
Override task affinity to prevent unknown activities running in our app tasks.
This commit is contained in:
parent
7a1322baf7
commit
6e6b04c57e
8 changed files with 144 additions and 13 deletions
1
changelog.d/4498.misc
Normal file
1
changelog.d/4498.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Override task affinity to prevent unknown activities running in our app tasks.
|
|
@ -131,6 +131,9 @@ android {
|
|||
// Required for sonar analysis
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar"
|
||||
|
||||
// Generate a random app task affinity
|
||||
manifestPlaceholders = [appTaskAffinitySuffix:"H_${gitRevision()}"]
|
||||
|
||||
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
|
||||
buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\""
|
||||
buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\""
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Vector.Light"
|
||||
android:taskAffinity="${applicationId}.${appTaskAffinitySuffix}"
|
||||
tools:replace="android:allowBackup">
|
||||
|
||||
<!-- No limit for screen ratio: avoid black strips -->
|
||||
|
@ -294,7 +295,7 @@
|
|||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:taskAffinity=".features.call.VectorCallActivity" />
|
||||
android:taskAffinity=".features.call.VectorCallActivity.${appTaskAffinitySuffix}" />
|
||||
<!-- PIP Support https://developer.android.com/guide/topics/ui/picture-in-picture -->
|
||||
<activity
|
||||
android:name=".features.call.conference.VectorJitsiActivity"
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.core.extensions
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
|
@ -29,6 +30,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import im.vector.app.R
|
||||
import timber.log.Timber
|
||||
|
||||
fun ComponentActivity.registerStartForActivityResult(onResult: (ActivityResult) -> Unit): ActivityResultLauncher<Intent> {
|
||||
return registerForActivityResult(ActivityResultContracts.StartActivityForResult(), onResult)
|
||||
|
@ -114,9 +116,25 @@ fun AppCompatActivity.hideKeyboard() {
|
|||
currentFocus?.hideKeyboard()
|
||||
}
|
||||
|
||||
/**
|
||||
* The current activity must be the root of a task to call onBackPressed, otherwise finish activities with the same task affinity.
|
||||
*/
|
||||
fun AppCompatActivity.validateBackPressed(onBackPressed: () -> Unit) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R && supportFragmentManager.backStackEntryCount == 0) {
|
||||
if (isTaskRoot) {
|
||||
onBackPressed()
|
||||
} else {
|
||||
Timber.e("Application is potentially corrupted by an unknown activity")
|
||||
finishAffinity()
|
||||
}
|
||||
} else {
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.restart() {
|
||||
startActivity(intent)
|
||||
finish()
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
fun Activity.keepScreenOn() {
|
||||
|
|
|
@ -42,6 +42,7 @@ import im.vector.app.core.extensions.exhaustive
|
|||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.extensions.validateBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.databinding.ActivityHomeBinding
|
||||
|
@ -515,7 +516,7 @@ class HomeActivity :
|
|||
if (views.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
validateBackPressed { super.onBackPressed() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,31 +16,128 @@
|
|||
|
||||
package im.vector.app.features.lifecycle
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.getSystemService
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager: PopupAlertManager) : Application.ActivityLifecycleCallbacks {
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
}
|
||||
/**
|
||||
* The activities information collected from the app manifest.
|
||||
*/
|
||||
private var activitiesInfo: Array<ActivityInfo> = emptyArray()
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
override fun onActivityPaused(activity: Activity) {}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
popupAlertManager.onNewActivityDisplayed(activity)
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
}
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
|
||||
}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
// restart the app if the task contains an unknown activity
|
||||
coroutineScope.launch {
|
||||
val isTaskCorrupted = try {
|
||||
isTaskCorrupted(activity)
|
||||
} catch (failure: Throwable) {
|
||||
when (failure) {
|
||||
// The task was not found. We can ignore it.
|
||||
is IllegalArgumentException -> {
|
||||
Timber.e("The task was not found: ${failure.localizedMessage}")
|
||||
false
|
||||
}
|
||||
is PackageManager.NameNotFoundException -> {
|
||||
Timber.e("Package manager error: ${failure.localizedMessage}")
|
||||
true
|
||||
}
|
||||
else -> throw failure
|
||||
}
|
||||
}
|
||||
|
||||
if (isTaskCorrupted) {
|
||||
Timber.e("Application is potentially corrupted by an unknown activity")
|
||||
MainActivity.restartApp(activity, MainActivityArgs())
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all activities running on the task with package name affinity are safe.
|
||||
*
|
||||
* @return true if an app task is corrupted by a potentially malicious activity
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Suppress("DEPRECATION")
|
||||
private suspend fun isTaskCorrupted(activity: Activity): Boolean = withContext(Dispatchers.Default) {
|
||||
val context = activity.applicationContext
|
||||
val packageManager: PackageManager = context.packageManager
|
||||
|
||||
// Get all activities from app manifest
|
||||
if (activitiesInfo.isEmpty()) {
|
||||
activitiesInfo = packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES).activities
|
||||
}
|
||||
|
||||
// Get all running activities on app task
|
||||
// and compare to activities declared in manifest
|
||||
val manager = context.getSystemService<ActivityManager>() ?: return@withContext false
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Android lint may return an error on topActivity field.
|
||||
// This field was added in ActivityManager.RecentTaskInfo class since Android M (API level 23)
|
||||
// and it is inherited from TaskInfo since Android Q (API level 29).
|
||||
// API 23 changes : https://developer.android.com/sdk/api_diff/23/changes/android.app.ActivityManager.RecentTaskInfo
|
||||
// API 29 changes : https://developer.android.com/sdk/api_diff/29/changes/android.app.ActivityManager.RecentTaskInfo
|
||||
manager.appTasks.any { appTask ->
|
||||
appTask.taskInfo.topActivity?.let { isPotentialMaliciousActivity(it) } ?: false
|
||||
}
|
||||
} else {
|
||||
// Android lint may return an error on topActivity field.
|
||||
// This was present in ActivityManager.RunningTaskInfo class since API level 1!
|
||||
// and it is inherited from TaskInfo since Android Q (API level 29).
|
||||
// API 29 changes : https://developer.android.com/sdk/api_diff/29/changes/android.app.ActivityManager.RunningTaskInfo
|
||||
manager.getRunningTasks(10).any { runningTaskInfo ->
|
||||
runningTaskInfo.topActivity?.let {
|
||||
// Check whether the activity task affinity matches with app task affinity.
|
||||
// The activity is considered safe when its task affinity doesn't correspond to app task affinity.
|
||||
if (packageManager.getActivityInfo(it, 0).taskAffinity == context.applicationInfo.taskAffinity) {
|
||||
isPotentialMaliciousActivity(it)
|
||||
} else false
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect potential malicious activity.
|
||||
* Check if the activity running in app task is declared in app manifest.
|
||||
*
|
||||
* @param activity the activity of the task
|
||||
* @return true if the activity is potentially malicious
|
||||
*/
|
||||
private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.none { it.name == activity.className }
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
|
|||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.validateBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivityLoginBinding
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
|
@ -279,6 +280,10 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
|
|||
?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
validateBackPressed { super.onBackPressed() }
|
||||
}
|
||||
|
||||
private fun onRegistrationStageNotSupported() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.app_name)
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.content.Intent
|
|||
import android.net.Uri
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.lazyViewModel
|
||||
import im.vector.app.core.extensions.validateBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.lifecycleAwareLazy
|
||||
import im.vector.app.databinding.ActivityLoginBinding
|
||||
|
@ -46,6 +47,10 @@ class OnboardingActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
|
|||
onboardingVariant.onNewIntent(intent)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
validateBackPressed { super.onBackPressed() }
|
||||
}
|
||||
|
||||
override fun initUiAndData() {
|
||||
onboardingVariant.initUiAndData(isFirstCreation())
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue