mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-11 15:12:24 +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.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import androidx.databinding.InverseMethod
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
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.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.utils.TextHolder
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.widget.IndeterminateCheckBox
|
||||
@@ -306,3 +309,38 @@ fun TextView.setText(text: TextHolder) {
|
||||
fun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) {
|
||||
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 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+
|
||||
list.remove(Tapjack)
|
||||
}
|
||||
if (Const.Version.atLeast_30_1()) {
|
||||
list.add(Restrict)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
|
||||
@@ -4,11 +4,13 @@ import android.graphics.drawable.Drawable
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import com.topjohnwu.magisk.databinding.DiffItem
|
||||
import com.topjohnwu.magisk.databinding.ItemWrapper
|
||||
import com.topjohnwu.magisk.databinding.ObservableRvItem
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.core.R as CoreR
|
||||
|
||||
class PolicyRvItem(
|
||||
private val viewModel: SuperuserViewModel,
|
||||
@@ -33,14 +35,34 @@ class PolicyRvItem(
|
||||
var isExpanded = false
|
||||
set(value) = set(value, field, { field = it }, BR.expanded)
|
||||
|
||||
val showSlider = Config.suRestrict || item.policy == SuPolicy.RESTRICT
|
||||
|
||||
@get:Bindable
|
||||
var isEnabled
|
||||
get() = item.policy == SuPolicy.ALLOW
|
||||
get() = item.policy >= SuPolicy.ALLOW
|
||||
set(value) = setImpl(value, isEnabled) {
|
||||
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
|
||||
var shouldNotify
|
||||
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 }
|
||||
fun updateState() {
|
||||
viewModelScope.launch {
|
||||
val res = if (enable) R.string.su_snack_grant else R.string.su_snack_deny
|
||||
item.item.policy = if (enable) SuPolicy.ALLOW else SuPolicy.DENY
|
||||
val res = if (policy >= SuPolicy.ALLOW) R.string.su_snack_grant else R.string.su_snack_deny
|
||||
item.item.policy = policy
|
||||
db.update(item.item)
|
||||
items.forEach {
|
||||
it.notifyPropertyChanged(BR.enabled)
|
||||
it.notifyPropertyChanged(BR.sliderValue)
|
||||
}
|
||||
SnackbarEvent(res.asText(item.appName)).publish()
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<include
|
||||
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}"
|
||||
isSelected="@{item.log.action != 2}"
|
||||
isTop="@{item.isTop}"
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.topjohnwu.magisk.databinding.DataBindingAdaptersKt" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.ui.superuser.PolicyRvItem" />
|
||||
@@ -85,16 +87,32 @@
|
||||
app:layout_constraintVertical_bias="0"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
<FrameLayout
|
||||
android:id="@+id/policy_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/l1"
|
||||
android:checked="@={item.enabled}"
|
||||
android:nextFocusLeft="@id/policy"
|
||||
app:layout_constraintBottom_toBottomOf="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>
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ object Config : PreferenceConfig, DBConfig {
|
||||
const val SU_NOTIFICATION = "su_notification"
|
||||
const val SU_REAUTH = "su_reauth"
|
||||
const val SU_TAPJACK = "su_tapjack"
|
||||
const val SU_RESTRICT = "su_restrict"
|
||||
const val CHECK_UPDATES = "check_update"
|
||||
const val RELEASE_CHANNEL = "release_channel"
|
||||
const val CUSTOM_CHANNEL = "custom_channel"
|
||||
@@ -147,6 +148,7 @@ object Config : PreferenceConfig, DBConfig {
|
||||
}
|
||||
var suReAuth by preference(Key.SU_REAUTH, false)
|
||||
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 UPDATE_CHANNEL = "update_channel"
|
||||
|
||||
@@ -26,9 +26,11 @@ object Const {
|
||||
const val MIN_VERSION = "v22.0"
|
||||
const val MIN_VERCODE = 22000
|
||||
|
||||
fun atLeast_24_0() = Info.env.versionCode >= 24000
|
||||
fun atLeast_25_0() = Info.env.versionCode >= 25000
|
||||
fun atLeast_28_0() = Info.env.versionCode >= 28000
|
||||
private fun isCanary() = (Info.env.versionCode % 100) != 0
|
||||
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
|
||||
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 {
|
||||
|
||||
@@ -4,15 +4,16 @@ import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
|
||||
|
||||
class SuPolicy(
|
||||
val uid: Int,
|
||||
var policy: Int = INTERACTIVE,
|
||||
var policy: Int = QUERY,
|
||||
var remain: Long = -1L,
|
||||
var logging: Boolean = true,
|
||||
var notification: Boolean = true,
|
||||
) {
|
||||
companion object {
|
||||
const val INTERACTIVE = 0
|
||||
const val QUERY = 0
|
||||
const val DENY = 1
|
||||
const val ALLOW = 2
|
||||
const val RESTRICT = 3
|
||||
}
|
||||
|
||||
fun toMap(): MutableMap<String, Any> {
|
||||
|
||||
@@ -70,7 +70,7 @@ object SuCallbackHandler {
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
||||
|
||||
if (notify)
|
||||
notify(context, log.action == SuPolicy.ALLOW, log.appName)
|
||||
notify(context, log.action >= SuPolicy.ALLOW, log.appName)
|
||||
|
||||
runBlocking { ServiceLocator.logRepo.insert(log) }
|
||||
}
|
||||
@@ -86,7 +86,7 @@ object SuCallbackHandler {
|
||||
pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)
|
||||
}.getOrNull() ?: "[UID] $uid"
|
||||
|
||||
notify(context, policy == SuPolicy.ALLOW, appName)
|
||||
notify(context, policy >= SuPolicy.ALLOW, appName)
|
||||
}
|
||||
|
||||
private fun notify(context: Context, granted: Boolean, appName: String) {
|
||||
|
||||
@@ -82,7 +82,11 @@ class SuRequestHandler(
|
||||
}
|
||||
|
||||
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) {
|
||||
policy.remain = TimeUnit.MINUTES.toSeconds(time)
|
||||
} else {
|
||||
|
||||
@@ -55,7 +55,7 @@ class AdditionalTest : BaseTest {
|
||||
|
||||
@Test
|
||||
fun testModuleCount() {
|
||||
var expected = 2
|
||||
var expected = 3
|
||||
if (Environment.mount()) expected++
|
||||
if (Environment.preinit()) expected++
|
||||
if (Environment.lsposed()) expected++
|
||||
@@ -90,8 +90,8 @@ class AdditionalTest : BaseTest {
|
||||
|
||||
assertNotNull("$MOUNT_TEST is not installed", modules.find { it.id == MOUNT_TEST })
|
||||
assertTrue(
|
||||
"/system/etc/newfile should exist",
|
||||
RootUtils.fs.getFile("/system/etc/newfile").exists()
|
||||
"/system/fonts/newfile should exist",
|
||||
RootUtils.fs.getFile("/system/fonts/newfile").exists()
|
||||
)
|
||||
assertFalse(
|
||||
"/system/bin/screenrecord should not exist",
|
||||
|
||||
@@ -98,8 +98,8 @@ class Environment : BaseTest {
|
||||
val error = "$MOUNT_TEST setup failed"
|
||||
val path = root.getChildFile(MOUNT_TEST)
|
||||
|
||||
// Create /system/etc/newfile
|
||||
val etc = path.getChildFile("system").getChildFile("etc")
|
||||
// Create /system/fonts/newfile
|
||||
val etc = path.getChildFile("system").getChildFile("fonts")
|
||||
assertTrue(error, etc.mkdirs())
|
||||
assertTrue(error, etc.getChildFile("newfile").createNewFile())
|
||||
|
||||
@@ -116,6 +116,10 @@ class Environment : BaseTest {
|
||||
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) {
|
||||
val error = "$SEPOLICY_RULE setup failed"
|
||||
val path = root.getChildFile(SEPOLICY_RULE)
|
||||
@@ -215,6 +219,7 @@ class Environment : BaseTest {
|
||||
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
|
||||
if (mount()) { setupMountTest(root) }
|
||||
if (preinit()) { setupSepolicyRuleModule(root) }
|
||||
setupSystemlessHost()
|
||||
setupEmptyZygiskModule(root)
|
||||
setupInvalidZygiskModule(root)
|
||||
setupRemoveModule(root)
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<string name="touch_filtered_warning">由于某个应用遮挡了超级用户请求界面,因此 Magisk 无法验证您的回应</string>
|
||||
<string name="deny">拒绝</string>
|
||||
<string name="prompt">提示</string>
|
||||
<string name="restrict">受限</string>
|
||||
<string name="grant">允许</string>
|
||||
<string name="su_warning">将授予对该设备的最高权限。\n如果不确定,请拒绝!</string>
|
||||
<string name="forever">永久</string>
|
||||
@@ -173,6 +174,8 @@
|
||||
<string name="settings_su_auth_title">身份验证</string>
|
||||
<string name="settings_su_auth_summary">对超级用户请求验证身份</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="setting_add_shortcut_summary">在隐藏后难以识别名称和图标的情况下,添加快捷方式到桌面</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="deny">Deny</string>
|
||||
<string name="prompt">Prompt</string>
|
||||
<string name="restrict">Restrict</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="forever">Forever</string>
|
||||
@@ -170,6 +171,8 @@
|
||||
<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_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="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>
|
||||
|
||||
@@ -30,4 +30,4 @@ android.nonFinalResIds=false
|
||||
|
||||
# Magisk
|
||||
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 😉
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
@@ -583,7 +583,7 @@ impl<S: Utf8CStrBuf + Sized> FsPathBuilder for S {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -595,7 +595,7 @@ impl FsPathBuilder for dyn Utf8CStrBuf + '_ {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -846,7 +846,7 @@ fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
|
||||
|
||||
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
|
||||
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) {
|
||||
BufReader::new(file).foreach_lines(|line| {
|
||||
parse_mount_info_line(line)
|
||||
|
||||
@@ -184,6 +184,10 @@ int parse_int(string_view 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 ret = -1;
|
||||
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> to);
|
||||
|
||||
uint32_t parse_uint32_hex(std::string_view s);
|
||||
int parse_int(std::string_view s);
|
||||
|
||||
using thread_entry = void *(*)(void *);
|
||||
|
||||
@@ -99,7 +99,7 @@ impl<T> EarlyExitExt<T> for Result<T, EarlyExit> {
|
||||
exit(0)
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("{}", output);
|
||||
eprintln!("{output}");
|
||||
print_help_msg();
|
||||
exit(1)
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ impl<T, E: Display> Loggable<T> for Result<T, E> {
|
||||
write!(w, "[{}:{}] ", caller.file(), caller.line())?;
|
||||
}
|
||||
f(w)?;
|
||||
writeln!(w, ": {:#}", e)
|
||||
writeln!(w, ": {e:#}")
|
||||
});
|
||||
Err(LoggedError::default())
|
||||
}
|
||||
@@ -366,7 +366,7 @@ impl Display for OsError<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let error = self.as_io_error();
|
||||
if self.name.is_empty() {
|
||||
write!(f, "{:#}", error)
|
||||
write!(f, "{error:#}")
|
||||
} else {
|
||||
match (self.arg1.ok(), self.arg2.ok()) {
|
||||
(Some(arg1), Some(arg2)) => {
|
||||
|
||||
@@ -269,13 +269,13 @@ impl Cpio {
|
||||
}
|
||||
|
||||
fn load_from_file(path: &Utf8CStr) -> LoggedResult<Self> {
|
||||
eprintln!("Loading cpio: [{}]", path);
|
||||
eprintln!("Loading cpio: [{path}]");
|
||||
let file = MappedFile::open(path)?;
|
||||
Self::load_from_data(file.as_ref())
|
||||
}
|
||||
|
||||
fn dump(&self, path: &str) -> LoggedResult<()> {
|
||||
eprintln!("Dumping cpio: [{}]", path);
|
||||
eprintln!("Dumping cpio: [{path}]");
|
||||
let mut file = File::create(path)?;
|
||||
let mut pos = 0usize;
|
||||
let mut inode = 300000i64;
|
||||
@@ -320,13 +320,13 @@ impl Cpio {
|
||||
fn rm(&mut self, path: &str, recursive: bool) {
|
||||
let path = norm_path(path);
|
||||
if self.entries.remove(&path).is_some() {
|
||||
eprintln!("Removed entry [{}]", path);
|
||||
eprintln!("Removed entry [{path}]");
|
||||
}
|
||||
if recursive {
|
||||
let path = path + "/";
|
||||
self.entries.retain(|k, _| {
|
||||
if k.starts_with(&path) {
|
||||
eprintln!("Removed entry [{}]", k);
|
||||
eprintln!("Removed entry [{k}]");
|
||||
false
|
||||
} else {
|
||||
true
|
||||
@@ -340,7 +340,7 @@ impl Cpio {
|
||||
.entries
|
||||
.get(path)
|
||||
.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);
|
||||
|
||||
@@ -435,7 +435,7 @@ impl Cpio {
|
||||
data: content,
|
||||
}),
|
||||
);
|
||||
eprintln!("Add file [{}] ({:04o})", path, mode);
|
||||
eprintln!("Add file [{path}] ({mode:04o})");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ impl Cpio {
|
||||
data: vec![],
|
||||
}),
|
||||
);
|
||||
eprintln!("Create directory [{}] ({:04o})", dir, mode);
|
||||
eprintln!("Create directory [{dir}] ({mode:04o})");
|
||||
}
|
||||
|
||||
fn ln(&mut self, src: &str, dst: &str) {
|
||||
@@ -466,7 +466,7 @@ impl Cpio {
|
||||
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<()> {
|
||||
@@ -475,7 +475,7 @@ impl Cpio {
|
||||
.remove(&norm_path(from))
|
||||
.ok_or_else(|| log_err!("no such entry {}", from))?;
|
||||
self.entries.insert(norm_path(to), entry);
|
||||
eprintln!("Move [{}] -> [{}]", from, to);
|
||||
eprintln!("Move [{from}] -> [{to}]");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ impl Cpio {
|
||||
if !recursive && !p.is_empty() && p.matches('/').count() > 1 {
|
||||
continue;
|
||||
}
|
||||
println!("{}\t{}", entry, name);
|
||||
println!("{entry}\t{name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,8 +511,7 @@ impl Cpio {
|
||||
let keep_verity = check_env("KEEPVERITY");
|
||||
let keep_force_encrypt = check_env("KEEPFORCEENCRYPT");
|
||||
eprintln!(
|
||||
"Patch with flag KEEPVERITY=[{}] KEEPFORCEENCRYPT=[{}]",
|
||||
keep_verity, keep_force_encrypt
|
||||
"Patch with flag KEEPVERITY=[{keep_verity}] KEEPFORCEENCRYPT=[{keep_force_encrypt}]"
|
||||
);
|
||||
self.entries.retain(|name, entry| {
|
||||
let fstab = (!keep_verity || !keep_force_encrypt)
|
||||
@@ -523,7 +522,7 @@ impl Cpio {
|
||||
&& name.starts_with("fstab");
|
||||
if !keep_verity {
|
||||
if fstab {
|
||||
eprintln!("Found fstab file [{}]", name);
|
||||
eprintln!("Found fstab file [{name}]");
|
||||
let len = patch_verity(entry.data.as_mut_slice());
|
||||
if len != entry.data.len() {
|
||||
entry.data.resize(len, 0);
|
||||
@@ -581,7 +580,7 @@ impl Cpio {
|
||||
} else {
|
||||
&name[8..]
|
||||
};
|
||||
eprintln!("Restore [{}] -> [{}]", name, new_name);
|
||||
eprintln!("Restore [{name}] -> [{new_name}]");
|
||||
backups.insert(new_name.to_string(), entry);
|
||||
}
|
||||
});
|
||||
@@ -658,16 +657,16 @@ impl Cpio {
|
||||
match action {
|
||||
Action::Backup(name, mut entry) => {
|
||||
let backup = if !skip_compress && entry.compress() {
|
||||
format!(".backup/{}.xz", name)
|
||||
format!(".backup/{name}.xz")
|
||||
} else {
|
||||
format!(".backup/{}", name)
|
||||
format!(".backup/{name}")
|
||||
};
|
||||
eprintln!("Backup [{}] -> [{}]", name, backup);
|
||||
eprintln!("Backup [{name}] -> [{backup}]");
|
||||
backups.insert(backup, entry);
|
||||
}
|
||||
Action::Record(name) => {
|
||||
eprintln!("Record new entry: [{}] -> [.backup/.rmlist]", name);
|
||||
rm_list.push_str(&format!("{}\0", name));
|
||||
eprintln!("Record new entry: [{name}] -> [.backup/.rmlist]");
|
||||
rm_list.push_str(&format!("{name}\0"));
|
||||
}
|
||||
Action::Noop => {}
|
||||
}
|
||||
|
||||
@@ -131,9 +131,9 @@ fn print_node(node: &FdtNode) {
|
||||
}
|
||||
);
|
||||
} else if size > MAX_PRINT_LEN {
|
||||
println!("[{}]: <bytes>({})", name, size);
|
||||
println!("[{name}]: <bytes>({size})");
|
||||
} else {
|
||||
println!("[{}]: {:02x?}", name, value);
|
||||
println!("[{name}]: {value:02x?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
|
||||
rw: bool,
|
||||
mut f: F,
|
||||
) -> LoggedResult<()> {
|
||||
eprintln!("Loading dtbs from [{}]", file);
|
||||
eprintln!("Loading dtbs from [{file}]");
|
||||
let file = if rw {
|
||||
MappedFile::open_rw(file)?
|
||||
} else {
|
||||
@@ -173,7 +173,7 @@ fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
|
||||
}
|
||||
let fdt = match Fdt::new(slice) {
|
||||
Err(FdtError::BufferTooSmall) => {
|
||||
eprintln!("dtb.{:04} is truncated", dtb_num);
|
||||
eprintln!("dtb.{dtb_num:04} is truncated");
|
||||
break;
|
||||
}
|
||||
Ok(fdt) => fdt,
|
||||
@@ -198,11 +198,11 @@ fn dtb_print(file: &Utf8CStr, fstab: bool) -> LoggedResult<()> {
|
||||
for_each_fdt(file, false, |n, fdt| {
|
||||
if fstab {
|
||||
if let Some(fstab) = find_fstab(&fdt) {
|
||||
eprintln!("Found fstab in dtb.{:04}", n);
|
||||
eprintln!("Found fstab in dtb.{n:04}");
|
||||
print_node(&fstab);
|
||||
}
|
||||
} else if let Some(mut root) = fdt.find_node("/") {
|
||||
eprintln!("Printing dtb.{:04}", n);
|
||||
eprintln!("Printing dtb.{n:04}");
|
||||
if root.name.is_empty() {
|
||||
root.name = "/";
|
||||
}
|
||||
@@ -248,7 +248,7 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
|
||||
&mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(w).get()
|
||||
};
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
// SAFETY: all matching patterns are ASCII bytes
|
||||
let skipped = std::str::from_utf8_unchecked(skipped);
|
||||
eprintln!("Remove pattern [{}]", skipped);
|
||||
eprintln!("Remove pattern [{skipped}]");
|
||||
sz -= len;
|
||||
read += len;
|
||||
} 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());
|
||||
for off in &v {
|
||||
eprintln!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
|
||||
eprintln!("Patch @ {off:#010X} [{from}] -> [{to}]");
|
||||
}
|
||||
!v.is_empty()
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ fn do_extract_boot_from_payload(
|
||||
let mut reader = BufReader::new(if in_path == "-" {
|
||||
unsafe { File::from_raw_fd(0) }
|
||||
} 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];
|
||||
@@ -107,7 +107,7 @@ fn do_extract_boot_from_payload(
|
||||
};
|
||||
|
||||
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
|
||||
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) {
|
||||
buf.clear();
|
||||
buf.write_fmt(format_args!("{}", pid)).ok();
|
||||
buf.write_fmt(format_args!("{pid}")).ok();
|
||||
file.write_all(buf.as_bytes()).log_ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ pub mod ffi {
|
||||
Query,
|
||||
Deny,
|
||||
Allow,
|
||||
Restrict,
|
||||
}
|
||||
|
||||
struct ModuleInfo {
|
||||
@@ -117,6 +118,7 @@ pub mod ffi {
|
||||
target_pid: i32,
|
||||
login: bool,
|
||||
keep_env: bool,
|
||||
drop_cap: bool,
|
||||
shell: String,
|
||||
command: 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]);
|
||||
if let Err(ref e) = result {
|
||||
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();
|
||||
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) {
|
||||
let fd = MAGISK_LOGD_FD.lock().unwrap().clone();
|
||||
if let Some(logd) = fd {
|
||||
if f(&logd).is_err() {
|
||||
// If any error occurs, shut down the logd pipe
|
||||
*MAGISK_LOGD_FD.lock().unwrap() = None;
|
||||
}
|
||||
if let Some(logd) = fd
|
||||
&& f(&logd).is_err()
|
||||
{
|
||||
// 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::load_prop_file;
|
||||
use base::{
|
||||
@@ -9,8 +9,12 @@ use libc::{MS_RDONLY, O_CLOEXEC, O_CREAT, O_RDONLY};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Component, Path};
|
||||
|
||||
const MAGISK_BIN_INJECT_PARTITIONS: [&Utf8CStr; 4] =
|
||||
[cstr!("/system/"), cstr!("/vendor/"), cstr!("/product/"), cstr!("/system_ext/")];
|
||||
const MAGISK_BIN_INJECT_PARTITIONS: [&Utf8CStr; 4] = [
|
||||
cstr!("/system/"),
|
||||
cstr!("/vendor/"),
|
||||
cstr!("/product/"),
|
||||
cstr!("/system_ext/"),
|
||||
];
|
||||
|
||||
const SECONDARY_READ_ONLY_PARTITIONS: [&Utf8CStr; 3] =
|
||||
[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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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
|
||||
// that the original mutable reference can only be reused if and only if
|
||||
// the newly created instance is destroyed.
|
||||
struct PathTracker<'a> {
|
||||
real: &'a mut dyn Utf8CStrBuf,
|
||||
tmp: &'a mut dyn Utf8CStrBuf,
|
||||
real_len: usize,
|
||||
tmp_len: usize,
|
||||
path: &'a mut dyn Utf8CStrBuf,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl PathTracker<'_> {
|
||||
fn from<'a>(real: &'a mut dyn Utf8CStrBuf, tmp: &'a mut dyn Utf8CStrBuf) -> PathTracker<'a> {
|
||||
let real_len = real.len();
|
||||
let tmp_len = tmp.len();
|
||||
PathTracker {
|
||||
real,
|
||||
tmp,
|
||||
real_len,
|
||||
tmp_len,
|
||||
}
|
||||
fn from<'a>(path: &'a mut dyn Utf8CStrBuf) -> PathTracker<'a> {
|
||||
let len = path.len();
|
||||
PathTracker { path, len }
|
||||
}
|
||||
|
||||
fn append(&mut self, name: &str) -> PathTracker {
|
||||
let real_len = self.real.len();
|
||||
let tmp_len = self.tmp.len();
|
||||
self.real.append_path(name);
|
||||
self.tmp.append_path(name);
|
||||
let len = self.path.len();
|
||||
self.path.append_path(name);
|
||||
PathTracker {
|
||||
real: self.real,
|
||||
tmp: self.tmp,
|
||||
real_len,
|
||||
tmp_len,
|
||||
path: self.path,
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone(&mut self) -> PathTracker {
|
||||
Self::from(self.real, self.tmp)
|
||||
fn reborrow(&mut self) -> PathTracker {
|
||||
Self::from(self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PathTracker<'_> {
|
||||
// Revert back to the original state after finish using the buffer
|
||||
fn drop(&mut self) {
|
||||
self.real.truncate(self.real_len);
|
||||
self.tmp.truncate(self.tmp_len);
|
||||
self.path.truncate(self.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 {
|
||||
Directory { children: FsNodeMap },
|
||||
File { src: Utf8CString },
|
||||
Symlink { target: Utf8CString, is_magisk_bin: bool },
|
||||
Symlink { target: Utf8CString },
|
||||
MagiskLink,
|
||||
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 {
|
||||
return Ok(());
|
||||
};
|
||||
let mut dir = Directory::open(path)?;
|
||||
let path_len = path.len();
|
||||
let mut dir = Directory::open(paths.module())?;
|
||||
|
||||
while let Some(entry) = dir.read()? {
|
||||
path.truncate(path_len);
|
||||
path.append_path(entry.name());
|
||||
let entry_paths = paths.append(entry.name());
|
||||
let path = entry_paths.module();
|
||||
if entry.is_dir() {
|
||||
let node = children
|
||||
.entry(entry.name().to_string())
|
||||
.or_insert_with(FsNode::new_dir);
|
||||
node.build_from_path(path)?;
|
||||
node.collect(entry_paths)?;
|
||||
} else if entry.is_symlink() {
|
||||
let mut link = cstr::buf::default();
|
||||
path.read_link(&mut link)?;
|
||||
@@ -128,7 +162,6 @@ impl FsNode {
|
||||
.entry(entry.name().to_string())
|
||||
.or_insert_with(|| FsNode::Symlink {
|
||||
target: link.to_owned(),
|
||||
is_magisk_bin: false,
|
||||
});
|
||||
} else {
|
||||
if entry.is_char_device() {
|
||||
@@ -140,10 +173,14 @@ impl FsNode {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if entry_paths.real().exists() {
|
||||
clone_attr(entry_paths.real(), path)?;
|
||||
}
|
||||
children
|
||||
.entry(entry.name().to_string())
|
||||
.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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
FsNode::Directory { children } => {
|
||||
let mut is_tmpfs = false;
|
||||
@@ -184,7 +221,7 @@ impl FsNode {
|
||||
children.retain(|name, node| {
|
||||
if name == ".replace" {
|
||||
return if is_root_dir {
|
||||
warn!("Unable to replace '{}', ignore request", path.real);
|
||||
warn!("Unable to replace '{}', ignore request", path.real());
|
||||
false
|
||||
} else {
|
||||
is_tmpfs = true;
|
||||
@@ -193,10 +230,10 @@ impl FsNode {
|
||||
}
|
||||
|
||||
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 {
|
||||
// Ignore the unsupported child node
|
||||
warn!("Unable to add '{}', skipped", path.real);
|
||||
warn!("Unable to add '{}', skipped", path.real());
|
||||
return false;
|
||||
}
|
||||
is_tmpfs = true;
|
||||
@@ -205,10 +242,10 @@ impl FsNode {
|
||||
});
|
||||
|
||||
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
|
||||
// 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 {
|
||||
for (name, node) in children {
|
||||
let path = path.append(name);
|
||||
@@ -217,26 +254,25 @@ impl FsNode {
|
||||
}
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
fn commit_tmpfs(&mut self, mut path: PathTracker) -> LoggedResult<()> {
|
||||
fn commit_tmpfs(&mut self, mut path: FilePaths) -> LoggedResult<()> {
|
||||
match self {
|
||||
FsNode::Directory { children } => {
|
||||
path.tmp.mkdirs(0o000)?;
|
||||
if path.real.exists() {
|
||||
clone_attr(path.real, path.tmp)?;
|
||||
} else if let Some(p) = path.tmp.parent_dir() {
|
||||
path.worker().mkdirs(0o000)?;
|
||||
if path.real().exists() {
|
||||
clone_attr(path.real(), path.worker())?;
|
||||
} else if let Some(p) = path.worker().parent_dir() {
|
||||
let parent = Utf8CString::from(p);
|
||||
clone_attr(&parent, path.tmp)?;
|
||||
clone_attr(&parent, path.worker())?;
|
||||
}
|
||||
|
||||
// Check whether a file name '.replace' exist
|
||||
@@ -251,7 +287,7 @@ impl FsNode {
|
||||
bind_mount(
|
||||
"mount",
|
||||
&src,
|
||||
path.real,
|
||||
path.real(),
|
||||
matches!(node, FsNode::Directory { .. }),
|
||||
)?;
|
||||
}
|
||||
@@ -264,7 +300,7 @@ impl FsNode {
|
||||
}
|
||||
|
||||
// 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() {
|
||||
if children.contains_key(entry.name().as_str()) {
|
||||
// Should not be mirrored, next
|
||||
@@ -281,11 +317,10 @@ impl FsNode {
|
||||
entry.name().to_string(),
|
||||
FsNode::Symlink {
|
||||
target: link.to_owned(),
|
||||
is_magisk_bin: false,
|
||||
},
|
||||
);
|
||||
} 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 } => {
|
||||
if path.real.exists() {
|
||||
clone_attr(path.real, src)?;
|
||||
}
|
||||
mount_dummy("mount", src, path.tmp, false)?;
|
||||
mount_dummy("mount", src, path.worker(), false)?;
|
||||
}
|
||||
FsNode::Symlink { target, is_magisk_bin } => {
|
||||
module_log!("mklink", path.tmp, target);
|
||||
path.tmp.create_symlink_to(target)?;
|
||||
// Avoid cloneing existing su attributes to our su
|
||||
if !*is_magisk_bin && path.real.exists() {
|
||||
clone_attr(path.real, path.tmp)?;
|
||||
FsNode::Symlink { target } => {
|
||||
module_log!("mklink", path.worker(), target);
|
||||
path.worker().create_symlink_to(target)?;
|
||||
if path.real().exists() {
|
||||
clone_attr(path.real(), path.worker())?;
|
||||
}
|
||||
}
|
||||
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 => {
|
||||
module_log!("delete", path.real, "null");
|
||||
module_log!("delete", path.real(), "null");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -349,34 +391,15 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
);
|
||||
|
||||
// Inject applet symlinks
|
||||
|
||||
children.insert(
|
||||
"su".to_string(),
|
||||
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,
|
||||
},
|
||||
);
|
||||
children.insert("su".to_string(), FsNode::MagiskLink);
|
||||
children.insert("resetprop".to_string(), FsNode::MagiskLink);
|
||||
children.insert("supolicy".to_string(), FsNode::MagiskLink);
|
||||
}
|
||||
|
||||
// Strip /system prefix to insert correct node
|
||||
fn strip_system_prefix(orig_item: &str) -> String {
|
||||
match orig_item.strip_prefix("/system/") {
|
||||
Some(rest) => format!("/{}", rest),
|
||||
Some(rest) => format!("/{rest}"),
|
||||
None => orig_item.to_string(),
|
||||
}
|
||||
}
|
||||
@@ -386,7 +409,10 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
|
||||
for orig_item in path_env.split(':') {
|
||||
// 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;
|
||||
}
|
||||
// Flatten apex path is not suitable too
|
||||
@@ -395,7 +421,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
}
|
||||
|
||||
// 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() {
|
||||
let item = strip_system_prefix(orig_item);
|
||||
candidates.push((item, 0));
|
||||
@@ -403,21 +429,25 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
}
|
||||
|
||||
let path = Utf8CString::from(orig_item);
|
||||
if let Ok(attr) = path.get_attr() && (attr.st.st_mode & 0x0001) != 0 {
|
||||
if let Ok(mut dir) = Directory::open(&path) {
|
||||
let mut count = 0;
|
||||
if let Err(_) = dir.pre_order_walk(|e| {
|
||||
if let Ok(attr) = path.get_attr()
|
||||
&& (attr.st.st_mode & 0x0001) != 0
|
||||
&& let Ok(mut dir) = Directory::open(&path)
|
||||
{
|
||||
let mut count = 0;
|
||||
if dir
|
||||
.pre_order_walk(|e| {
|
||||
if e.is_file() {
|
||||
count += 1;
|
||||
}
|
||||
Ok(WalkResult::Continue)
|
||||
}) {
|
||||
// Skip, we cannot ensure the result is correct
|
||||
continue;
|
||||
}
|
||||
let item = strip_system_prefix(orig_item);
|
||||
candidates.push((item, count));
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
// Skip, we cannot ensure the result is correct
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
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
|
||||
//
|
||||
// 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.
|
||||
|
||||
let mut path = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(MODULEMNT);
|
||||
let len = path.len();
|
||||
for info in module_list {
|
||||
path.truncate(len);
|
||||
path.append_path(&info.name);
|
||||
|
||||
// Read props
|
||||
let module_path_len = path.len();
|
||||
path.append_path("system.prop");
|
||||
if path.exists() {
|
||||
// Do NOT go through property service as it could cause boot lock
|
||||
load_prop_file(&path, true);
|
||||
let mut module_paths = root_paths.append(&info.name);
|
||||
{
|
||||
// Read props
|
||||
let prop = module_paths.append("system.prop");
|
||||
if prop.module().exists() {
|
||||
// Do NOT go through property service as it could cause boot lock
|
||||
load_prop_file(prop.module(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether skip mounting
|
||||
path.truncate(module_path_len);
|
||||
path.append_path("skip_mount");
|
||||
if path.exists() {
|
||||
continue;
|
||||
{
|
||||
// Check whether skip mounting
|
||||
let skip = module_paths.append("skip_mount");
|
||||
if skip.module().exists() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Double check whether the system folder exists
|
||||
path.truncate(module_path_len);
|
||||
path.append_path("system");
|
||||
if path.exists() {
|
||||
info!("{}: loading module files", &info.name);
|
||||
system.build_from_path(&mut path).log_ok();
|
||||
{
|
||||
// Double check whether the system folder exists
|
||||
let sys = module_paths.append("system");
|
||||
if sys.module().exists() {
|
||||
info!("{}: loading module files", &info.name);
|
||||
system.collect(sys).log_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,16 +638,6 @@ pub fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str) {
|
||||
}
|
||||
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 {
|
||||
// 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
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,37 +22,37 @@ pub fn setup_preinit_dir() {
|
||||
let dev_path = cstr::buf::new::<64>()
|
||||
.join_path(magisk_tmp)
|
||||
.join_path(PREINITDEV);
|
||||
if let Ok(attr) = dev_path.get_attr() {
|
||||
if 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
|
||||
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
|
||||
// What we do instead is to scan through the current mountinfo and find a pre-existing
|
||||
// mount point mounting our desired partition, and then bind mount the target folder.
|
||||
let preinit_dev = attr.st.st_rdev;
|
||||
let mnt_path = cstr::buf::default()
|
||||
.join_path(magisk_tmp)
|
||||
.join_path(PREINITMIRR);
|
||||
for info in parse_mount_info("self") {
|
||||
if info.root == "/" && info.device == preinit_dev {
|
||||
if !info.fs_option.split(',').any(|s| s == "rw") {
|
||||
// Only care about rw mounts
|
||||
continue;
|
||||
}
|
||||
let mut target = info.target;
|
||||
let target = Utf8CStr::from_string(&mut target);
|
||||
let mut preinit_dir = resolve_preinit_dir(target);
|
||||
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
|
||||
let r: LoggedResult<()> = try {
|
||||
preinit_dir.mkdir(0o700)?;
|
||||
mnt_path.mkdirs(0o755)?;
|
||||
mnt_path.remove().ok();
|
||||
mnt_path.create_symlink_to(preinit_dir)?;
|
||||
};
|
||||
if r.is_ok() {
|
||||
info!("* Found preinit dir: {}", preinit_dir);
|
||||
return;
|
||||
}
|
||||
if let Ok(attr) = dev_path.get_attr()
|
||||
&& 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
|
||||
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
|
||||
// What we do instead is to scan through the current mountinfo and find a pre-existing
|
||||
// mount point mounting our desired partition, and then bind mount the target folder.
|
||||
let preinit_dev = attr.st.st_rdev;
|
||||
let mnt_path = cstr::buf::default()
|
||||
.join_path(magisk_tmp)
|
||||
.join_path(PREINITMIRR);
|
||||
for info in parse_mount_info("self") {
|
||||
if info.root == "/" && info.device == preinit_dev {
|
||||
if !info.fs_option.split(',').any(|s| s == "rw") {
|
||||
// Only care about rw mounts
|
||||
continue;
|
||||
}
|
||||
let mut target = info.target;
|
||||
let target = Utf8CStr::from_string(&mut target);
|
||||
let mut preinit_dir = resolve_preinit_dir(target);
|
||||
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
|
||||
let r: LoggedResult<()> = try {
|
||||
preinit_dir.mkdir(0o700)?;
|
||||
mnt_path.mkdirs(0o755)?;
|
||||
mnt_path.remove().ok();
|
||||
mnt_path.create_symlink_to(preinit_dir)?;
|
||||
};
|
||||
if r.is_ok() {
|
||||
info!("* Found preinit dir: {}", preinit_dir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,10 +238,10 @@ pub fn revert_unmount(pid: i32) {
|
||||
let mut prev: Option<PathBuf> = None;
|
||||
targets.sort();
|
||||
targets.retain(|target| {
|
||||
if let Some(prev) = &prev {
|
||||
if Path::new(target).starts_with(prev) {
|
||||
return false;
|
||||
}
|
||||
if let Some(prev) = &prev
|
||||
&& Path::new(target).starts_with(prev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
prev = Some(PathBuf::from(target.clone()));
|
||||
true
|
||||
|
||||
@@ -167,10 +167,10 @@ pub fn persist_get_props(mut prop_cb: Pin<&mut PropCb>) {
|
||||
} else {
|
||||
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
|
||||
dir.pre_order_walk(|e| {
|
||||
if e.is_file() {
|
||||
if let Ok(mut value) = file_get_prop(e.name()) {
|
||||
prop_cb.exec(e.name(), Utf8CStr::from_string(&mut value));
|
||||
}
|
||||
if e.is_file()
|
||||
&& let Ok(mut value) = file_get_prop(e.name())
|
||||
{
|
||||
prop_cb.exec(e.name(), Utf8CStr::from_string(&mut value));
|
||||
}
|
||||
// Do not traverse recursively
|
||||
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 base::libc::{O_CLOEXEC, O_WRONLY};
|
||||
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")
|
||||
.open(O_WRONLY | O_CLOEXEC)
|
||||
.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();
|
||||
@@ -70,6 +69,7 @@ pub(crate) fn restorecon() {
|
||||
path.clear();
|
||||
path.push_str(DATABIN);
|
||||
restore_syscon(&mut path).log_ok();
|
||||
unsafe { libc::chmod(cstr!(MAGISKDB).as_ptr(), 0o000) };
|
||||
}
|
||||
|
||||
pub(crate) fn restore_tmpcon() -> LoggedResult<()> {
|
||||
|
||||
@@ -22,6 +22,7 @@ impl Default for SuRequest {
|
||||
target_pid: -1,
|
||||
login: false,
|
||||
keep_env: false,
|
||||
drop_cap: false,
|
||||
shell: DEFAULT_SHELL.to_string(),
|
||||
command: "".to_string(),
|
||||
context: "".to_string(),
|
||||
@@ -168,7 +169,10 @@ impl MagiskD {
|
||||
// Before unlocking, refresh the timestamp
|
||||
access.refresh();
|
||||
|
||||
// Fail fast
|
||||
if access.settings.policy == SuPolicy::Restrict {
|
||||
req.drop_cap = true;
|
||||
}
|
||||
|
||||
if access.settings.policy == SuPolicy::Deny {
|
||||
warn!("su: request rejected ({})", info.uid);
|
||||
client.write_pod(&SuPolicy::Deny.repr).ok();
|
||||
|
||||
@@ -52,13 +52,12 @@ fn set_stdin_raw() -> bool {
|
||||
|
||||
pub fn restore_stdin() -> bool {
|
||||
unsafe {
|
||||
if let Some(ref termios) = OLD_STDIN {
|
||||
if tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0
|
||||
&& tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0
|
||||
{
|
||||
warn!("Failed to restore terminal attributes");
|
||||
return false;
|
||||
}
|
||||
if let Some(ref termios) = OLD_STDIN
|
||||
&& tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0
|
||||
&& tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0
|
||||
{
|
||||
warn!("Failed to restore terminal attributes");
|
||||
return false;
|
||||
}
|
||||
OLD_STDIN = None;
|
||||
true
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
#include <getopt.h>
|
||||
#include <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <linux/securebits.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sched.h>
|
||||
#include <sys/types.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"
|
||||
" -Z, --context CONTEXT Change SELinux context\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"
|
||||
" -, -l, --login Pretend the shell to be a login shell\n"
|
||||
" -m, -p,\n"
|
||||
@@ -105,6 +109,7 @@ int su_client_main(int argc, char *argv[]) {
|
||||
{ "group", required_argument, nullptr, 'g' },
|
||||
{ "supp-group", required_argument, nullptr, 'G' },
|
||||
{ "interactive", no_argument, nullptr, 'i' },
|
||||
{ "drop-cap", no_argument, nullptr, 'd' },
|
||||
{ nullptr, 0, nullptr, 0 },
|
||||
};
|
||||
|
||||
@@ -121,7 +126,7 @@ int su_client_main(int argc, char *argv[]) {
|
||||
|
||||
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) {
|
||||
case 'c': {
|
||||
string command;
|
||||
@@ -146,6 +151,9 @@ int su_client_main(int argc, char *argv[]) {
|
||||
case 'p':
|
||||
req.keep_env = true;
|
||||
break;
|
||||
case 'd':
|
||||
req.drop_cap = true;
|
||||
break;
|
||||
case 's':
|
||||
req.shell = optarg;
|
||||
break;
|
||||
@@ -259,11 +267,66 @@ int su_client_main(int argc, char *argv[]) {
|
||||
return code;
|
||||
}
|
||||
|
||||
// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root
|
||||
static void set_identity(int uid, const rust::Vec<gid_t> &groups) {
|
||||
if (seteuid(0)) {
|
||||
PLOGE("seteuid (root)");
|
||||
static void drop_caps() {
|
||||
static auto last_valid_cap = []() {
|
||||
uint32_t cap = CAP_WAKE_ALARM;
|
||||
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;
|
||||
if (!groups.empty()) {
|
||||
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
|
||||
sigset_t block_set;
|
||||
sigemptyset(&block_set);
|
||||
sigprocmask(SIG_SETMASK, &block_set, nullptr);
|
||||
// Config privileges
|
||||
if (!req.context.empty()) {
|
||||
auto f = xopen_file("/proc/self/attr/exec", "we");
|
||||
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);
|
||||
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
|
||||
PLOGE("exec");
|
||||
|
||||
@@ -170,10 +170,10 @@ impl MagiskD {
|
||||
client.write_pod(&flags)?;
|
||||
|
||||
// Next send modules
|
||||
if zygisk_should_load_module(flags) {
|
||||
if let Some(module_fds) = self.get_module_fds(is_64_bit) {
|
||||
client.send_fds(&module_fds)?;
|
||||
}
|
||||
if zygisk_should_load_module(flags)
|
||||
&& let Some(module_fds) = self.get_module_fds(is_64_bit)
|
||||
{
|
||||
client.send_fds(&module_fds)?;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("error occurred: {}", e);
|
||||
eprintln!("error occurred: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,6 @@ pub fn gen_cxx_binding(name: &str) {
|
||||
println!("cargo:rerun-if-changed=lib.rs");
|
||||
let opt = Opt::default();
|
||||
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!("{}.hpp", name), code.header.as_slice()).ok_or_exit();
|
||||
write_if_diff(format!("{name}.cpp"), code.implementation.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 MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules");
|
||||
pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
|
||||
pub const MAGISKDB: &str = concatcp!(SECURE_DIR, "/magisk.db");
|
||||
|
||||
// tmpfs paths
|
||||
const INTERNAL_DIR: &str = ".magisk";
|
||||
|
||||
@@ -42,7 +42,7 @@ fn print_usage(cmd: &str) {
|
||||
eprintln!(
|
||||
r#"MagiskPolicy - SELinux Policy Patch Tool
|
||||
|
||||
Usage: {} [--options...] [policy statements...]
|
||||
Usage: {cmd} [--options...] [policy statements...]
|
||||
|
||||
Options:
|
||||
--help show help message for policy statements
|
||||
@@ -60,8 +60,7 @@ Options:
|
||||
|
||||
If neither --load, --load-split, nor --compile-split is specified,
|
||||
it will load from current live policies (/sys/fs/selinux/policy)
|
||||
"#,
|
||||
cmd
|
||||
"#
|
||||
);
|
||||
|
||||
format_statement_help(&mut FmtAdaptor(&mut stderr())).ok();
|
||||
|
||||
@@ -504,7 +504,7 @@ impl Display for Token<'_> {
|
||||
Token::ST => f.write_char('*'),
|
||||
Token::TL => 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),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user