mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-13 10:01:49 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78f7fa348e | ||
|
|
d8c448b99d | ||
|
|
d4b83b6a44 | ||
|
|
e5d36d1d24 | ||
|
|
ff18cb8e70 | ||
|
|
37a9724a54 | ||
|
|
d660401063 | ||
|
|
88541d6f49 | ||
|
|
ecd6129fe5 | ||
|
|
6dfe9df9e2 |
@@ -24,6 +24,7 @@ import androidx.core.widget.ImageViewCompat
|
|||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
import androidx.databinding.InverseBindingAdapter
|
import androidx.databinding.InverseBindingAdapter
|
||||||
import androidx.databinding.InverseBindingListener
|
import androidx.databinding.InverseBindingListener
|
||||||
|
import androidx.databinding.InverseMethod
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
@@ -33,9 +34,11 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
|||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.slider.Slider
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
import com.topjohnwu.magisk.utils.TextHolder
|
import com.topjohnwu.magisk.utils.TextHolder
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import com.topjohnwu.widget.IndeterminateCheckBox
|
import com.topjohnwu.widget.IndeterminateCheckBox
|
||||||
@@ -306,3 +309,38 @@ fun TextView.setText(text: TextHolder) {
|
|||||||
fun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) {
|
fun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) {
|
||||||
adapter = ArrayAdapter(context, layoutRes, items)
|
adapter = ArrayAdapter(context, layoutRes, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("labelFormatter")
|
||||||
|
fun Slider.setLabelFormatter(formatter: (Float) -> Int) {
|
||||||
|
setLabelFormatter { value -> resources.getString(formatter(value)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@InverseBindingAdapter(attribute = "android:value")
|
||||||
|
fun Slider.getValueBinding() = value
|
||||||
|
|
||||||
|
@BindingAdapter("android:valueAttrChanged")
|
||||||
|
fun Slider.setListener(attrChange: InverseBindingListener) {
|
||||||
|
addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
|
||||||
|
override fun onStartTrackingTouch(slider: Slider) = Unit
|
||||||
|
override fun onStopTrackingTouch(slider: Slider) = attrChange.onChange()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@InverseMethod("sliderValueToPolicy")
|
||||||
|
fun policyToSliderValue(policy: Int): Float {
|
||||||
|
return when (policy) {
|
||||||
|
SuPolicy.DENY -> 1f
|
||||||
|
SuPolicy.RESTRICT -> 2f
|
||||||
|
SuPolicy.ALLOW -> 3f
|
||||||
|
else -> 1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sliderValueToPolicy(value: Float): Int {
|
||||||
|
return when (value) {
|
||||||
|
1f -> SuPolicy.DENY
|
||||||
|
2f -> SuPolicy.RESTRICT
|
||||||
|
3f -> SuPolicy.ALLOW
|
||||||
|
else -> SuPolicy.DENY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -322,6 +322,12 @@ object Reauthenticate : BaseSettingsItem.Toggle() {
|
|||||||
override var value by Config::suReAuth
|
override var value by Config::suReAuth
|
||||||
|
|
||||||
override fun refresh() {
|
override fun refresh() {
|
||||||
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O && Info.showSuperUser
|
isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Restrict : BaseSettingsItem.Toggle() {
|
||||||
|
override val title = CoreR.string.settings_su_restrict_title.asText()
|
||||||
|
override val description = CoreR.string.settings_su_restrict_summary.asText()
|
||||||
|
override var value by Config::suRestrict
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
|||||||
// Can hide overlay windows on 12.0+
|
// Can hide overlay windows on 12.0+
|
||||||
list.remove(Tapjack)
|
list.remove(Tapjack)
|
||||||
}
|
}
|
||||||
|
if (Const.Version.atLeast_30_1()) {
|
||||||
|
list.add(Restrict)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
return list
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import android.graphics.drawable.Drawable
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||||
import com.topjohnwu.magisk.databinding.DiffItem
|
import com.topjohnwu.magisk.databinding.DiffItem
|
||||||
import com.topjohnwu.magisk.databinding.ItemWrapper
|
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||||
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||||
import com.topjohnwu.magisk.databinding.set
|
import com.topjohnwu.magisk.databinding.set
|
||||||
|
import com.topjohnwu.magisk.core.R as CoreR
|
||||||
|
|
||||||
class PolicyRvItem(
|
class PolicyRvItem(
|
||||||
private val viewModel: SuperuserViewModel,
|
private val viewModel: SuperuserViewModel,
|
||||||
@@ -33,14 +35,34 @@ class PolicyRvItem(
|
|||||||
var isExpanded = false
|
var isExpanded = false
|
||||||
set(value) = set(value, field, { field = it }, BR.expanded)
|
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||||
|
|
||||||
|
val showSlider = Config.suRestrict || item.policy == SuPolicy.RESTRICT
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var isEnabled
|
var isEnabled
|
||||||
get() = item.policy == SuPolicy.ALLOW
|
get() = item.policy >= SuPolicy.ALLOW
|
||||||
set(value) = setImpl(value, isEnabled) {
|
set(value) = setImpl(value, isEnabled) {
|
||||||
notifyPropertyChanged(BR.enabled)
|
notifyPropertyChanged(BR.enabled)
|
||||||
viewModel.togglePolicy(this, value)
|
viewModel.updatePolicy(this, if (it) SuPolicy.ALLOW else SuPolicy.DENY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:Bindable
|
||||||
|
var sliderValue
|
||||||
|
get() = item.policy
|
||||||
|
set(value) = setImpl(value, sliderValue) {
|
||||||
|
notifyPropertyChanged(BR.sliderValue)
|
||||||
|
notifyPropertyChanged(BR.enabled)
|
||||||
|
viewModel.updatePolicy(this, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sliderValueToPolicyString: (Float) -> Int = { value ->
|
||||||
|
when (value.toInt()) {
|
||||||
|
1 -> CoreR.string.deny
|
||||||
|
2 -> CoreR.string.restrict
|
||||||
|
3 -> CoreR.string.grant
|
||||||
|
else -> CoreR.string.deny
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var shouldNotify
|
var shouldNotify
|
||||||
get() = item.notification
|
get() = item.notification
|
||||||
|
|||||||
@@ -156,15 +156,16 @@ class SuperuserViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
fun updatePolicy(item: PolicyRvItem, policy: Int) {
|
||||||
val items = itemsPolicies.filter { it.item.uid == item.item.uid }
|
val items = itemsPolicies.filter { it.item.uid == item.item.uid }
|
||||||
fun updateState() {
|
fun updateState() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
|
val res = if (policy >= SuPolicy.ALLOW) R.string.su_snack_grant else R.string.su_snack_deny
|
||||||
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
item.item.policy = policy
|
||||||
db.update(item.item)
|
db.update(item.item)
|
||||||
items.forEach {
|
items.forEach {
|
||||||
it.notifyPropertyChanged(BR.enabled)
|
it.notifyPropertyChanged(BR.enabled)
|
||||||
|
it.notifyPropertyChanged(BR.sliderValue)
|
||||||
}
|
}
|
||||||
SnackbarEvent(res.asText(item.appName)).publish()
|
SnackbarEvent(res.asText(item.appName)).publish()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/log_track_container"
|
android:id="@+id/log_track_container"
|
||||||
bullet="@{item.log.action == 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
|
bullet="@{item.log.action >= 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}"
|
||||||
isBottom="@{item.isBottom}"
|
isBottom="@{item.isBottom}"
|
||||||
isSelected="@{item.log.action != 2}"
|
isSelected="@{item.log.action != 2}"
|
||||||
isTop="@{item.isTop}"
|
isTop="@{item.isTop}"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
|
<import type="com.topjohnwu.magisk.databinding.DataBindingAdaptersKt" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="item"
|
name="item"
|
||||||
type="com.topjohnwu.magisk.ui.superuser.PolicyRvItem" />
|
type="com.topjohnwu.magisk.ui.superuser.PolicyRvItem" />
|
||||||
@@ -85,16 +87,32 @@
|
|||||||
app:layout_constraintVertical_bias="0"
|
app:layout_constraintVertical_bias="0"
|
||||||
tools:text="com.topjohnwu.magisk" />
|
tools:text="com.topjohnwu.magisk" />
|
||||||
|
|
||||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
<FrameLayout
|
||||||
android:id="@+id/policy_indicator"
|
android:id="@+id/policy_indicator"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/l1"
|
android:layout_marginEnd="@dimen/l1"
|
||||||
android:checked="@={item.enabled}"
|
|
||||||
android:nextFocusLeft="@id/policy"
|
android:nextFocusLeft="@id/policy"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
gone="@{item.showSlider}"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checked="@={item.enabled}" />
|
||||||
|
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
goneUnless="@{item.showSlider}"
|
||||||
|
labelFormatter="@{item.sliderValueToPolicyString}"
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stepSize="1"
|
||||||
|
android:value="@={DataBindingAdaptersKt.policyToSliderValue(item.sliderValue)}"
|
||||||
|
android:valueFrom="1"
|
||||||
|
android:valueTo="3" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ object Config : PreferenceConfig, DBConfig {
|
|||||||
const val SU_NOTIFICATION = "su_notification"
|
const val SU_NOTIFICATION = "su_notification"
|
||||||
const val SU_REAUTH = "su_reauth"
|
const val SU_REAUTH = "su_reauth"
|
||||||
const val SU_TAPJACK = "su_tapjack"
|
const val SU_TAPJACK = "su_tapjack"
|
||||||
|
const val SU_RESTRICT = "su_restrict"
|
||||||
const val CHECK_UPDATES = "check_update"
|
const val CHECK_UPDATES = "check_update"
|
||||||
const val RELEASE_CHANNEL = "release_channel"
|
const val RELEASE_CHANNEL = "release_channel"
|
||||||
const val CUSTOM_CHANNEL = "custom_channel"
|
const val CUSTOM_CHANNEL = "custom_channel"
|
||||||
@@ -147,6 +148,7 @@ object Config : PreferenceConfig, DBConfig {
|
|||||||
}
|
}
|
||||||
var suReAuth by preference(Key.SU_REAUTH, false)
|
var suReAuth by preference(Key.SU_REAUTH, false)
|
||||||
var suTapjack by preference(Key.SU_TAPJACK, true)
|
var suTapjack by preference(Key.SU_TAPJACK, true)
|
||||||
|
var suRestrict by preference(Key.SU_RESTRICT, false)
|
||||||
|
|
||||||
private const val SU_FINGERPRINT = "su_fingerprint"
|
private const val SU_FINGERPRINT = "su_fingerprint"
|
||||||
private const val UPDATE_CHANNEL = "update_channel"
|
private const val UPDATE_CHANNEL = "update_channel"
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ object Const {
|
|||||||
const val MIN_VERSION = "v22.0"
|
const val MIN_VERSION = "v22.0"
|
||||||
const val MIN_VERCODE = 22000
|
const val MIN_VERCODE = 22000
|
||||||
|
|
||||||
fun atLeast_24_0() = Info.env.versionCode >= 24000
|
private fun isCanary() = (Info.env.versionCode % 100) != 0
|
||||||
fun atLeast_25_0() = Info.env.versionCode >= 25000
|
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
|
||||||
fun atLeast_28_0() = Info.env.versionCode >= 28000
|
fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary()
|
||||||
|
fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary()
|
||||||
|
fun atLeast_30_1() = Info.env.versionCode >= 30100 || isCanary()
|
||||||
}
|
}
|
||||||
|
|
||||||
object ID {
|
object ID {
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
|
|||||||
|
|
||||||
class SuPolicy(
|
class SuPolicy(
|
||||||
val uid: Int,
|
val uid: Int,
|
||||||
var policy: Int = INTERACTIVE,
|
var policy: Int = QUERY,
|
||||||
var remain: Long = -1L,
|
var remain: Long = -1L,
|
||||||
var logging: Boolean = true,
|
var logging: Boolean = true,
|
||||||
var notification: Boolean = true,
|
var notification: Boolean = true,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val INTERACTIVE = 0
|
const val QUERY = 0
|
||||||
const val DENY = 1
|
const val DENY = 1
|
||||||
const val ALLOW = 2
|
const val ALLOW = 2
|
||||||
|
const val RESTRICT = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toMap(): MutableMap<String, Any> {
|
fun toMap(): MutableMap<String, Any> {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ object SuCallbackHandler {
|
|||||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
||||||
|
|
||||||
if (notify)
|
if (notify)
|
||||||
notify(context, log.action == SuPolicy.ALLOW, log.appName)
|
notify(context, log.action >= SuPolicy.ALLOW, log.appName)
|
||||||
|
|
||||||
runBlocking { ServiceLocator.logRepo.insert(log) }
|
runBlocking { ServiceLocator.logRepo.insert(log) }
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ object SuCallbackHandler {
|
|||||||
pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)
|
pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)
|
||||||
}.getOrNull() ?: "[UID] $uid"
|
}.getOrNull() ?: "[UID] $uid"
|
||||||
|
|
||||||
notify(context, policy == SuPolicy.ALLOW, appName)
|
notify(context, policy >= SuPolicy.ALLOW, appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notify(context: Context, granted: Boolean, appName: String) {
|
private fun notify(context: Context, granted: Boolean, appName: String) {
|
||||||
|
|||||||
@@ -82,7 +82,11 @@ class SuRequestHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun respond(action: Int, time: Long) {
|
suspend fun respond(action: Int, time: Long) {
|
||||||
policy.policy = action
|
if (action == SuPolicy.ALLOW && Config.suRestrict) {
|
||||||
|
policy.policy = SuPolicy.RESTRICT
|
||||||
|
} else {
|
||||||
|
policy.policy = action
|
||||||
|
}
|
||||||
if (time >= 0) {
|
if (time >= 0) {
|
||||||
policy.remain = TimeUnit.MINUTES.toSeconds(time)
|
policy.remain = TimeUnit.MINUTES.toSeconds(time)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class AdditionalTest : BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testModuleCount() {
|
fun testModuleCount() {
|
||||||
var expected = 2
|
var expected = 3
|
||||||
if (Environment.mount()) expected++
|
if (Environment.mount()) expected++
|
||||||
if (Environment.preinit()) expected++
|
if (Environment.preinit()) expected++
|
||||||
if (Environment.lsposed()) expected++
|
if (Environment.lsposed()) expected++
|
||||||
@@ -90,8 +90,8 @@ class AdditionalTest : BaseTest {
|
|||||||
|
|
||||||
assertNotNull("$MOUNT_TEST is not installed", modules.find { it.id == MOUNT_TEST })
|
assertNotNull("$MOUNT_TEST is not installed", modules.find { it.id == MOUNT_TEST })
|
||||||
assertTrue(
|
assertTrue(
|
||||||
"/system/etc/newfile should exist",
|
"/system/fonts/newfile should exist",
|
||||||
RootUtils.fs.getFile("/system/etc/newfile").exists()
|
RootUtils.fs.getFile("/system/fonts/newfile").exists()
|
||||||
)
|
)
|
||||||
assertFalse(
|
assertFalse(
|
||||||
"/system/bin/screenrecord should not exist",
|
"/system/bin/screenrecord should not exist",
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ class Environment : BaseTest {
|
|||||||
val error = "$MOUNT_TEST setup failed"
|
val error = "$MOUNT_TEST setup failed"
|
||||||
val path = root.getChildFile(MOUNT_TEST)
|
val path = root.getChildFile(MOUNT_TEST)
|
||||||
|
|
||||||
// Create /system/etc/newfile
|
// Create /system/fonts/newfile
|
||||||
val etc = path.getChildFile("system").getChildFile("etc")
|
val etc = path.getChildFile("system").getChildFile("fonts")
|
||||||
assertTrue(error, etc.mkdirs())
|
assertTrue(error, etc.mkdirs())
|
||||||
assertTrue(error, etc.getChildFile("newfile").createNewFile())
|
assertTrue(error, etc.getChildFile("newfile").createNewFile())
|
||||||
|
|
||||||
@@ -116,6 +116,10 @@ class Environment : BaseTest {
|
|||||||
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupSystemlessHost() {
|
||||||
|
assertTrue("hosts setup failed", Shell.cmd("add_hosts_module").exec().isSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupSepolicyRuleModule(root: ExtendedFile) {
|
private fun setupSepolicyRuleModule(root: ExtendedFile) {
|
||||||
val error = "$SEPOLICY_RULE setup failed"
|
val error = "$SEPOLICY_RULE setup failed"
|
||||||
val path = root.getChildFile(SEPOLICY_RULE)
|
val path = root.getChildFile(SEPOLICY_RULE)
|
||||||
@@ -215,6 +219,7 @@ class Environment : BaseTest {
|
|||||||
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
||||||
if (mount()) { setupMountTest(root) }
|
if (mount()) { setupMountTest(root) }
|
||||||
if (preinit()) { setupSepolicyRuleModule(root) }
|
if (preinit()) { setupSepolicyRuleModule(root) }
|
||||||
|
setupSystemlessHost()
|
||||||
setupEmptyZygiskModule(root)
|
setupEmptyZygiskModule(root)
|
||||||
setupInvalidZygiskModule(root)
|
setupInvalidZygiskModule(root)
|
||||||
setupRemoveModule(root)
|
setupRemoveModule(root)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
<string name="touch_filtered_warning">由于某个应用遮挡了超级用户请求界面,因此 Magisk 无法验证您的回应</string>
|
<string name="touch_filtered_warning">由于某个应用遮挡了超级用户请求界面,因此 Magisk 无法验证您的回应</string>
|
||||||
<string name="deny">拒绝</string>
|
<string name="deny">拒绝</string>
|
||||||
<string name="prompt">提示</string>
|
<string name="prompt">提示</string>
|
||||||
|
<string name="restrict">受限</string>
|
||||||
<string name="grant">允许</string>
|
<string name="grant">允许</string>
|
||||||
<string name="su_warning">将授予对该设备的最高权限。\n如果不确定,请拒绝!</string>
|
<string name="su_warning">将授予对该设备的最高权限。\n如果不确定,请拒绝!</string>
|
||||||
<string name="forever">永久</string>
|
<string name="forever">永久</string>
|
||||||
@@ -173,6 +174,8 @@
|
|||||||
<string name="settings_su_auth_title">身份验证</string>
|
<string name="settings_su_auth_title">身份验证</string>
|
||||||
<string name="settings_su_auth_summary">对超级用户请求验证身份</string>
|
<string name="settings_su_auth_summary">对超级用户请求验证身份</string>
|
||||||
<string name="settings_su_auth_insecure">设备未配置验证方式</string>
|
<string name="settings_su_auth_insecure">设备未配置验证方式</string>
|
||||||
|
<string name="settings_su_restrict_title">限制超级用户权能</string>
|
||||||
|
<string name="settings_su_restrict_summary">默认限制新的超级用户应用。警告,这会破坏大多数应用,不建议启用。</string>
|
||||||
<string name="settings_customization">个性化</string>
|
<string name="settings_customization">个性化</string>
|
||||||
<string name="setting_add_shortcut_summary">在隐藏后难以识别名称和图标的情况下,添加快捷方式到桌面</string>
|
<string name="setting_add_shortcut_summary">在隐藏后难以识别名称和图标的情况下,添加快捷方式到桌面</string>
|
||||||
<string name="settings_doh_title">安全 DNS(DoH)</string>
|
<string name="settings_doh_title">安全 DNS(DoH)</string>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<string name="touch_filtered_warning">Because an app is obscuring a Superuser request, Magisk can\'t verify your response.</string>
|
<string name="touch_filtered_warning">Because an app is obscuring a Superuser request, Magisk can\'t verify your response.</string>
|
||||||
<string name="deny">Deny</string>
|
<string name="deny">Deny</string>
|
||||||
<string name="prompt">Prompt</string>
|
<string name="prompt">Prompt</string>
|
||||||
|
<string name="restrict">Restrict</string>
|
||||||
<string name="grant">Grant</string>
|
<string name="grant">Grant</string>
|
||||||
<string name="su_warning">Grants full access to your device.\nDeny if you\'re not sure!</string>
|
<string name="su_warning">Grants full access to your device.\nDeny if you\'re not sure!</string>
|
||||||
<string name="forever">Forever</string>
|
<string name="forever">Forever</string>
|
||||||
@@ -170,6 +171,8 @@
|
|||||||
<string name="settings_su_auth_title">User authentication</string>
|
<string name="settings_su_auth_title">User authentication</string>
|
||||||
<string name="settings_su_auth_summary">Ask for user authentication during Superuser requests</string>
|
<string name="settings_su_auth_summary">Ask for user authentication during Superuser requests</string>
|
||||||
<string name="settings_su_auth_insecure">No authentication method is configured on the device</string>
|
<string name="settings_su_auth_insecure">No authentication method is configured on the device</string>
|
||||||
|
<string name="settings_su_restrict_title">Restrict root capabilities</string>
|
||||||
|
<string name="settings_su_restrict_summary">Will restrict new superuser apps by default. Warning, this will break most apps, do not enable it.</string>
|
||||||
<string name="settings_customization">Customization</string>
|
<string name="settings_customization">Customization</string>
|
||||||
<string name="setting_add_shortcut_summary">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>
|
<string name="setting_add_shortcut_summary">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>
|
||||||
<string name="settings_doh_title">DNS over HTTPS</string>
|
<string name="settings_doh_title">DNS over HTTPS</string>
|
||||||
|
|||||||
@@ -30,4 +30,4 @@ android.nonFinalResIds=false
|
|||||||
|
|
||||||
# Magisk
|
# Magisk
|
||||||
magisk.stubVersion=40
|
magisk.stubVersion=40
|
||||||
magisk.versionCode=30000
|
magisk.versionCode=30100
|
||||||
|
|||||||
12
docs/faq.md
12
docs/faq.md
@@ -16,9 +16,17 @@ The following details should ensure that modules are properly disabled:
|
|||||||
|
|
||||||
Magisk no longer handles root hiding. There are plenty of Magisk/Zygisk modules available that specifically provide these functionalities, please search around 😉
|
Magisk no longer handles root hiding. There are plenty of Magisk/Zygisk modules available that specifically provide these functionalities, please search around 😉
|
||||||
|
|
||||||
### Q: After I hidden the Magisk app, the app icon is broken.
|
### Q: Magisk App shows Magisk Installed = N/A after an update but magisk su is still working.
|
||||||
|
|
||||||
When hiding the Magisk app, it will install a "stub" APK that has nothing in it. The only functionality this stub app has is downloading the full Magisk app APK into its internal storage and dynamically loading it. Due to the fact that the APK is literally _empty_, it does not contain the image resource for the app icon.
|
If upgrading with App hidden (ie. you took the 'Hide the Magisk app' option), the stub app (for hiding Magisk) may remain while a full Magisk app is also installed. This creates a conflict and the full app fails to see or access root... Uninstalling and reinstalling the full app can fix this, but not if a hidden app (stub) still exists.
|
||||||
|
|
||||||
|
The solution is to check for a hidden stub app and remove it. It may not show up normally in your launcher homescreen any longer, but should be visible from general settings, Apps. The hidden app will be named 'Settings' (default) or whatever you named it during the hiding process. Note that it is possible to have multiple obfuscated apps present. Uninstall any iterations of the hidden app you find and try opening the full app again. If necessary, uninstall it and reinstall the full app matching the binaries installed. Typing magisk -c in a terminal emulator app will show the version and version code for Magisk binaries installed (despite Installed = N/A showing).
|
||||||
|
|
||||||
|
Additionally, if a 'second space', eg. Workspace, Parallel Space etc, or another sandboxed environment, eg. a Multiple User additional profile, Island app or similar, is set up, check that no iterations of Magisk (either hidden or full apps) are running within these environments.
|
||||||
|
|
||||||
|
### Q: After I take the 'Hide the Magisk app' option the app icon is broken.
|
||||||
|
|
||||||
|
When hiding the Magisk app, it will install a "stub" APK that has nothing in it. The only functionality this stub app has is to download the full Magisk app APK data into its internal storage and dynamically load it. Due to the fact that the stub APK is literally empty, it does not contain the image resource for the app icon.
|
||||||
|
|
||||||
When you open the hidden Magisk app, it will offer you the option to create a shortcut in the homescreen (which has both the correct app name and icon) for your convenience. You can also manually ask the app to create the icon in app settings.
|
When you open the hidden Magisk app, it will offer you the option to create a shortcut in the homescreen (which has both the correct app name and icon) for your convenience. You can also manually ask the app to create the icon in app settings.
|
||||||
|
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ impl<S: Utf8CStrBuf + Sized> FsPathBuilder for S {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
|
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
|
||||||
self.write_fmt(format_args!("/{}", name)).ok();
|
self.write_fmt(format_args!("/{name}")).ok();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -595,7 +595,7 @@ impl FsPathBuilder for dyn Utf8CStrBuf + '_ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
|
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
|
||||||
self.write_fmt(format_args!("/{}", name)).ok();
|
self.write_fmt(format_args!("/{name}")).ok();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,7 +846,7 @@ fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
|
|||||||
|
|
||||||
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
|
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
let mut path = format!("/proc/{}/mountinfo", pid);
|
let mut path = format!("/proc/{pid}/mountinfo");
|
||||||
if let Ok(file) = Utf8CStr::from_string(&mut path).open(O_RDONLY | O_CLOEXEC) {
|
if let Ok(file) = Utf8CStr::from_string(&mut path).open(O_RDONLY | O_CLOEXEC) {
|
||||||
BufReader::new(file).foreach_lines(|line| {
|
BufReader::new(file).foreach_lines(|line| {
|
||||||
parse_mount_info_line(line)
|
parse_mount_info_line(line)
|
||||||
|
|||||||
@@ -184,6 +184,10 @@ int parse_int(string_view s) {
|
|||||||
return parse_num<int, 10>(s);
|
return parse_num<int, 10>(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t parse_uint32_hex(string_view s) {
|
||||||
|
return parse_num<uint32_t, 16>(s);
|
||||||
|
}
|
||||||
|
|
||||||
int switch_mnt_ns(int pid) {
|
int switch_mnt_ns(int pid) {
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
int fd = syscall(__NR_pidfd_open, pid, 0);
|
int fd = syscall(__NR_pidfd_open, pid, 0);
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ rust::Vec<size_t> mut_u8_patch(
|
|||||||
rust::Slice<const uint8_t> from,
|
rust::Slice<const uint8_t> from,
|
||||||
rust::Slice<const uint8_t> to);
|
rust::Slice<const uint8_t> to);
|
||||||
|
|
||||||
|
uint32_t parse_uint32_hex(std::string_view s);
|
||||||
int parse_int(std::string_view s);
|
int parse_int(std::string_view s);
|
||||||
|
|
||||||
using thread_entry = void *(*)(void *);
|
using thread_entry = void *(*)(void *);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ impl<T> EarlyExitExt<T> for Result<T, EarlyExit> {
|
|||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
eprintln!("{}", output);
|
eprintln!("{output}");
|
||||||
print_help_msg();
|
print_help_msg();
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ impl<T, E: Display> Loggable<T> for Result<T, E> {
|
|||||||
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
|
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
|
||||||
}
|
}
|
||||||
f(w)?;
|
f(w)?;
|
||||||
writeln!(w, ": {:#}", e)
|
writeln!(w, ": {e:#}")
|
||||||
});
|
});
|
||||||
Err(LoggedError::default())
|
Err(LoggedError::default())
|
||||||
}
|
}
|
||||||
@@ -366,7 +366,7 @@ impl Display for OsError<'_> {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let error = self.as_io_error();
|
let error = self.as_io_error();
|
||||||
if self.name.is_empty() {
|
if self.name.is_empty() {
|
||||||
write!(f, "{:#}", error)
|
write!(f, "{error:#}")
|
||||||
} else {
|
} else {
|
||||||
match (self.arg1.ok(), self.arg2.ok()) {
|
match (self.arg1.ok(), self.arg2.ok()) {
|
||||||
(Some(arg1), Some(arg2)) => {
|
(Some(arg1), Some(arg2)) => {
|
||||||
|
|||||||
@@ -269,13 +269,13 @@ impl Cpio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_from_file(path: &Utf8CStr) -> LoggedResult<Self> {
|
fn load_from_file(path: &Utf8CStr) -> LoggedResult<Self> {
|
||||||
eprintln!("Loading cpio: [{}]", path);
|
eprintln!("Loading cpio: [{path}]");
|
||||||
let file = MappedFile::open(path)?;
|
let file = MappedFile::open(path)?;
|
||||||
Self::load_from_data(file.as_ref())
|
Self::load_from_data(file.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump(&self, path: &str) -> LoggedResult<()> {
|
fn dump(&self, path: &str) -> LoggedResult<()> {
|
||||||
eprintln!("Dumping cpio: [{}]", path);
|
eprintln!("Dumping cpio: [{path}]");
|
||||||
let mut file = File::create(path)?;
|
let mut file = File::create(path)?;
|
||||||
let mut pos = 0usize;
|
let mut pos = 0usize;
|
||||||
let mut inode = 300000i64;
|
let mut inode = 300000i64;
|
||||||
@@ -320,13 +320,13 @@ impl Cpio {
|
|||||||
fn rm(&mut self, path: &str, recursive: bool) {
|
fn rm(&mut self, path: &str, recursive: bool) {
|
||||||
let path = norm_path(path);
|
let path = norm_path(path);
|
||||||
if self.entries.remove(&path).is_some() {
|
if self.entries.remove(&path).is_some() {
|
||||||
eprintln!("Removed entry [{}]", path);
|
eprintln!("Removed entry [{path}]");
|
||||||
}
|
}
|
||||||
if recursive {
|
if recursive {
|
||||||
let path = path + "/";
|
let path = path + "/";
|
||||||
self.entries.retain(|k, _| {
|
self.entries.retain(|k, _| {
|
||||||
if k.starts_with(&path) {
|
if k.starts_with(&path) {
|
||||||
eprintln!("Removed entry [{}]", k);
|
eprintln!("Removed entry [{k}]");
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
@@ -340,7 +340,7 @@ impl Cpio {
|
|||||||
.entries
|
.entries
|
||||||
.get(path)
|
.get(path)
|
||||||
.ok_or_else(|| log_err!("No such file"))?;
|
.ok_or_else(|| log_err!("No such file"))?;
|
||||||
eprintln!("Extracting entry [{}] to [{}]", path, out);
|
eprintln!("Extracting entry [{path}] to [{out}]");
|
||||||
|
|
||||||
let out = Utf8CStr::from_string(out);
|
let out = Utf8CStr::from_string(out);
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ impl Cpio {
|
|||||||
data: content,
|
data: content,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
eprintln!("Add file [{}] ({:04o})", path, mode);
|
eprintln!("Add file [{path}] ({mode:04o})");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +451,7 @@ impl Cpio {
|
|||||||
data: vec![],
|
data: vec![],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
eprintln!("Create directory [{}] ({:04o})", dir, mode);
|
eprintln!("Create directory [{dir}] ({mode:04o})");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ln(&mut self, src: &str, dst: &str) {
|
fn ln(&mut self, src: &str, dst: &str) {
|
||||||
@@ -466,7 +466,7 @@ impl Cpio {
|
|||||||
data: norm_path(src).as_bytes().to_vec(),
|
data: norm_path(src).as_bytes().to_vec(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
eprintln!("Create symlink [{}] -> [{}]", dst, src);
|
eprintln!("Create symlink [{dst}] -> [{src}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mv(&mut self, from: &str, to: &str) -> LoggedResult<()> {
|
fn mv(&mut self, from: &str, to: &str) -> LoggedResult<()> {
|
||||||
@@ -475,7 +475,7 @@ impl Cpio {
|
|||||||
.remove(&norm_path(from))
|
.remove(&norm_path(from))
|
||||||
.ok_or_else(|| log_err!("no such entry {}", from))?;
|
.ok_or_else(|| log_err!("no such entry {}", from))?;
|
||||||
self.entries.insert(norm_path(to), entry);
|
self.entries.insert(norm_path(to), entry);
|
||||||
eprintln!("Move [{}] -> [{}]", from, to);
|
eprintln!("Move [{from}] -> [{to}]");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ impl Cpio {
|
|||||||
if !recursive && !p.is_empty() && p.matches('/').count() > 1 {
|
if !recursive && !p.is_empty() && p.matches('/').count() > 1 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
println!("{}\t{}", entry, name);
|
println!("{entry}\t{name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,8 +511,7 @@ impl Cpio {
|
|||||||
let keep_verity = check_env("KEEPVERITY");
|
let keep_verity = check_env("KEEPVERITY");
|
||||||
let keep_force_encrypt = check_env("KEEPFORCEENCRYPT");
|
let keep_force_encrypt = check_env("KEEPFORCEENCRYPT");
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Patch with flag KEEPVERITY=[{}] KEEPFORCEENCRYPT=[{}]",
|
"Patch with flag KEEPVERITY=[{keep_verity}] KEEPFORCEENCRYPT=[{keep_force_encrypt}]"
|
||||||
keep_verity, keep_force_encrypt
|
|
||||||
);
|
);
|
||||||
self.entries.retain(|name, entry| {
|
self.entries.retain(|name, entry| {
|
||||||
let fstab = (!keep_verity || !keep_force_encrypt)
|
let fstab = (!keep_verity || !keep_force_encrypt)
|
||||||
@@ -523,7 +522,7 @@ impl Cpio {
|
|||||||
&& name.starts_with("fstab");
|
&& name.starts_with("fstab");
|
||||||
if !keep_verity {
|
if !keep_verity {
|
||||||
if fstab {
|
if fstab {
|
||||||
eprintln!("Found fstab file [{}]", name);
|
eprintln!("Found fstab file [{name}]");
|
||||||
let len = patch_verity(entry.data.as_mut_slice());
|
let len = patch_verity(entry.data.as_mut_slice());
|
||||||
if len != entry.data.len() {
|
if len != entry.data.len() {
|
||||||
entry.data.resize(len, 0);
|
entry.data.resize(len, 0);
|
||||||
@@ -581,7 +580,7 @@ impl Cpio {
|
|||||||
} else {
|
} else {
|
||||||
&name[8..]
|
&name[8..]
|
||||||
};
|
};
|
||||||
eprintln!("Restore [{}] -> [{}]", name, new_name);
|
eprintln!("Restore [{name}] -> [{new_name}]");
|
||||||
backups.insert(new_name.to_string(), entry);
|
backups.insert(new_name.to_string(), entry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -658,16 +657,16 @@ impl Cpio {
|
|||||||
match action {
|
match action {
|
||||||
Action::Backup(name, mut entry) => {
|
Action::Backup(name, mut entry) => {
|
||||||
let backup = if !skip_compress && entry.compress() {
|
let backup = if !skip_compress && entry.compress() {
|
||||||
format!(".backup/{}.xz", name)
|
format!(".backup/{name}.xz")
|
||||||
} else {
|
} else {
|
||||||
format!(".backup/{}", name)
|
format!(".backup/{name}")
|
||||||
};
|
};
|
||||||
eprintln!("Backup [{}] -> [{}]", name, backup);
|
eprintln!("Backup [{name}] -> [{backup}]");
|
||||||
backups.insert(backup, entry);
|
backups.insert(backup, entry);
|
||||||
}
|
}
|
||||||
Action::Record(name) => {
|
Action::Record(name) => {
|
||||||
eprintln!("Record new entry: [{}] -> [.backup/.rmlist]", name);
|
eprintln!("Record new entry: [{name}] -> [.backup/.rmlist]");
|
||||||
rm_list.push_str(&format!("{}\0", name));
|
rm_list.push_str(&format!("{name}\0"));
|
||||||
}
|
}
|
||||||
Action::Noop => {}
|
Action::Noop => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,9 +131,9 @@ fn print_node(node: &FdtNode) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if size > MAX_PRINT_LEN {
|
} else if size > MAX_PRINT_LEN {
|
||||||
println!("[{}]: <bytes>({})", name, size);
|
println!("[{name}]: <bytes>({size})");
|
||||||
} else {
|
} else {
|
||||||
println!("[{}]: {:02x?}", name, value);
|
println!("[{name}]: {value:02x?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
|
|||||||
rw: bool,
|
rw: bool,
|
||||||
mut f: F,
|
mut f: F,
|
||||||
) -> LoggedResult<()> {
|
) -> LoggedResult<()> {
|
||||||
eprintln!("Loading dtbs from [{}]", file);
|
eprintln!("Loading dtbs from [{file}]");
|
||||||
let file = if rw {
|
let file = if rw {
|
||||||
MappedFile::open_rw(file)?
|
MappedFile::open_rw(file)?
|
||||||
} else {
|
} else {
|
||||||
@@ -173,7 +173,7 @@ fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
|
|||||||
}
|
}
|
||||||
let fdt = match Fdt::new(slice) {
|
let fdt = match Fdt::new(slice) {
|
||||||
Err(FdtError::BufferTooSmall) => {
|
Err(FdtError::BufferTooSmall) => {
|
||||||
eprintln!("dtb.{:04} is truncated", dtb_num);
|
eprintln!("dtb.{dtb_num:04} is truncated");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ok(fdt) => fdt,
|
Ok(fdt) => fdt,
|
||||||
@@ -198,11 +198,11 @@ fn dtb_print(file: &Utf8CStr, fstab: bool) -> LoggedResult<()> {
|
|||||||
for_each_fdt(file, false, |n, fdt| {
|
for_each_fdt(file, false, |n, fdt| {
|
||||||
if fstab {
|
if fstab {
|
||||||
if let Some(fstab) = find_fstab(&fdt) {
|
if let Some(fstab) = find_fstab(&fdt) {
|
||||||
eprintln!("Found fstab in dtb.{:04}", n);
|
eprintln!("Found fstab in dtb.{n:04}");
|
||||||
print_node(&fstab);
|
print_node(&fstab);
|
||||||
}
|
}
|
||||||
} else if let Some(mut root) = fdt.find_node("/") {
|
} else if let Some(mut root) = fdt.find_node("/") {
|
||||||
eprintln!("Printing dtb.{:04}", n);
|
eprintln!("Printing dtb.{n:04}");
|
||||||
if root.name.is_empty() {
|
if root.name.is_empty() {
|
||||||
root.name = "/";
|
root.name = "/";
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
|
|||||||
&mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(w).get()
|
&mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(w).get()
|
||||||
};
|
};
|
||||||
w[..=4].copy_from_slice(b"want");
|
w[..=4].copy_from_slice(b"want");
|
||||||
eprintln!("Patch [skip_initramfs] -> [want_initramfs] in dtb.{:04}", n);
|
eprintln!("Patch [skip_initramfs] -> [want_initramfs] in dtb.{n:04}");
|
||||||
patched = true;
|
patched = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fn remove_pattern(buf: &mut [u8], pattern_matcher: unsafe fn(&[u8]) -> Option<us
|
|||||||
let skipped = buf.get_unchecked(read..(read + len));
|
let skipped = buf.get_unchecked(read..(read + len));
|
||||||
// SAFETY: all matching patterns are ASCII bytes
|
// SAFETY: all matching patterns are ASCII bytes
|
||||||
let skipped = std::str::from_utf8_unchecked(skipped);
|
let skipped = std::str::from_utf8_unchecked(skipped);
|
||||||
eprintln!("Remove pattern [{}]", skipped);
|
eprintln!("Remove pattern [{skipped}]");
|
||||||
sz -= len;
|
sz -= len;
|
||||||
read += len;
|
read += len;
|
||||||
} else {
|
} else {
|
||||||
@@ -114,7 +114,7 @@ pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
|
|||||||
|
|
||||||
let v = map.patch(pattern.as_slice(), patch.as_slice());
|
let v = map.patch(pattern.as_slice(), patch.as_slice());
|
||||||
for off in &v {
|
for off in &v {
|
||||||
eprintln!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
|
eprintln!("Patch @ {off:#010X} [{from}] -> [{to}]");
|
||||||
}
|
}
|
||||||
!v.is_empty()
|
!v.is_empty()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ fn do_extract_boot_from_payload(
|
|||||||
let mut reader = BufReader::new(if in_path == "-" {
|
let mut reader = BufReader::new(if in_path == "-" {
|
||||||
unsafe { File::from_raw_fd(0) }
|
unsafe { File::from_raw_fd(0) }
|
||||||
} else {
|
} else {
|
||||||
File::open(in_path).log_with_msg(|w| write!(w, "Cannot open '{}'", in_path))?
|
File::open(in_path).log_with_msg(|w| write!(w, "Cannot open '{in_path}'"))?
|
||||||
});
|
});
|
||||||
|
|
||||||
let buf = &mut [0u8; 4];
|
let buf = &mut [0u8; 4];
|
||||||
@@ -107,7 +107,7 @@ fn do_extract_boot_from_payload(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut out_file =
|
let mut out_file =
|
||||||
File::create(out_path).log_with_msg(|w| write!(w, "Cannot write to '{}'", out_path))?;
|
File::create(out_path).log_with_msg(|w| write!(w, "Cannot write to '{out_path}'"))?;
|
||||||
|
|
||||||
// Skip the manifest signature
|
// Skip the manifest signature
|
||||||
reader.skip(manifest_sig_len as usize)?;
|
reader.skip(manifest_sig_len as usize)?;
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ fn switch_cgroup(cgroup: &str, pid: i32) {
|
|||||||
}
|
}
|
||||||
if let Ok(mut file) = buf.open(O_WRONLY | O_APPEND | O_CLOEXEC) {
|
if let Ok(mut file) = buf.open(O_WRONLY | O_APPEND | O_CLOEXEC) {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
buf.write_fmt(format_args!("{}", pid)).ok();
|
buf.write_fmt(format_args!("{pid}")).ok();
|
||||||
file.write_all(buf.as_bytes()).log_ok();
|
file.write_all(buf.as_bytes()).log_ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ pub mod ffi {
|
|||||||
Query,
|
Query,
|
||||||
Deny,
|
Deny,
|
||||||
Allow,
|
Allow,
|
||||||
|
Restrict,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ModuleInfo {
|
struct ModuleInfo {
|
||||||
@@ -117,6 +118,7 @@ pub mod ffi {
|
|||||||
target_pid: i32,
|
target_pid: i32,
|
||||||
login: bool,
|
login: bool,
|
||||||
keep_env: bool,
|
keep_env: bool,
|
||||||
|
drop_cap: bool,
|
||||||
shell: String,
|
shell: String,
|
||||||
command: String,
|
command: String,
|
||||||
context: String,
|
context: String,
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ fn write_log_to_pipe(mut logd: &File, prio: i32, msg: &Utf8CStr) -> io::Result<u
|
|||||||
let result = logd.write_vectored(&[io1, io2]);
|
let result = logd.write_vectored(&[io1, io2]);
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
let mut buf = cstr::buf::default();
|
let mut buf = cstr::buf::default();
|
||||||
buf.write_fmt(format_args!("Cannot write_log_to_pipe: {}", e))
|
buf.write_fmt(format_args!("Cannot write_log_to_pipe: {e}"))
|
||||||
.ok();
|
.ok();
|
||||||
android_log_write(LogLevel::Error, &buf);
|
android_log_write(LogLevel::Error, &buf);
|
||||||
}
|
}
|
||||||
@@ -142,11 +142,11 @@ static MAGISK_LOGD_FD: Mutex<Option<Arc<File>>> = Mutex::new(None);
|
|||||||
|
|
||||||
fn with_logd_fd<R, F: FnOnce(&File) -> io::Result<R>>(f: F) {
|
fn with_logd_fd<R, F: FnOnce(&File) -> io::Result<R>>(f: F) {
|
||||||
let fd = MAGISK_LOGD_FD.lock().unwrap().clone();
|
let fd = MAGISK_LOGD_FD.lock().unwrap().clone();
|
||||||
if let Some(logd) = fd {
|
if let Some(logd) = fd
|
||||||
if f(&logd).is_err() {
|
&& f(&logd).is_err()
|
||||||
// If any error occurs, shut down the logd pipe
|
{
|
||||||
*MAGISK_LOGD_FD.lock().unwrap() = None;
|
// If any error occurs, shut down the logd pipe
|
||||||
}
|
*MAGISK_LOGD_FD.lock().unwrap() = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::consts::{MODULEMNT, WORKERDIR};
|
use crate::consts::{MODULEMNT, MODULEROOT, WORKERDIR};
|
||||||
use crate::ffi::{ModuleInfo, get_magisk_tmp};
|
use crate::ffi::{ModuleInfo, get_magisk_tmp};
|
||||||
use crate::load_prop_file;
|
use crate::load_prop_file;
|
||||||
use base::{
|
use base::{
|
||||||
@@ -9,8 +9,12 @@ use libc::{MS_RDONLY, O_CLOEXEC, O_CREAT, O_RDONLY};
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Component, Path};
|
use std::path::{Component, Path};
|
||||||
|
|
||||||
const MAGISK_BIN_INJECT_PARTITIONS: [&Utf8CStr; 4] =
|
const MAGISK_BIN_INJECT_PARTITIONS: [&Utf8CStr; 4] = [
|
||||||
[cstr!("/system/"), cstr!("/vendor/"), cstr!("/product/"), cstr!("/system_ext/")];
|
cstr!("/system/"),
|
||||||
|
cstr!("/vendor/"),
|
||||||
|
cstr!("/product/"),
|
||||||
|
cstr!("/system_ext/"),
|
||||||
|
];
|
||||||
|
|
||||||
const SECONDARY_READ_ONLY_PARTITIONS: [&Utf8CStr; 3] =
|
const SECONDARY_READ_ONLY_PARTITIONS: [&Utf8CStr; 3] =
|
||||||
[cstr!("/vendor"), cstr!("/product"), cstr!("/system_ext")];
|
[cstr!("/vendor"), cstr!("/product"), cstr!("/system_ext")];
|
||||||
@@ -40,62 +44,93 @@ fn mount_dummy(reason: &str, src: &Utf8CStr, dest: &Utf8CStr, is_dir: bool) -> O
|
|||||||
bind_mount(reason, src, dest, false)
|
bind_mount(reason, src, dest, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// File paths that act like a stack, popping out the last element
|
// File path that act like a stack, popping out the last element
|
||||||
// automatically when out of scope. Using Rust's lifetime mechanism,
|
// automatically when out of scope. Using Rust's lifetime mechanism,
|
||||||
// we can ensure the buffer will never be incorrectly copied or modified.
|
// we can ensure the buffer will never be incorrectly copied or modified.
|
||||||
// After calling append or clone, the mutable reference's lifetime is
|
// After calling append or reborrow, the mutable reference's lifetime is
|
||||||
// "transferred" to the returned object, and the compiler will guarantee
|
// "transferred" to the returned object, and the compiler will guarantee
|
||||||
// that the original mutable reference can only be reused if and only if
|
// that the original mutable reference can only be reused if and only if
|
||||||
// the newly created instance is destroyed.
|
// the newly created instance is destroyed.
|
||||||
struct PathTracker<'a> {
|
struct PathTracker<'a> {
|
||||||
real: &'a mut dyn Utf8CStrBuf,
|
path: &'a mut dyn Utf8CStrBuf,
|
||||||
tmp: &'a mut dyn Utf8CStrBuf,
|
len: usize,
|
||||||
real_len: usize,
|
|
||||||
tmp_len: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathTracker<'_> {
|
impl PathTracker<'_> {
|
||||||
fn from<'a>(real: &'a mut dyn Utf8CStrBuf, tmp: &'a mut dyn Utf8CStrBuf) -> PathTracker<'a> {
|
fn from<'a>(path: &'a mut dyn Utf8CStrBuf) -> PathTracker<'a> {
|
||||||
let real_len = real.len();
|
let len = path.len();
|
||||||
let tmp_len = tmp.len();
|
PathTracker { path, len }
|
||||||
PathTracker {
|
|
||||||
real,
|
|
||||||
tmp,
|
|
||||||
real_len,
|
|
||||||
tmp_len,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append(&mut self, name: &str) -> PathTracker {
|
fn append(&mut self, name: &str) -> PathTracker {
|
||||||
let real_len = self.real.len();
|
let len = self.path.len();
|
||||||
let tmp_len = self.tmp.len();
|
self.path.append_path(name);
|
||||||
self.real.append_path(name);
|
|
||||||
self.tmp.append_path(name);
|
|
||||||
PathTracker {
|
PathTracker {
|
||||||
real: self.real,
|
path: self.path,
|
||||||
tmp: self.tmp,
|
len,
|
||||||
real_len,
|
|
||||||
tmp_len,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone(&mut self) -> PathTracker {
|
fn reborrow(&mut self) -> PathTracker {
|
||||||
Self::from(self.real, self.tmp)
|
Self::from(self.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for PathTracker<'_> {
|
impl Drop for PathTracker<'_> {
|
||||||
// Revert back to the original state after finish using the buffer
|
// Revert back to the original state after finish using the buffer
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.real.truncate(self.real_len);
|
self.path.truncate(self.len);
|
||||||
self.tmp.truncate(self.tmp_len);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FilePaths<'a> {
|
||||||
|
real: PathTracker<'a>,
|
||||||
|
worker: PathTracker<'a>,
|
||||||
|
module_mnt: PathTracker<'a>,
|
||||||
|
module_root: PathTracker<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilePaths<'_> {
|
||||||
|
fn append(&mut self, name: &str) -> FilePaths {
|
||||||
|
FilePaths {
|
||||||
|
real: self.real.append(name),
|
||||||
|
worker: self.worker.append(name),
|
||||||
|
module_mnt: self.module_mnt.append(name),
|
||||||
|
module_root: self.module_root.append(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reborrow(&mut self) -> FilePaths {
|
||||||
|
FilePaths {
|
||||||
|
real: self.real.reborrow(),
|
||||||
|
worker: self.worker.reborrow(),
|
||||||
|
module_mnt: self.module_mnt.reborrow(),
|
||||||
|
module_root: self.module_root.reborrow(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real(&self) -> &Utf8CStr {
|
||||||
|
self.real.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worker(&self) -> &Utf8CStr {
|
||||||
|
self.worker.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_mnt(&self) -> &Utf8CStr {
|
||||||
|
self.module_mnt.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module(&self) -> &Utf8CStr {
|
||||||
|
self.module_root.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FsNode {
|
enum FsNode {
|
||||||
Directory { children: FsNodeMap },
|
Directory { children: FsNodeMap },
|
||||||
File { src: Utf8CString },
|
File { src: Utf8CString },
|
||||||
Symlink { target: Utf8CString, is_magisk_bin: bool },
|
Symlink { target: Utf8CString },
|
||||||
|
MagiskLink,
|
||||||
Whiteout,
|
Whiteout,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,21 +141,20 @@ impl FsNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_from_path(&mut self, path: &mut dyn Utf8CStrBuf) -> LoggedResult<()> {
|
fn collect(&mut self, mut paths: FilePaths) -> LoggedResult<()> {
|
||||||
let FsNode::Directory { children } = self else {
|
let FsNode::Directory { children } = self else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let mut dir = Directory::open(path)?;
|
let mut dir = Directory::open(paths.module())?;
|
||||||
let path_len = path.len();
|
|
||||||
|
|
||||||
while let Some(entry) = dir.read()? {
|
while let Some(entry) = dir.read()? {
|
||||||
path.truncate(path_len);
|
let entry_paths = paths.append(entry.name());
|
||||||
path.append_path(entry.name());
|
let path = entry_paths.module();
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
let node = children
|
let node = children
|
||||||
.entry(entry.name().to_string())
|
.entry(entry.name().to_string())
|
||||||
.or_insert_with(FsNode::new_dir);
|
.or_insert_with(FsNode::new_dir);
|
||||||
node.build_from_path(path)?;
|
node.collect(entry_paths)?;
|
||||||
} else if entry.is_symlink() {
|
} else if entry.is_symlink() {
|
||||||
let mut link = cstr::buf::default();
|
let mut link = cstr::buf::default();
|
||||||
path.read_link(&mut link)?;
|
path.read_link(&mut link)?;
|
||||||
@@ -128,7 +162,6 @@ impl FsNode {
|
|||||||
.entry(entry.name().to_string())
|
.entry(entry.name().to_string())
|
||||||
.or_insert_with(|| FsNode::Symlink {
|
.or_insert_with(|| FsNode::Symlink {
|
||||||
target: link.to_owned(),
|
target: link.to_owned(),
|
||||||
is_magisk_bin: false,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if entry.is_char_device() {
|
if entry.is_char_device() {
|
||||||
@@ -140,10 +173,14 @@ impl FsNode {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if entry_paths.real().exists() {
|
||||||
|
clone_attr(entry_paths.real(), path)?;
|
||||||
|
}
|
||||||
children
|
children
|
||||||
.entry(entry.name().to_string())
|
.entry(entry.name().to_string())
|
||||||
.or_insert_with(|| FsNode::File {
|
.or_insert_with(|| FsNode::File {
|
||||||
src: path.to_owned(),
|
// Make sure to mount from module_mnt, not module
|
||||||
|
src: entry_paths.module_mnt().to_owned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +201,7 @@ impl FsNode {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FsNode::Symlink { .. } | FsNode::Whiteout => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +212,7 @@ impl FsNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&mut self, mut path: PathTracker, is_root_dir: bool) -> LoggedResult<()> {
|
fn commit(&mut self, mut path: FilePaths, is_root_dir: bool) -> LoggedResult<()> {
|
||||||
match self {
|
match self {
|
||||||
FsNode::Directory { children } => {
|
FsNode::Directory { children } => {
|
||||||
let mut is_tmpfs = false;
|
let mut is_tmpfs = false;
|
||||||
@@ -184,7 +221,7 @@ impl FsNode {
|
|||||||
children.retain(|name, node| {
|
children.retain(|name, node| {
|
||||||
if name == ".replace" {
|
if name == ".replace" {
|
||||||
return if is_root_dir {
|
return if is_root_dir {
|
||||||
warn!("Unable to replace '{}', ignore request", path.real);
|
warn!("Unable to replace '{}', ignore request", path.real());
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
is_tmpfs = true;
|
is_tmpfs = true;
|
||||||
@@ -193,10 +230,10 @@ impl FsNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let path = path.append(name);
|
let path = path.append(name);
|
||||||
if node.parent_should_be_tmpfs(path.real) {
|
if node.parent_should_be_tmpfs(path.real()) {
|
||||||
if is_root_dir {
|
if is_root_dir {
|
||||||
// Ignore the unsupported child node
|
// Ignore the unsupported child node
|
||||||
warn!("Unable to add '{}', skipped", path.real);
|
warn!("Unable to add '{}', skipped", path.real());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
is_tmpfs = true;
|
is_tmpfs = true;
|
||||||
@@ -205,10 +242,10 @@ impl FsNode {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if is_tmpfs {
|
if is_tmpfs {
|
||||||
self.commit_tmpfs(path.clone())?;
|
self.commit_tmpfs(path.reborrow())?;
|
||||||
// Transitioning from non-tmpfs to tmpfs, we need to actually mount the
|
// Transitioning from non-tmpfs to tmpfs, we need to actually mount the
|
||||||
// worker dir to dest after all children are committed.
|
// worker dir to dest after all children are committed.
|
||||||
bind_mount("move", path.tmp, path.real, true)?;
|
bind_mount("move", path.worker(), path.real(), true)?;
|
||||||
} else {
|
} else {
|
||||||
for (name, node) in children {
|
for (name, node) in children {
|
||||||
let path = path.append(name);
|
let path = path.append(name);
|
||||||
@@ -217,26 +254,25 @@ impl FsNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FsNode::File { src } => {
|
FsNode::File { src } => {
|
||||||
clone_attr(path.real, src)?;
|
bind_mount("mount", src, path.real(), false)?;
|
||||||
bind_mount("mount", src, path.real, false)?;
|
|
||||||
}
|
}
|
||||||
FsNode::Symlink { .. } | FsNode::Whiteout => {
|
_ => {
|
||||||
error!("Unable to handle '{}': parent should be tmpfs", path.real);
|
error!("Unable to handle '{}': parent should be tmpfs", path.real());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_tmpfs(&mut self, mut path: PathTracker) -> LoggedResult<()> {
|
fn commit_tmpfs(&mut self, mut path: FilePaths) -> LoggedResult<()> {
|
||||||
match self {
|
match self {
|
||||||
FsNode::Directory { children } => {
|
FsNode::Directory { children } => {
|
||||||
path.tmp.mkdirs(0o000)?;
|
path.worker().mkdirs(0o000)?;
|
||||||
if path.real.exists() {
|
if path.real().exists() {
|
||||||
clone_attr(path.real, path.tmp)?;
|
clone_attr(path.real(), path.worker())?;
|
||||||
} else if let Some(p) = path.tmp.parent_dir() {
|
} else if let Some(p) = path.worker().parent_dir() {
|
||||||
let parent = Utf8CString::from(p);
|
let parent = Utf8CString::from(p);
|
||||||
clone_attr(&parent, path.tmp)?;
|
clone_attr(&parent, path.worker())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether a file name '.replace' exist
|
// Check whether a file name '.replace' exist
|
||||||
@@ -251,7 +287,7 @@ impl FsNode {
|
|||||||
bind_mount(
|
bind_mount(
|
||||||
"mount",
|
"mount",
|
||||||
&src,
|
&src,
|
||||||
path.real,
|
path.real(),
|
||||||
matches!(node, FsNode::Directory { .. }),
|
matches!(node, FsNode::Directory { .. }),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@@ -264,7 +300,7 @@ impl FsNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Traverse the real directory and mount mirrors
|
// Traverse the real directory and mount mirrors
|
||||||
if let Ok(mut dir) = Directory::open(path.real) {
|
if let Ok(mut dir) = Directory::open(path.real()) {
|
||||||
while let Ok(Some(entry)) = dir.read() {
|
while let Ok(Some(entry)) = dir.read() {
|
||||||
if children.contains_key(entry.name().as_str()) {
|
if children.contains_key(entry.name().as_str()) {
|
||||||
// Should not be mirrored, next
|
// Should not be mirrored, next
|
||||||
@@ -281,11 +317,10 @@ impl FsNode {
|
|||||||
entry.name().to_string(),
|
entry.name().to_string(),
|
||||||
FsNode::Symlink {
|
FsNode::Symlink {
|
||||||
target: link.to_owned(),
|
target: link.to_owned(),
|
||||||
is_magisk_bin: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mount_dummy("mirror", path.real, path.tmp, entry.is_dir())?;
|
mount_dummy("mirror", path.real(), path.worker(), entry.is_dir())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,21 +332,28 @@ impl FsNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FsNode::File { src } => {
|
FsNode::File { src } => {
|
||||||
if path.real.exists() {
|
mount_dummy("mount", src, path.worker(), false)?;
|
||||||
clone_attr(path.real, src)?;
|
|
||||||
}
|
|
||||||
mount_dummy("mount", src, path.tmp, false)?;
|
|
||||||
}
|
}
|
||||||
FsNode::Symlink { target, is_magisk_bin } => {
|
FsNode::Symlink { target } => {
|
||||||
module_log!("mklink", path.tmp, target);
|
module_log!("mklink", path.worker(), target);
|
||||||
path.tmp.create_symlink_to(target)?;
|
path.worker().create_symlink_to(target)?;
|
||||||
// Avoid cloneing existing su attributes to our su
|
if path.real().exists() {
|
||||||
if !*is_magisk_bin && path.real.exists() {
|
clone_attr(path.real(), path.worker())?;
|
||||||
clone_attr(path.real, path.tmp)?;
|
}
|
||||||
|
}
|
||||||
|
FsNode::MagiskLink => {
|
||||||
|
if let Some(name) = path.real().file_name()
|
||||||
|
&& name == "supolicy"
|
||||||
|
{
|
||||||
|
module_log!("mklink", path.worker(), "./magiskpolicy");
|
||||||
|
path.worker().create_symlink_to(cstr!("./magiskpolicy"))?;
|
||||||
|
} else {
|
||||||
|
module_log!("mklink", path.worker(), "./magisk");
|
||||||
|
path.worker().create_symlink_to(cstr!("./magisk"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FsNode::Whiteout => {
|
FsNode::Whiteout => {
|
||||||
module_log!("delete", path.real, "null");
|
module_log!("delete", path.real(), "null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -349,34 +391,15 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Inject applet symlinks
|
// Inject applet symlinks
|
||||||
|
children.insert("su".to_string(), FsNode::MagiskLink);
|
||||||
children.insert(
|
children.insert("resetprop".to_string(), FsNode::MagiskLink);
|
||||||
"su".to_string(),
|
children.insert("supolicy".to_string(), FsNode::MagiskLink);
|
||||||
FsNode::Symlink {
|
|
||||||
target: Utf8CString::from("./magisk"),
|
|
||||||
is_magisk_bin: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
children.insert(
|
|
||||||
"resetprop".to_string(),
|
|
||||||
FsNode::Symlink {
|
|
||||||
target: Utf8CString::from("./magisk"),
|
|
||||||
is_magisk_bin: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
children.insert(
|
|
||||||
"supolicy".to_string(),
|
|
||||||
FsNode::Symlink {
|
|
||||||
target: Utf8CString::from("./magiskpolicy"),
|
|
||||||
is_magisk_bin: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip /system prefix to insert correct node
|
// Strip /system prefix to insert correct node
|
||||||
fn strip_system_prefix(orig_item: &str) -> String {
|
fn strip_system_prefix(orig_item: &str) -> String {
|
||||||
match orig_item.strip_prefix("/system/") {
|
match orig_item.strip_prefix("/system/") {
|
||||||
Some(rest) => format!("/{}", rest),
|
Some(rest) => format!("/{rest}"),
|
||||||
None => orig_item.to_string(),
|
None => orig_item.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +409,10 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
|||||||
|
|
||||||
for orig_item in path_env.split(':') {
|
for orig_item in path_env.split(':') {
|
||||||
// Filter not suitbale paths
|
// Filter not suitbale paths
|
||||||
if !MAGISK_BIN_INJECT_PARTITIONS.iter().any(|p| orig_item.starts_with(p.as_str())) {
|
if !MAGISK_BIN_INJECT_PARTITIONS
|
||||||
|
.iter()
|
||||||
|
.any(|p| orig_item.starts_with(p.as_str()))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Flatten apex path is not suitable too
|
// Flatten apex path is not suitable too
|
||||||
@@ -395,7 +421,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override existing su first
|
// Override existing su first
|
||||||
let su_path = Utf8CString::from(format!("{}/su", orig_item));
|
let su_path = Utf8CString::from(format!("{orig_item}/su"));
|
||||||
if su_path.exists() {
|
if su_path.exists() {
|
||||||
let item = strip_system_prefix(orig_item);
|
let item = strip_system_prefix(orig_item);
|
||||||
candidates.push((item, 0));
|
candidates.push((item, 0));
|
||||||
@@ -403,21 +429,25 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let path = Utf8CString::from(orig_item);
|
let path = Utf8CString::from(orig_item);
|
||||||
if let Ok(attr) = path.get_attr() && (attr.st.st_mode & 0x0001) != 0 {
|
if let Ok(attr) = path.get_attr()
|
||||||
if let Ok(mut dir) = Directory::open(&path) {
|
&& (attr.st.st_mode & 0x0001) != 0
|
||||||
let mut count = 0;
|
&& let Ok(mut dir) = Directory::open(&path)
|
||||||
if let Err(_) = dir.pre_order_walk(|e| {
|
{
|
||||||
|
let mut count = 0;
|
||||||
|
if dir
|
||||||
|
.pre_order_walk(|e| {
|
||||||
if e.is_file() {
|
if e.is_file() {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
Ok(WalkResult::Continue)
|
Ok(WalkResult::Continue)
|
||||||
}) {
|
})
|
||||||
// Skip, we cannot ensure the result is correct
|
.is_err()
|
||||||
continue;
|
{
|
||||||
}
|
// Skip, we cannot ensure the result is correct
|
||||||
let item = strip_system_prefix(orig_item);
|
continue;
|
||||||
candidates.push((item, count));
|
|
||||||
}
|
}
|
||||||
|
let item = strip_system_prefix(orig_item);
|
||||||
|
candidates.push((item, count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,40 +546,56 @@ fn inject_zygisk_bins(system: &mut FsNode, name: &str) {
|
|||||||
pub fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str) {
|
pub fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str) {
|
||||||
let mut system = FsNode::new_dir();
|
let mut system = FsNode::new_dir();
|
||||||
|
|
||||||
|
// Build all the base "prefix" paths
|
||||||
|
let mut root = cstr::buf::default().join_path("/");
|
||||||
|
|
||||||
|
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
|
||||||
|
|
||||||
|
let mut module_mnt = cstr::buf::default()
|
||||||
|
.join_path(get_magisk_tmp())
|
||||||
|
.join_path(MODULEMNT);
|
||||||
|
|
||||||
|
let mut worker = cstr::buf::default()
|
||||||
|
.join_path(get_magisk_tmp())
|
||||||
|
.join_path(WORKERDIR);
|
||||||
|
|
||||||
|
// Create a collection of all relevant paths
|
||||||
|
let mut root_paths = FilePaths {
|
||||||
|
real: PathTracker::from(&mut root),
|
||||||
|
worker: PathTracker::from(&mut worker),
|
||||||
|
module_mnt: PathTracker::from(&mut module_mnt),
|
||||||
|
module_root: PathTracker::from(&mut module_dir),
|
||||||
|
};
|
||||||
|
|
||||||
// Step 1: Create virtual filesystem tree
|
// Step 1: Create virtual filesystem tree
|
||||||
//
|
//
|
||||||
// In this step, there is zero logic applied during tree construction; we simply collect
|
// In this step, there is zero logic applied during tree construction; we simply collect
|
||||||
// and record the union of all module filesystem trees under each of their /system directory.
|
// and record the union of all module filesystem trees under each of their /system directory.
|
||||||
|
|
||||||
let mut path = cstr::buf::default()
|
|
||||||
.join_path(get_magisk_tmp())
|
|
||||||
.join_path(MODULEMNT);
|
|
||||||
let len = path.len();
|
|
||||||
for info in module_list {
|
for info in module_list {
|
||||||
path.truncate(len);
|
let mut module_paths = root_paths.append(&info.name);
|
||||||
path.append_path(&info.name);
|
{
|
||||||
|
// Read props
|
||||||
// Read props
|
let prop = module_paths.append("system.prop");
|
||||||
let module_path_len = path.len();
|
if prop.module().exists() {
|
||||||
path.append_path("system.prop");
|
// Do NOT go through property service as it could cause boot lock
|
||||||
if path.exists() {
|
load_prop_file(prop.module(), true);
|
||||||
// Do NOT go through property service as it could cause boot lock
|
}
|
||||||
load_prop_file(&path, true);
|
|
||||||
}
|
}
|
||||||
|
{
|
||||||
// Check whether skip mounting
|
// Check whether skip mounting
|
||||||
path.truncate(module_path_len);
|
let skip = module_paths.append("skip_mount");
|
||||||
path.append_path("skip_mount");
|
if skip.module().exists() {
|
||||||
if path.exists() {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
// Double check whether the system folder exists
|
// Double check whether the system folder exists
|
||||||
path.truncate(module_path_len);
|
let sys = module_paths.append("system");
|
||||||
path.append_path("system");
|
if sys.module().exists() {
|
||||||
if path.exists() {
|
info!("{}: loading module files", &info.name);
|
||||||
info!("{}: loading module files", &info.name);
|
system.collect(sys).log_ok();
|
||||||
system.build_from_path(&mut path).log_ok();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,16 +638,6 @@ pub fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str) {
|
|||||||
}
|
}
|
||||||
roots.insert("system", system);
|
roots.insert("system", system);
|
||||||
|
|
||||||
// Reuse the path buffer
|
|
||||||
path.clear();
|
|
||||||
path.push_str("/");
|
|
||||||
|
|
||||||
// Build the base worker directory path
|
|
||||||
let mut tmp = cstr::buf::default()
|
|
||||||
.join_path(get_magisk_tmp())
|
|
||||||
.join_path(WORKERDIR);
|
|
||||||
|
|
||||||
let mut tracker = PathTracker::from(&mut path, &mut tmp);
|
|
||||||
for (dir, mut root) in roots {
|
for (dir, mut root) in roots {
|
||||||
// Step 4: Convert virtual filesystem tree into concrete operations
|
// Step 4: Convert virtual filesystem tree into concrete operations
|
||||||
//
|
//
|
||||||
@@ -610,7 +646,7 @@ pub fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str) {
|
|||||||
// The "core" of the logic is to decide which directories need to be rebuilt in the
|
// The "core" of the logic is to decide which directories need to be rebuilt in the
|
||||||
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
|
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
|
||||||
|
|
||||||
let path = tracker.append(dir);
|
let path = root_paths.append(dir);
|
||||||
root.commit(path, true).log_ok();
|
root.commit(path, true).log_ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,37 +22,37 @@ pub fn setup_preinit_dir() {
|
|||||||
let dev_path = cstr::buf::new::<64>()
|
let dev_path = cstr::buf::new::<64>()
|
||||||
.join_path(magisk_tmp)
|
.join_path(magisk_tmp)
|
||||||
.join_path(PREINITDEV);
|
.join_path(PREINITDEV);
|
||||||
if let Ok(attr) = dev_path.get_attr() {
|
if let Ok(attr) = dev_path.get_attr()
|
||||||
if attr.st.st_mode & libc::S_IFMT as c_uint == libc::S_IFBLK.as_() {
|
&& attr.st.st_mode & libc::S_IFMT as c_uint == libc::S_IFBLK.as_()
|
||||||
// DO NOT mount the block device directly, as we do not know the flags and configs
|
{
|
||||||
// to properly mount the partition; mounting block devices directly as rw could cause
|
// DO NOT mount the block device directly, as we do not know the flags and configs
|
||||||
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
|
// to properly mount the partition; mounting block devices directly as rw could cause
|
||||||
// What we do instead is to scan through the current mountinfo and find a pre-existing
|
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
|
||||||
// mount point mounting our desired partition, and then bind mount the target folder.
|
// What we do instead is to scan through the current mountinfo and find a pre-existing
|
||||||
let preinit_dev = attr.st.st_rdev;
|
// mount point mounting our desired partition, and then bind mount the target folder.
|
||||||
let mnt_path = cstr::buf::default()
|
let preinit_dev = attr.st.st_rdev;
|
||||||
.join_path(magisk_tmp)
|
let mnt_path = cstr::buf::default()
|
||||||
.join_path(PREINITMIRR);
|
.join_path(magisk_tmp)
|
||||||
for info in parse_mount_info("self") {
|
.join_path(PREINITMIRR);
|
||||||
if info.root == "/" && info.device == preinit_dev {
|
for info in parse_mount_info("self") {
|
||||||
if !info.fs_option.split(',').any(|s| s == "rw") {
|
if info.root == "/" && info.device == preinit_dev {
|
||||||
// Only care about rw mounts
|
if !info.fs_option.split(',').any(|s| s == "rw") {
|
||||||
continue;
|
// Only care about rw mounts
|
||||||
}
|
continue;
|
||||||
let mut target = info.target;
|
}
|
||||||
let target = Utf8CStr::from_string(&mut target);
|
let mut target = info.target;
|
||||||
let mut preinit_dir = resolve_preinit_dir(target);
|
let target = Utf8CStr::from_string(&mut target);
|
||||||
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
|
let mut preinit_dir = resolve_preinit_dir(target);
|
||||||
let r: LoggedResult<()> = try {
|
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
|
||||||
preinit_dir.mkdir(0o700)?;
|
let r: LoggedResult<()> = try {
|
||||||
mnt_path.mkdirs(0o755)?;
|
preinit_dir.mkdir(0o700)?;
|
||||||
mnt_path.remove().ok();
|
mnt_path.mkdirs(0o755)?;
|
||||||
mnt_path.create_symlink_to(preinit_dir)?;
|
mnt_path.remove().ok();
|
||||||
};
|
mnt_path.create_symlink_to(preinit_dir)?;
|
||||||
if r.is_ok() {
|
};
|
||||||
info!("* Found preinit dir: {}", preinit_dir);
|
if r.is_ok() {
|
||||||
return;
|
info!("* Found preinit dir: {}", preinit_dir);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,10 +238,10 @@ pub fn revert_unmount(pid: i32) {
|
|||||||
let mut prev: Option<PathBuf> = None;
|
let mut prev: Option<PathBuf> = None;
|
||||||
targets.sort();
|
targets.sort();
|
||||||
targets.retain(|target| {
|
targets.retain(|target| {
|
||||||
if let Some(prev) = &prev {
|
if let Some(prev) = &prev
|
||||||
if Path::new(target).starts_with(prev) {
|
&& Path::new(target).starts_with(prev)
|
||||||
return false;
|
{
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
prev = Some(PathBuf::from(target.clone()));
|
prev = Some(PathBuf::from(target.clone()));
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -167,10 +167,10 @@ pub fn persist_get_props(mut prop_cb: Pin<&mut PropCb>) {
|
|||||||
} else {
|
} else {
|
||||||
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
|
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
|
||||||
dir.pre_order_walk(|e| {
|
dir.pre_order_walk(|e| {
|
||||||
if e.is_file() {
|
if e.is_file()
|
||||||
if let Ok(mut value) = file_get_prop(e.name()) {
|
&& let Ok(mut value) = file_get_prop(e.name())
|
||||||
prop_cb.exec(e.name(), Utf8CStr::from_string(&mut value));
|
{
|
||||||
}
|
prop_cb.exec(e.name(), Utf8CStr::from_string(&mut value));
|
||||||
}
|
}
|
||||||
// Do not traverse recursively
|
// Do not traverse recursively
|
||||||
Ok(WalkResult::Skip)
|
Ok(WalkResult::Skip)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::consts::{DATABIN, LOG_PIPE, MAGISK_LOG_CON, MODULEROOT, SECURE_DIR};
|
use crate::consts::{DATABIN, LOG_PIPE, MAGISK_LOG_CON, MAGISKDB, MODULEROOT, SECURE_DIR};
|
||||||
use crate::ffi::get_magisk_tmp;
|
use crate::ffi::get_magisk_tmp;
|
||||||
use base::libc::{O_CLOEXEC, O_WRONLY};
|
use base::libc::{O_CLOEXEC, O_WRONLY};
|
||||||
use base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc};
|
use base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc};
|
||||||
@@ -55,10 +55,9 @@ pub(crate) fn restorecon() {
|
|||||||
if let Ok(mut file) = cstr!("/sys/fs/selinux/context")
|
if let Ok(mut file) = cstr!("/sys/fs/selinux/context")
|
||||||
.open(O_WRONLY | O_CLOEXEC)
|
.open(O_WRONLY | O_CLOEXEC)
|
||||||
.log()
|
.log()
|
||||||
|
&& file.write_all(ADB_CON.as_bytes_with_nul()).is_ok()
|
||||||
{
|
{
|
||||||
if file.write_all(ADB_CON.as_bytes_with_nul()).is_ok() {
|
cstr!(SECURE_DIR).set_secontext(ADB_CON).log_ok();
|
||||||
cstr!(SECURE_DIR).set_secontext(ADB_CON).log_ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut path = cstr::buf::default();
|
let mut path = cstr::buf::default();
|
||||||
@@ -70,6 +69,7 @@ pub(crate) fn restorecon() {
|
|||||||
path.clear();
|
path.clear();
|
||||||
path.push_str(DATABIN);
|
path.push_str(DATABIN);
|
||||||
restore_syscon(&mut path).log_ok();
|
restore_syscon(&mut path).log_ok();
|
||||||
|
unsafe { libc::chmod(cstr!(MAGISKDB).as_ptr(), 0o000) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn restore_tmpcon() -> LoggedResult<()> {
|
pub(crate) fn restore_tmpcon() -> LoggedResult<()> {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ impl Default for SuRequest {
|
|||||||
target_pid: -1,
|
target_pid: -1,
|
||||||
login: false,
|
login: false,
|
||||||
keep_env: false,
|
keep_env: false,
|
||||||
|
drop_cap: false,
|
||||||
shell: DEFAULT_SHELL.to_string(),
|
shell: DEFAULT_SHELL.to_string(),
|
||||||
command: "".to_string(),
|
command: "".to_string(),
|
||||||
context: "".to_string(),
|
context: "".to_string(),
|
||||||
@@ -168,7 +169,10 @@ impl MagiskD {
|
|||||||
// Before unlocking, refresh the timestamp
|
// Before unlocking, refresh the timestamp
|
||||||
access.refresh();
|
access.refresh();
|
||||||
|
|
||||||
// Fail fast
|
if access.settings.policy == SuPolicy::Restrict {
|
||||||
|
req.drop_cap = true;
|
||||||
|
}
|
||||||
|
|
||||||
if access.settings.policy == SuPolicy::Deny {
|
if access.settings.policy == SuPolicy::Deny {
|
||||||
warn!("su: request rejected ({})", info.uid);
|
warn!("su: request rejected ({})", info.uid);
|
||||||
client.write_pod(&SuPolicy::Deny.repr).ok();
|
client.write_pod(&SuPolicy::Deny.repr).ok();
|
||||||
|
|||||||
@@ -52,13 +52,12 @@ fn set_stdin_raw() -> bool {
|
|||||||
|
|
||||||
pub fn restore_stdin() -> bool {
|
pub fn restore_stdin() -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(ref termios) = OLD_STDIN {
|
if let Some(ref termios) = OLD_STDIN
|
||||||
if tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0
|
&& tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0
|
||||||
&& tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0
|
&& tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0
|
||||||
{
|
{
|
||||||
warn!("Failed to restore terminal attributes");
|
warn!("Failed to restore terminal attributes");
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
OLD_STDIN = None;
|
OLD_STDIN = None;
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
#include <linux/securebits.h>
|
||||||
|
#include <sys/capability.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -46,6 +49,7 @@ int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGI
|
|||||||
" as a primary group if the option -g is not specified\n"
|
" as a primary group if the option -g is not specified\n"
|
||||||
" -Z, --context CONTEXT Change SELinux context\n"
|
" -Z, --context CONTEXT Change SELinux context\n"
|
||||||
" -t, --target PID PID to take mount namespace from\n"
|
" -t, --target PID PID to take mount namespace from\n"
|
||||||
|
" -d, --drop-cap Drop all Linux capabilities\n"
|
||||||
" -h, --help Display this help message and exit\n"
|
" -h, --help Display this help message and exit\n"
|
||||||
" -, -l, --login Pretend the shell to be a login shell\n"
|
" -, -l, --login Pretend the shell to be a login shell\n"
|
||||||
" -m, -p,\n"
|
" -m, -p,\n"
|
||||||
@@ -105,6 +109,7 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
{ "group", required_argument, nullptr, 'g' },
|
{ "group", required_argument, nullptr, 'g' },
|
||||||
{ "supp-group", required_argument, nullptr, 'G' },
|
{ "supp-group", required_argument, nullptr, 'G' },
|
||||||
{ "interactive", no_argument, nullptr, 'i' },
|
{ "interactive", no_argument, nullptr, 'i' },
|
||||||
|
{ "drop-cap", no_argument, nullptr, 'd' },
|
||||||
{ nullptr, 0, nullptr, 0 },
|
{ nullptr, 0, nullptr, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,7 +126,7 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
bool interactive = false;
|
bool interactive = false;
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "c:hlimps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
|
while ((c = getopt_long(argc, argv, "c:hlimpds:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'c': {
|
case 'c': {
|
||||||
string command;
|
string command;
|
||||||
@@ -146,6 +151,9 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
case 'p':
|
case 'p':
|
||||||
req.keep_env = true;
|
req.keep_env = true;
|
||||||
break;
|
break;
|
||||||
|
case 'd':
|
||||||
|
req.drop_cap = true;
|
||||||
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
req.shell = optarg;
|
req.shell = optarg;
|
||||||
break;
|
break;
|
||||||
@@ -259,11 +267,66 @@ int su_client_main(int argc, char *argv[]) {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root
|
static void drop_caps() {
|
||||||
static void set_identity(int uid, const rust::Vec<gid_t> &groups) {
|
static auto last_valid_cap = []() {
|
||||||
if (seteuid(0)) {
|
uint32_t cap = CAP_WAKE_ALARM;
|
||||||
PLOGE("seteuid (root)");
|
while (prctl(PR_CAPBSET_READ, cap) >= 0) {
|
||||||
|
cap++;
|
||||||
|
}
|
||||||
|
return cap - 1;
|
||||||
|
}();
|
||||||
|
// Drop bounding set
|
||||||
|
for (uint32_t cap = 0; cap <= last_valid_cap; cap++) {
|
||||||
|
if (cap != CAP_SETUID) {
|
||||||
|
prctl(PR_CAPBSET_DROP, cap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Clean inheritable set
|
||||||
|
__user_cap_header_struct header = {.version = _LINUX_CAPABILITY_VERSION_3};
|
||||||
|
__user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3] = {};
|
||||||
|
if (capget(&header, &data[0]) == 0) {
|
||||||
|
for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {
|
||||||
|
data[i].inheritable = 0;
|
||||||
|
}
|
||||||
|
capset(&header, &data[0]);
|
||||||
|
}
|
||||||
|
// All capabilities will be lost after exec
|
||||||
|
prctl(PR_SET_SECUREBITS, SECBIT_NOROOT);
|
||||||
|
// Except CAP_SETUID in bounding set, it is a marker for restricted process
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool proc_is_restricted(pid_t pid) {
|
||||||
|
char buf[32] = {};
|
||||||
|
auto bnd = "CapBnd:"sv;
|
||||||
|
uint32_t data[_LINUX_CAPABILITY_U32S_3] = {};
|
||||||
|
ssprintf(buf, sizeof(buf), "/proc/%d/status", pid);
|
||||||
|
file_readline(buf, [&](string_view line) -> bool {
|
||||||
|
if (line.starts_with(bnd)) {
|
||||||
|
auto p = line.begin();
|
||||||
|
advance(p, bnd.size());
|
||||||
|
while (isspace(*p)) advance(p, 1);
|
||||||
|
line.remove_prefix(distance(line.begin(), p));
|
||||||
|
for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {
|
||||||
|
auto cap = line.substr((_LINUX_CAPABILITY_U32S_3 - 1 - i) * 8, 8);
|
||||||
|
data[i] = parse_uint32_hex(cap);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
bool equal = true;
|
||||||
|
for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {
|
||||||
|
if (i == CAP_TO_INDEX(CAP_SETUID)) {
|
||||||
|
if (data[i] != CAP_TO_MASK(CAP_SETUID)) equal = false;
|
||||||
|
} else {
|
||||||
|
if (data[i] != 0) equal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_identity(int uid, const rust::Vec<gid_t> &groups) {
|
||||||
gid_t gid;
|
gid_t gid;
|
||||||
if (!groups.empty()) {
|
if (!groups.empty()) {
|
||||||
if (setgroups(groups.size(), groups.data())) {
|
if (setgroups(groups.size(), groups.data())) {
|
||||||
@@ -404,15 +467,21 @@ void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unblock all signals
|
// Config privileges
|
||||||
sigset_t block_set;
|
|
||||||
sigemptyset(&block_set);
|
|
||||||
sigprocmask(SIG_SETMASK, &block_set, nullptr);
|
|
||||||
if (!req.context.empty()) {
|
if (!req.context.empty()) {
|
||||||
auto f = xopen_file("/proc/self/attr/exec", "we");
|
auto f = xopen_file("/proc/self/attr/exec", "we");
|
||||||
if (f) fprintf(f.get(), "%s", req.context.c_str());
|
if (f) fprintf(f.get(), "%s", req.context.c_str());
|
||||||
}
|
}
|
||||||
set_identity(req.target_uid, req.gids);
|
if (req.target_uid != AID_ROOT || req.drop_cap || proc_is_restricted(pid))
|
||||||
|
drop_caps();
|
||||||
|
if (req.target_uid != AID_ROOT || req.gids.size() > 0)
|
||||||
|
set_identity(req.target_uid, req.gids);
|
||||||
|
|
||||||
|
// Unblock all signals
|
||||||
|
sigset_t block_set;
|
||||||
|
sigemptyset(&block_set);
|
||||||
|
sigprocmask(SIG_SETMASK, &block_set, nullptr);
|
||||||
|
|
||||||
execvp(req.shell.c_str(), (char **) argv);
|
execvp(req.shell.c_str(), (char **) argv);
|
||||||
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
|
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
|
||||||
PLOGE("exec");
|
PLOGE("exec");
|
||||||
|
|||||||
@@ -170,10 +170,10 @@ impl MagiskD {
|
|||||||
client.write_pod(&flags)?;
|
client.write_pod(&flags)?;
|
||||||
|
|
||||||
// Next send modules
|
// Next send modules
|
||||||
if zygisk_should_load_module(flags) {
|
if zygisk_should_load_module(flags)
|
||||||
if let Some(module_fds) = self.get_module_fds(is_64_bit) {
|
&& let Some(module_fds) = self.get_module_fds(is_64_bit)
|
||||||
client.send_fds(&module_fds)?;
|
{
|
||||||
}
|
client.send_fds(&module_fds)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're not in system_server, we are done
|
// If we're not in system_server, we are done
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ impl<T, E: Display> ResultExt<T> for Result<T, E> {
|
|||||||
match self {
|
match self {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("error occurred: {}", e);
|
eprintln!("error occurred: {e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,6 @@ pub fn gen_cxx_binding(name: &str) {
|
|||||||
println!("cargo:rerun-if-changed=lib.rs");
|
println!("cargo:rerun-if-changed=lib.rs");
|
||||||
let opt = Opt::default();
|
let opt = Opt::default();
|
||||||
let code = cxx_gen::generate_header_and_cc_with_path("lib.rs", &opt);
|
let code = cxx_gen::generate_header_and_cc_with_path("lib.rs", &opt);
|
||||||
write_if_diff(format!("{}.cpp", name), code.implementation.as_slice()).ok_or_exit();
|
write_if_diff(format!("{name}.cpp"), code.implementation.as_slice()).ok_or_exit();
|
||||||
write_if_diff(format!("{}.hpp", name), code.header.as_slice()).ok_or_exit();
|
write_if_diff(format!("{name}.hpp"), code.header.as_slice()).ok_or_exit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub const LOGFILE: &str = "/cache/magisk.log";
|
|||||||
pub const SECURE_DIR: &str = "/data/adb";
|
pub const SECURE_DIR: &str = "/data/adb";
|
||||||
pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules");
|
pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules");
|
||||||
pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
|
pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
|
||||||
|
pub const MAGISKDB: &str = concatcp!(SECURE_DIR, "/magisk.db");
|
||||||
|
|
||||||
// tmpfs paths
|
// tmpfs paths
|
||||||
const INTERNAL_DIR: &str = ".magisk";
|
const INTERNAL_DIR: &str = ".magisk";
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fn print_usage(cmd: &str) {
|
|||||||
eprintln!(
|
eprintln!(
|
||||||
r#"MagiskPolicy - SELinux Policy Patch Tool
|
r#"MagiskPolicy - SELinux Policy Patch Tool
|
||||||
|
|
||||||
Usage: {} [--options...] [policy statements...]
|
Usage: {cmd} [--options...] [policy statements...]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--help show help message for policy statements
|
--help show help message for policy statements
|
||||||
@@ -60,8 +60,7 @@ Options:
|
|||||||
|
|
||||||
If neither --load, --load-split, nor --compile-split is specified,
|
If neither --load, --load-split, nor --compile-split is specified,
|
||||||
it will load from current live policies (/sys/fs/selinux/policy)
|
it will load from current live policies (/sys/fs/selinux/policy)
|
||||||
"#,
|
"#
|
||||||
cmd
|
|
||||||
);
|
);
|
||||||
|
|
||||||
format_statement_help(&mut FmtAdaptor(&mut stderr())).ok();
|
format_statement_help(&mut FmtAdaptor(&mut stderr())).ok();
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ impl Display for Token<'_> {
|
|||||||
Token::ST => f.write_char('*'),
|
Token::ST => f.write_char('*'),
|
||||||
Token::TL => f.write_char('~'),
|
Token::TL => f.write_char('~'),
|
||||||
Token::HP => f.write_char('-'),
|
Token::HP => f.write_char('-'),
|
||||||
Token::HX(n) => f.write_fmt(format_args!("{:06X}", n)),
|
Token::HX(n) => f.write_fmt(format_args!("{n:06X}")),
|
||||||
Token::ID(s) => f.write_str(s),
|
Token::ID(s) => f.write_str(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user