mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-01 16:46:39 +00:00
Update su request process
Due to changes in ec3705f2ed, the app can
no longer communicate with the dameon through a socket opened on the
daemon side due to SELinux restrictions. The workaround here is to have
the daemon decide a socket name, send it to the app, have the app create
the socket server, then finally the daemon connects to the app through
the socket.
This commit is contained in:
@@ -28,6 +28,7 @@ object Const {
|
||||
const val MIN_VERCODE = 19000
|
||||
const val PROVIDER_CONNECT = 20200
|
||||
const val DYNAMIC_PATH = 20400
|
||||
const val SEPOLICY_REDESIGN = 20416
|
||||
}
|
||||
|
||||
object ID {
|
||||
|
||||
@@ -2,48 +2,61 @@ package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.LocalServerSocket
|
||||
import android.net.LocalSocket
|
||||
import android.net.LocalSocketAddress
|
||||
import android.os.CountDownTimer
|
||||
import androidx.collection.ArrayMap
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
|
||||
import com.topjohnwu.magisk.core.model.MagiskPolicy
|
||||
import com.topjohnwu.magisk.core.model.toPolicy
|
||||
import com.topjohnwu.magisk.extensions.now
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import timber.log.Timber
|
||||
import java.io.*
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class SuRequestHandler(
|
||||
private val packageManager: PackageManager,
|
||||
private val policyDB: PolicyDao
|
||||
) {
|
||||
private val socket: LocalSocket = LocalSocket()
|
||||
private lateinit var out: DataOutputStream
|
||||
private lateinit var socket: LocalSocket
|
||||
private lateinit var output: DataOutputStream
|
||||
private lateinit var input: DataInputStream
|
||||
|
||||
protected var timer: CountDownTimer = DefaultCountDown()
|
||||
set(value) {
|
||||
field.cancel()
|
||||
field = value
|
||||
field.start()
|
||||
}
|
||||
protected lateinit var policy: MagiskPolicy
|
||||
private set
|
||||
|
||||
abstract fun onStart()
|
||||
|
||||
fun start(intent: Intent): Boolean {
|
||||
val socketName = intent.getStringExtra("socket") ?: return false
|
||||
val name = intent.getStringExtra("socket") ?: return false
|
||||
|
||||
try {
|
||||
socket.connect(LocalSocketAddress(socketName, LocalSocketAddress.Namespace.ABSTRACT))
|
||||
out = DataOutputStream(BufferedOutputStream(socket.outputStream))
|
||||
if (Info.env.magiskVersionCode >= Const.Version.SEPOLICY_REDESIGN) {
|
||||
val server = LocalServerSocket(name)
|
||||
val futureSocket = Shell.EXECUTOR.submit(Callable { server.accept() })
|
||||
try {
|
||||
socket = futureSocket.get(1, TimeUnit.SECONDS)
|
||||
} catch (e: Exception) {
|
||||
// Timeout or any IO errors
|
||||
throw e
|
||||
} finally {
|
||||
server.close()
|
||||
}
|
||||
} else {
|
||||
socket = LocalSocket()
|
||||
socket.connect(LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT))
|
||||
}
|
||||
output = DataOutputStream(BufferedOutputStream(socket.outputStream))
|
||||
input = DataInputStream(BufferedInputStream(socket.inputStream))
|
||||
val map = readRequest()
|
||||
val map = Shell.EXECUTOR.submit(Callable { readRequest() })
|
||||
.runCatching { get(1, TimeUnit.SECONDS) }.getOrNull() ?: return false
|
||||
val uid = map["uid"]?.toIntOrNull() ?: return false
|
||||
policy = uid.toPolicy(packageManager)
|
||||
} catch (e: Exception) {
|
||||
@@ -65,9 +78,7 @@ abstract class SuRequestHandler(
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
timer.start()
|
||||
onStart()
|
||||
UiThreadHandler.run { onStart() }
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -81,23 +92,22 @@ abstract class SuRequestHandler(
|
||||
policy.until = until
|
||||
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
|
||||
|
||||
if (until >= 0)
|
||||
policyDB.update(policy).blockingAwait()
|
||||
|
||||
try {
|
||||
out.writeInt(policy.policy)
|
||||
out.flush()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
} finally {
|
||||
runCatching {
|
||||
input.close()
|
||||
out.close()
|
||||
socket.close()
|
||||
Shell.EXECUTOR.submit {
|
||||
try {
|
||||
output.writeInt(policy.policy)
|
||||
output.flush()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
} finally {
|
||||
if (until >= 0)
|
||||
policyDB.update(policy).blockingAwait()
|
||||
runCatching {
|
||||
input.close()
|
||||
output.close()
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -118,11 +128,4 @@ abstract class SuRequestHandler(
|
||||
return ret
|
||||
}
|
||||
|
||||
private inner class DefaultCountDown
|
||||
: CountDownTimer(TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1)) {
|
||||
override fun onFinish() {
|
||||
respond(MagiskPolicy.DENY, 0)
|
||||
}
|
||||
override fun onTick(remains: Long) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
|
||||
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.model.events.DieEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseUIActivity
|
||||
import io.reactivex.Single
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() {
|
||||
@@ -37,8 +39,11 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
fun showRequest() {
|
||||
if (!viewModel.handleRequest(intent))
|
||||
finish()
|
||||
Single.fromCallable {
|
||||
viewModel.handleRequest(intent)
|
||||
}.subscribeK {
|
||||
if (!it) finish()
|
||||
}
|
||||
}
|
||||
|
||||
fun runHandler(action: String?) {
|
||||
|
||||
@@ -13,12 +13,11 @@ import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
|
||||
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.DENY
|
||||
import com.topjohnwu.magisk.core.su.SuRequestHandler
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
|
||||
import com.topjohnwu.magisk.model.events.DieEvent
|
||||
import com.topjohnwu.magisk.ui.base.BaseViewModel
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
|
||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
@@ -41,13 +40,11 @@ class SuRequestViewModel(
|
||||
|
||||
val grantEnabled = KObservableField(false)
|
||||
|
||||
private val items = DiffObservableList(ComparableRvItem.callback)
|
||||
private val itemBinding = ItemBinding.of<ComparableRvItem<*>> { binding, _, item ->
|
||||
item.bind(binding)
|
||||
}
|
||||
|
||||
val adapter = BindingListViewAdapter<ComparableRvItem<*>>(1).apply {
|
||||
itemBinding = this@SuRequestViewModel.itemBinding
|
||||
private val items = res.getStringArray(R.array.allow_timeout).map { SpinnerRvItem(it) }
|
||||
val adapter = BindingListViewAdapter<SpinnerRvItem>(1).apply {
|
||||
itemBinding = ItemBinding.of { binding, _, item ->
|
||||
item.bind(binding)
|
||||
}
|
||||
setItems(items)
|
||||
}
|
||||
|
||||
@@ -81,7 +78,11 @@ class SuRequestViewModel(
|
||||
|
||||
private inner class Handler : SuRequestHandler(pm, policyDB) {
|
||||
|
||||
private lateinit var timer: CountDownTimer
|
||||
|
||||
fun respond(action: Int) {
|
||||
timer.cancel()
|
||||
|
||||
val pos = selectedItemPosition.value
|
||||
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
|
||||
respond(action, Config.Value.TIMEOUT_LIST[pos])
|
||||
@@ -96,30 +97,36 @@ class SuRequestViewModel(
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
res.getStringArray(R.array.allow_timeout)
|
||||
.map { SpinnerRvItem(it) }
|
||||
.let { items.update(it) }
|
||||
|
||||
icon.value = policy.applicationInfo.loadIcon(pm)
|
||||
title.value = policy.appName
|
||||
packageName.value = policy.packageName
|
||||
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
||||
|
||||
// Override timer
|
||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
timer = object : CountDownTimer(millis, 1000) {
|
||||
override fun onTick(remains: Long) {
|
||||
if (remains <= millis - 1000) {
|
||||
grantEnabled.value = true
|
||||
}
|
||||
denyText.value = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
denyText.value = res.getString(R.string.deny)
|
||||
respond(DENY)
|
||||
}
|
||||
UiThreadHandler.handler.post {
|
||||
// Delay is required to properly do selection
|
||||
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
|
||||
}
|
||||
|
||||
// Set timer
|
||||
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
|
||||
timer = SuTimer(millis, 1000).apply { start() }
|
||||
}
|
||||
|
||||
private inner class SuTimer(
|
||||
private val millis: Long,
|
||||
interval: Long
|
||||
) : CountDownTimer(millis, interval) {
|
||||
|
||||
override fun onTick(remains: Long) {
|
||||
if (!grantEnabled.value && remains <= millis - 1000) {
|
||||
grantEnabled.value = true
|
||||
}
|
||||
denyText.value = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
denyText.value = res.getString(R.string.deny)
|
||||
respond(DENY)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,7 @@ open class DiffObservableList<T>(
|
||||
@MainThread
|
||||
fun update(newItems: List<T>) {
|
||||
val diffResult = doCalculateDiff(list, newItems)
|
||||
list = newItems.toMutableList()
|
||||
diffResult.dispatchUpdatesTo(listCallback)
|
||||
update(newItems, diffResult)
|
||||
}
|
||||
|
||||
override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
android:minWidth="350dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:id="@+id/request_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -45,7 +45,7 @@
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
style="@style/WidgetFoundation.Icon"
|
||||
android:layout_gravity="center_vertical"
|
||||
@@ -65,7 +65,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -78,7 +78,7 @@
|
||||
android:textStyle="bold"
|
||||
tools:text="Magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -92,16 +92,17 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
<Spinner
|
||||
android:id="@+id/timeout"
|
||||
onTouch="@{() -> viewModel.spinnerTouched()}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:enabled="@{viewModel.grantEnabled}"
|
||||
android:adapter="@{viewModel.adapter}"
|
||||
android:selection="@={viewModel.selectedItemPosition}" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
<TextView
|
||||
android:id="@+id/warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
Reference in New Issue
Block a user