Compare commits

...

100 Commits

Author SHA1 Message Date
topjohnwu
1e3edb8883 Release new canary build
[skip ci]
2025-03-21 10:37:49 -07:00
topjohnwu
3b8b61bf35 Remove ZipUtils.kt 2025-03-20 14:54:25 -07:00
topjohnwu
6f90456036 Properly buffer I/O 2025-03-20 13:16:39 -07:00
topjohnwu
f56fd4e215 Always close outputStream in processFile
Fix #8735
2025-03-19 17:57:31 -07:00
topjohnwu
aa35aac5d5 Update dependencies 2025-03-19 15:55:06 -07:00
topjohnwu
1f162b819d Update ONDK r29.0 2025-03-19 10:55:08 -07:00
Wang Han
52ef1d1cb2 Simplify matching selinux context of child zygote (#8845) 2025-03-11 01:27:15 -07:00
John Wu
f14e3a89cc Enable optimize_for_size for Rust std (#8844) 2025-03-10 15:50:43 -07:00
topjohnwu
95d3eac2e0 Cleanup xwrap functions 2025-03-09 01:10:41 -08:00
Wang Han
8e73536e02 Remove unused hasGMS variable 2025-03-09 00:16:25 -08:00
LoveSy
12a0870bc9 Replace bzip2 with bz2-rs 2025-03-08 14:37:17 -08:00
topjohnwu
6ff82c4e86 Introduce FsPathFollow
Make sure all operations of FsPath do not follow symlinks, and provide
a way to explicitly switch over to a set of operations that DO follow
symlinks by FsPath::follow_link.
2025-03-07 15:51:51 -08:00
LoveSy
c64de35375 Move magiskpolicy cli to argh 2025-03-07 14:29:30 -08:00
topjohnwu
ee5283f4e8 Update release script 2025-03-07 14:14:06 -08:00
LoveSy
bd0e954fea Replace zlib with zlib-rs 2025-03-07 11:40:02 -08:00
topjohnwu
675471a49e Upgrade argh to stable release 2025-03-07 02:38:36 -08:00
topjohnwu
c90e73ccec Migration to Edition 2024 2025-03-07 02:35:25 -08:00
topjohnwu
a43c1267d8 Update Cargo.toml 2025-03-07 02:35:25 -08:00
vvb2060
e8958c6b5c get_secontext: ignore ENODATA 2025-03-06 20:03:36 -08:00
LoveSy
e8a3bf82c6 set exit code of log ExitOnError to -1 2025-03-06 20:03:15 -08:00
topjohnwu
27fd79176a Update ONDK to r28.3 2025-03-06 17:41:28 -08:00
topjohnwu
28d86a3454 Update rustup_wrapper
[skip ci]
2025-03-06 16:12:35 -08:00
topjohnwu
c6c1a17ae6 Address several clippy warnings 2025-03-03 02:15:14 -08:00
topjohnwu
2b47d47215 Also run clippy with release mode
[skip ci]
2025-03-02 23:14:46 -08:00
David K.
0e82df9e10 Support zImage compression types other than gzip.
Instead of just searching for the gzip magic, it now incrementally searches the kernel for the first thing that `check_fmt_lg` doesn't report as `UNKNOWN`.
2025-03-02 13:35:13 -08:00
topjohnwu
893821ad88 Skip all tests on master push 2025-03-02 02:30:11 -08:00
topjohnwu
6b80fbfa99 Fix cache save condition 2025-03-02 02:30:11 -08:00
topjohnwu
8c3c7d0194 Build on master push 2025-03-02 01:51:42 -08:00
topjohnwu
b94a3d9f2f Do not store cache on pull request 2025-03-02 01:51:42 -08:00
Wang Han
442d0b5ddc Delete bootctl binary if execution fails
New devices may use AIDL bootctrl HAL, so if bootctl hal-info fails,
simply remove the temp file and return.
2025-03-01 22:43:55 -08:00
topjohnwu
494615d9a0 Support ./build.py emulator with an APK argument 2025-02-28 17:17:24 -08:00
vvb2060
afbfb81837 docs: add avd_magisk.sh to faq 2025-02-28 17:17:24 -08:00
vvb2060
3ed4e258a3 avd_magisk: add general usage 2025-02-28 17:17:24 -08:00
vvb2060
dddd41c95b avd_magisk: support rootfs without sbin 2025-02-28 17:17:24 -08:00
topjohnwu
5f2ca81e86 Update AGP 2025-02-28 13:19:16 -08:00
topjohnwu
c9eac0c438 Introduce new sepolicy strategy for legacy devices
The existing sepolicy patching strategy looks like this:

1. 2SI: use LD_PRELOAD to hijack `security_load_policy`
2. Split policy: devices using split policy implies it also needs to
   do early mount, which means fstab is stored in device tree.
   So we do the following:
   - Hijack the fstab node in the device tree in sysfs
   - Wait for init to mount selinuxfs for us
   - Hijack selinuxfs to intercept sepolicy loading
3. Monolithic policy: directly patch `/sepolicy`

Method #1 and #2 both has the magiskinit pre-init daemon handling
the sepolicy patching and loading process, while method #3 gives us
zero control over sepolicy loading process. Downsides:

a. Pre-init daemon bypasses the need to guess which sepolicy init
   will load, because the original init will literally send the stock
   sepolicy file directly to us with this approach.
b. If we want to add more features/functionalities during the sepolicy
   patching process, we will leave out devices using method #3

In order to solve these issues, we completely redesign the sepolicy
patching strategy for non-2SI devices. Instead of limiting usage of
pre-init daemon to early mount devices, we always intercept the
sepolicy loading process regardless of the Android version and device
setup. This will give us a unified implementation for sepolicy patching,
and will make it easier to develop further new features down the line.
2025-02-28 09:39:10 -08:00
topjohnwu
b6b34f7612 Fix overlay.d context preservation 2025-02-27 01:57:25 -08:00
LoveSy
e55c413261 Correctly handle truncated dtb 2025-02-23 20:31:55 -08:00
topjohnwu
0399cde50a Cleanup logcat.log for each invocation 2025-02-18 01:04:19 -08:00
topjohnwu
019eb03823 Hide or remove mut constructors for Utf8CStr 2025-02-17 11:38:11 -08:00
topjohnwu
363410e1c0 Introduce cstr_buf helper functions 2025-02-17 11:32:21 -08:00
topjohnwu
fc2ef21660 Introduce path! macro for FsPath 2025-02-17 01:46:19 -08:00
topjohnwu
18cb659ff3 Run clippy through build.py 2025-02-17 01:31:59 -08:00
topjohnwu
63231d97ce Properly handle db downgrades 2025-02-16 17:01:36 -08:00
topjohnwu
9ac81a8a25 Skip module tests on API < 26 2025-02-16 16:20:09 -08:00
topjohnwu
79af2787ae Workaround potential OOM when signing APKs 2025-02-16 16:20:09 -08:00
topjohnwu
f5f9b285c0 Add module tests 2025-02-16 16:20:09 -08:00
topjohnwu
6c05f2ae85 Test processing Shamiko module zip 2025-02-16 16:20:09 -08:00
topjohnwu
29043e1684 Consolidate setup methods 2025-02-16 16:20:09 -08:00
topjohnwu
b73d4a7022 Fix log_ok() 2025-02-16 12:01:25 -08:00
topjohnwu
ad95e8951b Skip download in lsposed setup test
Download the zip during build time
2025-02-16 01:26:40 -08:00
topjohnwu
bf591fca12 Fix Utf8CString constructor and add more comments 2025-02-16 01:17:48 -08:00
topjohnwu
dcf027884d Update FsPathBuf 2025-02-15 18:27:45 -08:00
topjohnwu
584f3820fe Make all Utf8CStrWrite Utf8CStrBuf 2025-02-15 18:27:45 -08:00
topjohnwu
3c7c46307a Partially cleanup MagiskInit code 2025-02-15 18:27:45 -08:00
vvb2060
4d80361805 core: search for first available dir in PATH 2025-02-15 15:08:26 -08:00
LoveSy
9a74e19117 Add log_ok() for log().ok() 2025-02-14 14:24:13 -08:00
LoveSy
b1e17706a4 Format code 2025-02-14 14:24:13 -08:00
LoveSy
caad129d69 Move MagiskInit::patch_sepolicy to rust 2025-02-14 14:24:13 -08:00
LoveSy
da58571ce5 Remove redundant rust export 2025-02-14 14:24:13 -08:00
LoveSy
2aa7f1c094 Move MagiskInit::check_two_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
823e31a91b Use linker to link vfprintf as tiny_vfprintf 2025-02-14 14:24:13 -08:00
LoveSy
fb926ae302 Move MagiskInit::redirect_second_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
e0489eeffd Move MagiskInit::first_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
dc9d5a4cac Move MagiskInit::second_stage to rust 2025-02-14 14:24:13 -08:00
LoveSy
143743d0b0 Refactor init.cpp to init.rs 2025-02-14 14:24:13 -08:00
LoveSy
563f0d5ad5 Move BootConfig::print to rust 2025-02-14 14:24:13 -08:00
LoveSy
c99f4a591b Move MagiskInit::exec_init to rust 2025-02-14 14:24:13 -08:00
LoveSy
449204e380 Move MagiskInit::prepare_data to rust 2025-02-14 14:24:13 -08:00
LoveSy
a85c4c6528 Move MagiskInit::MagiskInit to rust 2025-02-14 14:24:13 -08:00
LoveSy
d203a6fff6 Move MagiskInit to rust 2025-02-14 14:24:13 -08:00
LoveSy
6c612d66d7 Move BootConfig to rust 2025-02-14 14:24:13 -08:00
topjohnwu
540253a55b Remove unnecessary FFI 2025-02-14 11:24:46 -08:00
topjohnwu
15b7c4ccd1 Fix tmpfs mounts in avd_magisk.sh 2025-02-14 10:17:31 -08:00
topjohnwu
442d5335ea Consolidate get_module_fds implementation
Close #8767
2025-02-12 02:55:27 +08:00
topjohnwu
8a80eea597 Directly deal with Rust &str in sepolicy.cpp 2025-02-12 01:26:06 +08:00
Wang Han
5e35703091 Ensure target path exists before mknod
Co-authored-by: LoveSy <shana@zju.edu.cn>
2025-02-12 01:13:56 +08:00
topjohnwu
b7ca73f431 Remove an additional unique_ptr indirection 2025-02-05 14:18:16 +08:00
Wang Han
a14fc90f07 Fix fetching notification settings from db (#8761)
Co-authored-by: LoveSy <shana@zju.edu.cn>
2025-02-04 17:42:21 +08:00
LoveSy
c913f7ec74 Make sepolicy a shared type between rust and cxx 2025-02-04 00:36:11 +08:00
topjohnwu
7f6c9e8411 Fix zygisk module load 2025-02-03 23:21:51 +08:00
topjohnwu
bb02ea3a20 Fix file descriptor IPC 2025-02-03 18:21:03 +08:00
LoveSy
3981c9665e Replace rust inner functions to try blocks 2025-02-02 22:09:55 +08:00
topjohnwu
88628fdf3c Make sure IPC is arch agnostic 2025-02-02 22:08:41 +08:00
topjohnwu
0469817781 Cleanup code and bindings 2025-02-02 22:08:41 +08:00
topjohnwu
a786801141 Implement su_daemon in Rust 2025-02-02 22:08:41 +08:00
topjohnwu
ab86732c89 Implement simple serialization over IPC 2025-02-02 22:08:41 +08:00
topjohnwu
59622d1688 Use static methods in cxx-rs 2025-02-02 02:46:33 +08:00
LoveSy
58a25a3e2b Fix su with tty 2025-02-01 16:50:53 +08:00
topjohnwu
15dca29a87 Update cxx-rs 2025-02-01 02:02:29 +08:00
Wang Han
46980819c0 Expose safe mode option on 28+ 2025-01-31 12:20:02 +08:00
topjohnwu
4fb6a7268c Fix SDK 27 and 28 tests 2025-01-31 02:52:27 +08:00
topjohnwu
c05e963f37 Address clippy warnings 2025-01-31 02:52:27 +08:00
topjohnwu
7f7f625864 Code reorganization 2025-01-31 02:52:27 +08:00
topjohnwu
b25aa8295a Move bootstage into Rust 2025-01-31 02:52:27 +08:00
topjohnwu
15a605765c Fully implement daemon side of Zygisk in Rust 2025-01-31 02:52:27 +08:00
topjohnwu
b575c95710 Implement fd I/O on Rust side 2025-01-31 02:52:27 +08:00
topjohnwu
a48a9c858a Migrate zygisk handler to Rust 2025-01-31 02:52:27 +08:00
topjohnwu
0d8d6290a3 Move module list into MagiskD 2025-01-31 02:52:27 +08:00
topjohnwu
4dcd733ddd Minor code cleanup 2025-01-31 02:52:27 +08:00
137 changed files with 5934 additions and 4835 deletions

5
.gitattributes vendored
View File

@@ -12,13 +12,10 @@
# Denote all files that are truly binary and should not be modified.
tools/** binary
tools/rustup_wrapper/** -binary
*.jar binary
*.exe binary
*.apk binary
*.png binary
*.jpg binary
*.ttf binary
# Help GitHub detect languages
native/jni/external/** linguist-vendored
native/jni/systemproperties/** linguist-language=C++

View File

@@ -26,6 +26,15 @@ runs:
- name: Cache sccache
uses: actions/cache@v4
if: ${{ github.event_name != 'pull_request' }}
with:
path: .sccache
key: sccache-${{ runner.os }}-${{ github.sha }}
restore-keys: sccache-${{ runner.os }}-
- name: Restore sccache
uses: actions/cache/restore@v4
if: ${{ github.event_name == 'pull_request' }}
with:
path: .sccache
key: sccache-${{ runner.os }}-${{ github.sha }}
@@ -55,7 +64,7 @@ runs:
- name: Cache Gradle dependencies
uses: actions/cache@v4
if: inputs.is-asset-build == 'true'
if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
with:
path: |
.gradle/caches
@@ -66,7 +75,7 @@ runs:
- name: Restore Gradle dependencies
uses: actions/cache/restore@v4
if: inputs.is-asset-build == 'false'
if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
with:
path: |
.gradle/caches
@@ -78,19 +87,17 @@ runs:
- name: Cache Gradle build cache
uses: actions/cache@v4
if: inputs.is-asset-build == 'true'
if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}
with:
path: |
.gradle/caches/build-cache-*
path: .gradle/caches/build-cache-*
key: gradle-build-cache-${{ github.sha }}
restore-keys: gradle-build-cache-
- name: Restore Gradle build cache
uses: actions/cache/restore@v4
if: inputs.is-asset-build == 'false'
if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}
with:
path: |
.gradle/caches/build-cache-*
path: .gradle/caches/build-cache-*
key: gradle-build-cache-${{ github.sha }}
restore-keys: gradle-build-cache-
enableCrossOsArchive: true

View File

@@ -1,6 +1,16 @@
name: Magisk Build
on:
push:
branches: [master]
paths:
- "app/**"
- "native/**"
- "buildSrc/**"
- "build.py"
- "gradle.properties"
- "gradle/libs.versions.toml"
- ".github/workflows/build.yml"
pull_request:
branches: [master]
workflow_dispatch:
@@ -71,6 +81,7 @@ jobs:
name: Test API ${{ matrix.version }} (x86_64)
runs-on: ubuntu-24.04
needs: build
if: ${{ github.event_name != 'push' }}
strategy:
fail-fast: false
matrix:
@@ -117,6 +128,7 @@ jobs:
name: Test API ${{ matrix.version }} (x86)
runs-on: ubuntu-24.04
needs: build
if: ${{ github.event_name != 'push' }}
strategy:
fail-fast: false
matrix:
@@ -158,6 +170,7 @@ jobs:
name: Test ${{ matrix.device }}
runs-on: ubuntu-24.04
needs: build
if: ${{ github.event_name != 'push' }}
env:
CF_HOME: /home/runner/aosp_cf_phone
strategy:

6
.gitmodules vendored
View File

@@ -4,18 +4,12 @@
[submodule "lz4"]
path = native/src/external/lz4
url = https://github.com/lz4/lz4.git
[submodule "bzip2"]
path = native/src/external/bzip2
url = https://github.com/nemequ/bzip2.git
[submodule "xz"]
path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git
[submodule "libcxx"]
path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"]
path = native/src/external/zlib
url = https://android.googlesource.com/platform/external/zlib
[submodule "zopfli"]
path = native/src/external/zopfli
url = https://github.com/google/zopfli.git

View File

@@ -22,7 +22,7 @@ Click the icon below to download Magisk apk.
[![](https://img.shields.io/badge/Magisk-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28103)
## Useful Links

View File

@@ -41,7 +41,7 @@ object RebootMenu {
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
}
if (Const.Version.isCanary()) {
if (Const.Version.atLeast_28_0()) {
menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2
} else {
menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false

View File

@@ -14,7 +14,7 @@ object Const {
else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()
// Paths
const val MAGISK_PATH = "/data/adb/modules"
const val MODULE_PATH = "/data/adb/modules"
const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
@@ -28,6 +28,7 @@ object Const {
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 isCanary() = isCanary(Info.env.versionCode)
fun isCanary(ver: Int) = ver > 0 && ver % 100 != 0

View File

@@ -47,7 +47,6 @@ object Info {
private set
private var crypto = ""
var hasGMS = true
val isEmulator =
Build.DEVICE.contains("vsoc")
|| getProperty("ro.kernel.qemu", "0") == "1"

View File

@@ -5,7 +5,6 @@ import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.ProviderInstaller
import com.topjohnwu.magisk.core.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.LocaleSetting
import okhttp3.Cache
import okhttp3.ConnectionSpec
@@ -72,9 +71,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
chain.proceed(request.build())
}
if (!ProviderInstaller.install(context)) {
Info.hasGMS = false
}
ProviderInstaller.install(context)
return builder.build()
}

View File

@@ -5,6 +5,7 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.nio.ExtendedFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -12,7 +13,7 @@ import java.io.IOException
import java.util.Locale
data class LocalModule(
private val path: String,
private val base: ExtendedFile,
) : Module() {
private val svc get() = ServiceLocator.networkService
@@ -24,20 +25,18 @@ data class LocalModule(
var description: String = ""
var updateInfo: OnlineModule? = null
var outdated = false
private var updateUrl: String = ""
private val removeFile = RootUtils.fs.getFile(path, "remove")
private val disableFile = RootUtils.fs.getFile(path, "disable")
private val updateFile = RootUtils.fs.getFile(path, "update")
private val riruFolder = RootUtils.fs.getFile(path, "riru")
private val zygiskFolder = RootUtils.fs.getFile(path, "zygisk")
private val unloaded = RootUtils.fs.getFile(zygiskFolder, "unloaded")
val updated: Boolean get() = updateFile.exists()
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
val isZygisk: Boolean get() = zygiskFolder.exists()
val zygiskUnloaded: Boolean get() = unloaded.exists()
val hasAction: Boolean;
private val removeFile = base.getChildFile("remove")
private val disableFile = base.getChildFile("disable")
private val updateFile = base.getChildFile("update")
val zygiskFolder = base.getChildFile("zygisk")
val updated get() = updateFile.exists()
val isRiru = (id == "riru-core") || base.getChildFile("riru").exists()
val isZygisk = zygiskFolder.exists()
val zygiskUnloaded = zygiskFolder.getChildFile("unloaded").exists()
val hasAction = base.getChildFile("action.sh").exists()
var enable: Boolean
get() = !disableFile.exists()
@@ -90,19 +89,16 @@ data class LocalModule(
init {
runCatching {
parseProps(Shell.cmd("dos2unix < $path/module.prop").exec().out)
parseProps(Shell.cmd("dos2unix < $base/module.prop").exec().out)
}
if (id.isEmpty()) {
val sep = path.lastIndexOf('/')
id = path.substring(sep + 1)
id = base.name
}
if (name.isEmpty()) {
name = id
}
hasAction = RootUtils.fs.getFile(path, "action.sh").exists()
}
suspend fun fetch(): Boolean {
@@ -125,14 +121,14 @@ data class LocalModule(
companion object {
fun loaded() = RootUtils.fs.getFile(Const.MAGISK_PATH).exists()
fun loaded() = RootUtils.fs.getFile(Const.MODULE_PATH).exists()
suspend fun installed() = withContext(Dispatchers.IO) {
RootUtils.fs.getFile(Const.MAGISK_PATH)
RootUtils.fs.getFile(Const.MODULE_PATH)
.listFiles()
.orEmpty()
.filter { !it.isFile && !it.isHidden }
.map { LocalModule("${Const.MAGISK_PATH}/${it.name}") }
.map { LocalModule(it) }
.sortedBy { it.name.lowercase(Locale.ROOT) }
}
}

View File

@@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class ByteArrayStream extends ByteArrayOutputStream {
@@ -27,4 +28,8 @@ public class ByteArrayStream extends ByteArrayOutputStream {
public ByteArrayInputStream getInputStream() {
return new ByteArrayInputStream(buf, 0, count);
}
public ByteBuffer toByteBuffer() {
return ByteBuffer.wrap(buf, 0, count);
}
}

View File

@@ -510,7 +510,7 @@ public class SignApk {
privateKey[0] = key;
// Generate, in memory, an APK signed using standard JAR Signature Scheme.
ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
ByteArrayStream v1SignedApkBuf = new ByteArrayStream();
JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
// Use maximum compression for compressed entries because the APK lives forever on
// the system partition.
@@ -519,8 +519,7 @@ public class SignApk {
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, publicKey, privateKey, timestamp, outputJar);
outputJar.close();
ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray());
v1SignedApkBuf.reset();
ByteBuffer v1SignedApk = v1SignedApkBuf.toByteBuffer();
ByteBuffer[] outputChunks;
List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey,

View File

@@ -17,7 +17,6 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAll
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.DummyList
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@@ -47,7 +46,6 @@ import java.io.OutputStream
import java.io.PushbackInputStream
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.util.Arrays
import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean
@@ -191,7 +189,8 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) =
out.use { copyAll(it, 1024 * 1024) }
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
// Make sure available is never called on the actual stream and always return 0
@@ -225,8 +224,8 @@ abstract class MagiskInstallImpl protected constructor(
console.add("- Processing tar file")
var entry: TarArchiveEntry? = tarIn.nextEntry
fun TarArchiveEntry.decompressedStream(): InputStream {
val stream = if (name.endsWith(".lz4"))
fun decompressedStream(): InputStream {
val stream = if (tarIn.currentEntry.name.endsWith(".lz4"))
FramedLZ4CompressorInputStream(tarIn, true) else tarIn
return NoAvailableStream(stream)
}
@@ -252,9 +251,9 @@ abstract class MagiskInstallImpl protected constructor(
if (bootItem != null) {
console.add("-- Extracting: ${bootItem.name}")
entry.decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
} else if (entry.name.contains("vbmeta.img")) {
val rawData = entry.decompressedStream().readBytes()
val rawData = decompressedStream().readBytes()
// Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256)
continue
@@ -287,7 +286,7 @@ abstract class MagiskInstallImpl protected constructor(
} else {
console.add("-- Copying : ${entry.name}")
tarOut.putArchiveEntry(entry)
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
tarIn.copyAll(tarOut)
tarOut.closeArchiveEntry()
}
entry = tarIn.nextEntry ?: break
@@ -429,7 +428,7 @@ abstract class MagiskInstallImpl protected constructor(
// Process input file
try {
PushbackInputStream(uri.inputStream(), 512).use { src ->
PushbackInputStream(uri.inputStream().buffered(1024 * 1024), 512).use { src ->
val head = ByteArray(512)
if (src.read(head) != head.size) {
console.add("! Invalid input file")
@@ -438,12 +437,13 @@ abstract class MagiskInstallImpl protected constructor(
src.unread(head)
val magic = head.copyOf(4)
val tarMagic = Arrays.copyOfRange(head, 257, 262)
val tarMagic = head.copyOfRange(257, 262)
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
// tar file
outFile = MediaStoreUtils.getFile("$destName.tar")
outStream = TarArchiveOutputStream(outFile.uri.outputStream()).also {
val os = outFile.uri.outputStream().buffered(1024 * 1024)
outStream = TarArchiveOutputStream(os).also {
it.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
}
@@ -500,7 +500,7 @@ abstract class MagiskInstallImpl protected constructor(
bootItem.file = newBoot
bootItem.copyTo(outStream as TarArchiveOutputStream)
} else {
newBoot.newInputStream().copyAndClose(outStream)
newBoot.newInputStream().use { it.copyAll(outStream, 1024 * 1024) }
}
newBoot.delete()
@@ -514,6 +514,8 @@ abstract class MagiskInstallImpl protected constructor(
outFile.delete()
Timber.e(e)
return false
} finally {
outStream.close()
}
// Fix up binaries

View File

@@ -1,50 +0,0 @@
package com.topjohnwu.magisk.core.utils
import com.topjohnwu.magisk.core.ktx.copyAll
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import java.io.File
import java.io.IOException
import java.io.InputStream
@Throws(IOException::class)
suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
ZipFile.Builder().setFile(this).get().use { zip ->
for (entry in zip.entries) {
if (!entry.name.startsWith(path) || entry.isDirectory) {
// Ignore directories, only create files
continue
}
val name = if (junkPath)
entry.name.substring(entry.name.lastIndexOf('/') + 1)
else
entry.name
val dest = File(folder, name)
dest.parentFile?.mkdirs()
dest.outputStream().use { out -> zip.getInputStream(entry).copyAll(out) }
}
}
}
@Throws(IOException::class)
suspend fun InputStream.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
ZipArchiveInputStream(this).use { zin ->
var entry: ZipArchiveEntry
while (true) {
entry = zin.nextEntry ?: break
if (!entry.name.startsWith(path) || entry.isDirectory) {
// Ignore directories, only create files
continue
}
val name = if (junkPath)
entry.name.substring(entry.name.lastIndexOf('/') + 1)
else
entry.name
val dest = File(folder, name)
dest.parentFile?.mkdirs()
dest.outputStream().use { out -> zin.copyAll(out) }
}
}
}

View File

@@ -5,9 +5,17 @@ import androidx.annotation.Keep
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.utils.RootUtils
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.TimeUnit
@@ -21,6 +29,17 @@ class AdditionalTest : BaseTest {
private const val SHELL_PKG = "com.android.shell"
private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER"
private const val LSPOSED_PKG = "org.lsposed.manager"
private lateinit var modules: List<LocalModule>
@BeforeClass
@JvmStatic
fun before() {
BaseTest.prerequisite()
runBlocking {
modules = LocalModule.installed()
}
}
}
@After
@@ -29,9 +48,24 @@ class AdditionalTest : BaseTest {
}
@Test
fun testLaunchLsposedManager() {
fun testModuleCount() {
var expected = 0
if (Environment.testModules()) expected +=2
if (Environment.lsposed()) expected++
if (Environment.shamiko()) expected++
assertEquals("Module count incorrect", expected, modules.size)
}
@Test
fun testLsposed() {
assumeTrue(Environment.lsposed())
val module = modules.find { it.id == "zygisk_lsposed" }
assertNotNull("zygisk_lsposed is not installed", module)
module!!
assertFalse("zygisk_lsposed is not enabled", module.zygiskUnloaded)
// Launch lsposed manager to ensure the module is active
uiAutomation.executeShellCommand(
"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity"
).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }
@@ -42,4 +76,39 @@ class AdditionalTest : BaseTest {
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
)
}
@Test
fun testModule01() {
assumeTrue(Environment.testModules())
val module = modules.find { it.id == "test_01" }
assertNotNull("test_01 is not installed", module)
assertTrue(
"/system/etc/newfile should exist",
RootUtils.fs.getFile("/system/etc/newfile").exists()
)
assertFalse(
"/system/bin/screenrecord should not exist",
RootUtils.fs.getFile("/system/bin/screenrecord").exists()
)
module!!
assertTrue("test_01 should be zygisk unloaded", module.zygiskUnloaded)
}
@Test
fun testModule02() {
assumeTrue(Environment.testModules())
val module = modules.find { it.id == "test_02" }
assertNotNull("test_02 is not installed", module)
module!!
assertTrue("test_02 should be zygisk unloaded", module.zygiskUnloaded)
}
@Test
fun testModule03() {
assumeTrue(Environment.testModules())
assertNull("test_03 should be removed", modules.find { it.id == "test_03" })
}
}

View File

@@ -12,7 +12,8 @@ import org.junit.Assert.assertTrue
interface BaseTest {
val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
val context: Context get() = instrumentation.targetContext
val appContext: Context get() = instrumentation.targetContext
val testContext: Context get() = instrumentation.context
val uiAutomation: UiAutomation get() = instrumentation.uiAutomation
val device: UiDevice get() = UiDevice.getInstance(instrumentation)

View File

@@ -6,21 +6,28 @@ import androidx.annotation.Keep
import androidx.core.net.toUri
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.download.DownloadNotifier
import com.topjohnwu.magisk.core.download.DownloadProcessor
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.tasks.AppMigration
import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.nio.ExtendedFile
import kotlinx.coroutines.runBlocking
import org.apache.commons.compress.archivers.zip.ZipFile
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import timber.log.Timber
import java.io.File
@Keep
@RunWith(AndroidJUnit4::class)
@@ -31,12 +38,21 @@ class Environment : BaseTest {
@JvmStatic
fun before() = BaseTest.prerequisite()
fun lsposed(): Boolean {
return Build.VERSION.SDK_INT >= 27 && Build.VERSION.SDK_INT <= 34
// The kernel running on emulators < API 26 does not play well with
// magic mount. Skip module tests on those legacy platforms.
fun testModules(): Boolean {
return Build.VERSION.SDK_INT >= 26
}
private const val LSPOSED_URL =
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
fun lsposed(): Boolean {
return Build.VERSION.SDK_INT in 27..34
}
fun shamiko(): Boolean {
return Build.VERSION.SDK_INT >= 27
}
private const val MODULE_ERROR = "Module zip processing incorrect"
}
object TimberLog : CallbackList<String>(Runnable::run) {
@@ -45,34 +61,125 @@ class Environment : BaseTest {
}
}
private fun checkModuleZip(file: File) {
// Make sure module processing is correct
ZipFile.Builder().setFile(file).get().use { zip ->
val meta = zip.entries
.asSequence()
.filter { it.name.startsWith("META-INF") }
.toMutableList()
assertEquals(MODULE_ERROR, 6, meta.size)
val binary = zip.getInputStream(
zip.getEntry("META-INF/com/google/android/update-binary")
).use { it.readBytes() }
val ref = appContext.assets.open("module_installer.sh").use { it.readBytes() }
assertArrayEquals(MODULE_ERROR, ref, binary)
val script = zip.getInputStream(
zip.getEntry("META-INF/com/google/android/updater-script")
).use { it.readBytes() }
assertArrayEquals(MODULE_ERROR, "#MAGISK\n".toByteArray(), script)
}
}
private fun setupModule01(root: ExtendedFile) {
val error = "test_01 setup failed"
val path = root.getChildFile("test_01")
// Create /system/etc/newfile
val etc = path.getChildFile("system").getChildFile("etc")
assertTrue(error, etc.mkdirs())
assertTrue(error, etc.getChildFile("newfile").createNewFile())
// Delete /system/bin/screenrecord
val bin = path.getChildFile("system").getChildFile("bin")
assertTrue(error, bin.mkdirs())
assertTrue(error, Shell.cmd("mknod $bin/screenrecord c 0 0").exec().isSuccess)
// Create an empty zygisk folder
val module = LocalModule(path)
assertTrue(error, module.zygiskFolder.mkdir())
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupModule02(root: ExtendedFile) {
val error = "test_02 setup failed"
val path = root.getChildFile("test_02")
// Create invalid zygisk libraries
val module = LocalModule(path)
assertTrue(error, module.zygiskFolder.mkdirs())
assertTrue(error, module.zygiskFolder.getChildFile("armeabi-v7a.so").createNewFile())
assertTrue(error, module.zygiskFolder.getChildFile("arm64-v8a.so").createNewFile())
assertTrue(error, module.zygiskFolder.getChildFile("x86.so").createNewFile())
assertTrue(error, module.zygiskFolder.getChildFile("x86_64.so").createNewFile())
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
private fun setupModule03(root: ExtendedFile) {
val error = "test_03 setup failed"
val path = root.getChildFile("test_03")
// Create a new module but mark is as "remove"
val module = LocalModule(path)
assertTrue(error, path.mkdirs())
assertTrue(error, path.getChildFile("service.sh").createNewFile())
module.remove = true
assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess)
}
@Test
fun setupMagisk() {
fun setupEnvironment() {
runBlocking {
assertTrue(
"Magisk setup failed",
MagiskInstaller.Emulator(TimberLog, TimberLog).exec()
)
}
}
@Test
fun setupLsposed() {
assumeTrue(lsposed())
val notify = object : DownloadNotifier {
override val context = this@Environment.context
override val context = appContext
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
}
val processor = DownloadProcessor(notify)
val zip = context.cachedFile("lsposed.zip")
val shamiko = appContext.cachedFile("shamiko.zip")
runBlocking {
ServiceLocator.networkService.fetchFile(LSPOSED_URL).byteStream().use {
processor.handleModule(it, zip.toUri())
testContext.assets.open("shamiko.zip").use {
processor.handleModule(it, shamiko.toUri())
}
assertTrue(
"LSPosed installation failed",
FlashZip(zip.toUri(), TimberLog, TimberLog).exec()
)
checkModuleZip(shamiko)
if (shamiko()) {
assertTrue(
"Shamiko installation failed",
FlashZip(shamiko.toUri(), TimberLog, TimberLog).exec()
)
}
}
val lsp = appContext.cachedFile("lsposed.zip")
runBlocking {
testContext.assets.open("lsposed.zip").use {
processor.handleModule(it, lsp.toUri())
}
checkModuleZip(lsp)
if (lsposed()) {
assertTrue(
"LSPosed installation failed",
FlashZip(lsp.toUri(), TimberLog, TimberLog).exec()
)
}
}
if (testModules()) {
val root = RootUtils.fs.getFile(Const.MODULE_PATH)
setupModule01(root)
setupModule02(root)
setupModule03(root)
}
}
@@ -82,7 +189,7 @@ class Environment : BaseTest {
assertTrue(
"App hiding failed",
AppMigration.patchAndHide(
context = context,
context = appContext,
label = "Settings",
pkg = "repackaged.$APP_PACKAGE_NAME"
)
@@ -95,7 +202,7 @@ class Environment : BaseTest {
runBlocking {
assertTrue(
"App restoration failed",
AppMigration.restoreApp(context)
AppMigration.restoreApp(appContext)
)
}
}

View File

@@ -7,12 +7,12 @@ public class ProviderInstaller {
private static final String GMS_PACKAGE_NAME = "com.google.android.gms";
public static boolean install(Context context) {
public static void install(Context context) {
try {
// Check if gms is a system app
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(GMS_PACKAGE_NAME, 0);
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
return false;
return;
}
// Try installing new SSL provider from Google Play Service
@@ -22,9 +22,7 @@ public class ProviderInstaller {
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
.getMethod("insertProvider", Context.class)
.invoke(null, gms);
} catch (Exception e) {
return false;
} catch (Exception ignored) {
}
return true;
}
}

View File

@@ -20,7 +20,7 @@ android {
}
}
setupAppCommon()
setupTestApk()
dependencies {
implementation(libs.test.runner)

201
build.py
View File

@@ -2,7 +2,6 @@
import argparse
import copy
import glob
import lzma
import multiprocessing
import os
import platform
@@ -57,14 +56,6 @@ if is_windows:
if not sys.version_info >= (3, 8):
error("Requires Python 3.8+")
try:
sdk_path = Path(os.environ["ANDROID_HOME"])
except KeyError:
try:
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
except KeyError:
error("Please set Android SDK path to environment variable ANDROID_HOME")
cpu_count = multiprocessing.cpu_count()
os_name = platform.system().lower()
@@ -80,17 +71,6 @@ default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
support_targets = default_targets | {"resetprop"}
rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
# Common paths
ndk_root = sdk_path / "ndk"
ndk_path = ndk_root / "magisk"
ndk_build = ndk_path / "ndk-build"
rust_bin = ndk_path / "toolchains" / "rust" / "bin"
llvm_bin = ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
cargo = rust_bin / "cargo"
gradlew = Path.cwd() / "gradlew"
adb_path = sdk_path / "platform-tools" / "adb"
native_gen_path = Path("native", "out", "generated").resolve()
# Global vars
config = {}
args = {}
@@ -162,10 +142,6 @@ def cmd_out(cmds: list):
)
def xz(data):
return lzma.compress(data, preset=9, check=lzma.CHECK_NONE)
###############
# Build Native
###############
@@ -256,6 +232,7 @@ def build_cpp_src(targets: set):
def run_cargo(cmds):
ensure_paths()
env = os.environ.copy()
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
@@ -333,6 +310,7 @@ def dump_flag_header():
flag_txt += f'#define MAGISK_VER_CODE {config["versionCode"]}\n'
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
native_gen_path = Path("native", "out", "generated")
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
write_if_diff(native_gen_path / "flags.h", flag_txt)
@@ -342,6 +320,8 @@ def dump_flag_header():
def build_native():
ensure_paths()
# Verify NDK install
try:
with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
@@ -401,13 +381,14 @@ def find_jdk():
if no_jdk:
error(
"Please set Android Studio's path to environment variable ANDROID_STUDIO,\n"
+ "or install JDK 17 and make sure 'javac' is available in PATH"
+ "or install JDK 21 and make sure 'javac' is available in PATH"
)
return env
def build_apk(module: str):
ensure_paths()
env = find_jdk()
build_type = "Release" if args.release else "Debug"
@@ -479,6 +460,7 @@ def build_test():
def cleanup():
ensure_paths()
support_targets = {"native", "cpp", "rust", "app"}
if args.targets:
targets = set(args.targets) & support_targets
@@ -520,6 +502,17 @@ def build_all():
############
def clippy_cli():
args.force_out = True
os.chdir(Path("native", "src"))
cmds = ["clippy", "--no-deps", "--target", ""]
for triple in build_abis.values():
cmds[3] = triple
run_cargo(cmds)
run_cargo(cmds + ["--release"])
os.chdir(Path("..", ".."))
def cargo_cli():
args.force_out = True
if len(args.commands) >= 1 and args.commands[0] == "--":
@@ -530,6 +523,7 @@ def cargo_cli():
def setup_ndk():
ensure_paths()
ndk_ver = config["ondkVersion"]
url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz"
ndk_archive = url.split("/")[-1]
@@ -548,14 +542,54 @@ def setup_ndk():
mv(ondk_path, ndk_path)
def setup_rustup():
wrapper_dir = Path(args.wrapper_dir)
rm_rf(wrapper_dir)
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
if "CARGO_HOME" in os.environ:
cargo_home = Path(os.environ["CARGO_HOME"])
else:
cargo_home = Path.home() / ".cargo"
cargo_bin = cargo_home / "bin"
for src in cargo_bin.iterdir():
tgt = wrapper_dir / src.name
tgt.symlink_to(f"rustup{EXE_EXT}")
# Build rustup_wrapper
wrapper_src = Path("tools", "rustup_wrapper")
cargo_toml = wrapper_src / "Cargo.toml"
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
if args.verbose > 1:
cmds.append("--verbose")
run_cargo(cmds)
# Replace rustup with wrapper
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
wrapper.unlink(missing_ok=True)
cp(wrapper_src / "target" / "release" / (f"rustup_wrapper{EXE_EXT}"), wrapper)
wrapper.chmod(0o755)
##################
# AVD and testing
##################
def push_files(script):
if args.build:
build_all()
ensure_adb()
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
if not abi:
error("Cannot detect emulator ABI")
apk = Path(
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
)
if args.apk:
apk = Path(args.apk)
else:
apk = Path(
config["outdir"], ("app-release.apk" if args.release else "app-debug.apk")
)
# Extract busybox from APK
busybox = Path(config["outdir"], "busybox")
@@ -577,9 +611,6 @@ def push_files(script):
def setup_avd():
if not args.skip:
build_all()
header("* Setting up emulator")
push_files(Path("scripts", "avd_magisk.sh"))
@@ -590,17 +621,8 @@ def setup_avd():
def patch_avd_file():
if not args.skip:
build_all()
input = Path(args.image)
if args.output:
output = Path(args.output)
else:
output = input.parent / f"{input.name}.magisk"
src_file = f"/data/local/tmp/{input.name}"
out_file = f"{src_file}.magisk"
output = Path(args.output)
header(f"* Patching {input.name}")
@@ -610,6 +632,9 @@ def patch_avd_file():
if proc.returncode != 0:
error("adb push failed!")
src_file = f"/data/local/tmp/{input.name}"
out_file = f"{src_file}.magisk"
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_patch.sh", src_file])
if proc.returncode != 0:
error("avd_patch.sh failed!")
@@ -621,37 +646,48 @@ def patch_avd_file():
header(f"Output: {output}")
def setup_rustup():
wrapper_dir = Path(args.wrapper_dir)
rm_rf(wrapper_dir)
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
if "CARGO_HOME" in os.environ:
cargo_home = Path(os.environ["CARGO_HOME"])
else:
cargo_home = Path.home() / ".cargo"
cargo_bin = cargo_home / "bin"
for src in cargo_bin.iterdir():
tgt = wrapper_dir / src.name
tgt.symlink_to(src)
# Build rustup_wrapper
wrapper_src = Path("tools", "rustup_wrapper")
cargo_toml = wrapper_src / "Cargo.toml"
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
if args.verbose > 1:
cmds.append("--verbose")
run_cargo(cmds)
# Replace rustup with wrapper
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
wrapper.unlink(missing_ok=True)
cp(wrapper_src / "target" / "release" / (f"rustup_wrapper{EXE_EXT}"), wrapper)
wrapper.chmod(0o755)
##########################
# Config, paths, argparse
##########################
##################
# Config and args
##################
def ensure_paths():
global sdk_path, ndk_root, ndk_path, ndk_build, rust_bin
global llvm_bin, cargo, gradlew, adb_path, native_gen_path
# Skip if already initialized
if "sdk_path" in globals():
return
try:
sdk_path = Path(os.environ["ANDROID_HOME"])
except KeyError:
try:
sdk_path = Path(os.environ["ANDROID_SDK_ROOT"])
except KeyError:
error("Please set Android SDK path to environment variable ANDROID_HOME")
ndk_root = sdk_path / "ndk"
ndk_path = ndk_root / "magisk"
ndk_build = ndk_path / "ndk-build"
rust_bin = ndk_path / "toolchains" / "rust" / "bin"
llvm_bin = (
ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
)
cargo = rust_bin / "cargo"
adb_path = sdk_path / "platform-tools" / "adb"
gradlew = Path.cwd() / "gradlew"
# We allow using several functionality with only ADB
def ensure_adb():
global adb_path
if "adb_path" not in globals():
adb_path = shutil.which("adb")
if not adb_path:
error("Command 'adb' cannot be found in PATH")
else:
adb_path = Path(adb_path)
def parse_props(file):
@@ -751,17 +787,19 @@ def parse_args():
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
emu_parser = subparsers.add_parser("emulator", help="setup AVD for development")
emu_parser.add_argument("apk", help="a Magisk APK to use", nargs="?")
emu_parser.add_argument(
"-s", "--skip", action="store_true", help="skip building binaries and the app"
"-b", "--build", action="store_true", help="build before patching"
)
avd_patch_parser = subparsers.add_parser(
"avd_patch", help="patch AVD ramdisk.img or init_boot.img"
)
avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img")
avd_patch_parser.add_argument("output", help="optional output file name", nargs="?")
avd_patch_parser.add_argument("output", help="output file name")
avd_patch_parser.add_argument("--apk", help="a Magisk APK to use")
avd_patch_parser.add_argument(
"-s", "--skip", action="store_true", help="skip building binaries and the app"
"-b", "--build", action="store_true", help="build before patching"
)
cargo_parser = subparsers.add_parser(
@@ -769,6 +807,8 @@ def parse_args():
)
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
clippy_parser = subparsers.add_parser("clippy", help="run clippy on Rust sources")
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
rustup_parser.add_argument(
"wrapper_dir", help="path to setup rustup wrapper binaries"
@@ -778,6 +818,7 @@ def parse_args():
all_parser.set_defaults(func=build_all)
native_parser.set_defaults(func=build_native)
cargo_parser.set_defaults(func=cargo_cli)
clippy_parser.set_defaults(func=clippy_cli)
rustup_parser.set_defaults(func=setup_rustup)
app_parser.set_defaults(func=build_app)
stub_parser.set_defaults(func=build_stub)
@@ -794,7 +835,13 @@ def parse_args():
return parser.parse_args()
args = parse_args()
load_config()
vars(args)["force_out"] = False
args.func()
def main():
global args
args = parse_args()
load_config()
vars(args)["force_out"] = False
args.func()
if __name__ == "__main__":
main()

View File

@@ -74,7 +74,7 @@ fun Project.setupCommon() {
compileSdkVersion(35)
buildToolsVersion = "35.0.1"
ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "28.0.12674087"
ndkVersion = "29.0.13113456"
defaultConfig {
minSdk = 23
@@ -115,6 +115,27 @@ fun Project.setupCommon() {
}
}
private fun Project.downloadFile(url: String, checksum: String): File {
val file = layout.buildDirectory.file(checksum).get().asFile
if (file.exists()) {
val md = MessageDigest.getInstance("SHA-256")
file.inputStream().use { md.update(it.readAllBytes()) }
val hash = HexFormat.of().formatHex(md.digest())
if (hash != checksum) {
file.delete()
}
}
if (!file.exists()) {
file.parentFile.mkdirs()
URI(url).toURL().openStream().use { dl ->
file.outputStream().use {
dl.copyTo(it)
}
}
}
return file
}
const val BUSYBOX_DOWNLOAD_URL =
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip"
const val BUSYBOX_ZIP_CHECKSUM =
@@ -144,24 +165,7 @@ fun Project.setupCoreLib() {
val downloadBusybox by tasks.registering(Copy::class) {
dependsOn(syncLibs)
val bb = layout.buildDirectory.file(BUSYBOX_ZIP_CHECKSUM).get().asFile
if (bb.exists()) {
val md = MessageDigest.getInstance("SHA-256")
bb.inputStream().use { md.update(it.readAllBytes()) }
val hash = HexFormat.of().formatHex(md.digest())
if (hash != BUSYBOX_ZIP_CHECKSUM) {
bb.delete()
}
}
if (!bb.exists()) {
bb.parentFile.mkdirs()
URI(BUSYBOX_DOWNLOAD_URL).toURL().openStream().use { dl ->
bb.outputStream().use {
dl.copyTo(it)
}
}
}
from(zipTree(bb))
from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM)))
include(abiList.map { "$it/libbusybox.so" })
into("src/main/jniLibs")
}
@@ -462,3 +466,31 @@ fun Project.setupStubApk() {
delete.addAll(listOf("src/debug/AndroidManifest.xml", "src/release/AndroidManifest.xml"))
}
}
const val LSPOSED_DOWNLOAD_URL =
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
const val LSPOSED_CHECKSUM =
"0ebc6bcb465d1c4b44b7220ab5f0252e6b4eb7fe43da74650476d2798bb29622"
const val SHAMIKO_DOWNLOAD_URL =
"https://github.com/LSPosed/LSPosed.github.io/releases/download/shamiko-383/Shamiko-v1.2.1-383-release.zip"
const val SHAMIKO_CHECKSUM =
"93754a038c2d8f0e985bad45c7303b96f70a93d8335060e50146f028d3a9b13f"
fun Project.setupTestApk() {
setupAppCommon()
androidApp.applicationVariants.all {
val variantCapped = name.replaceFirstChar { it.uppercase() }
val dlTask by tasks.register("download${variantCapped}Lsposed", Sync::class) {
from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {
rename { "lsposed.zip" }
}
from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {
rename { "shamiko.zip" }
}
into("src/${this@all.name}/assets")
}
mergeAssetsProvider.configure { dependsOn(dlTask) }
}
}

View File

@@ -7,9 +7,10 @@ If you have USB debugging enabled in developer options, connect your phone to th
If unfortunately you do not have USB debugging enabled you can boot using the Safe Mode key combo to cause Magisk to create an empty file named 'disable' in modules directories which disables modules when next booted with Magisk. Most modern Android devices support such a special key combo at boot to enter system Safe Mode as an emergency option, but **please note** that Magisk's key combo detection occurs _earlier_ than system detection so the key combo timing indicated by many online guides may need to be altered to activate Magisk's Safe Mode. (It's possible to activate system Safe Mode but not Magisk Safe Mode and vice versa.)
The following details should ensure that modules are properly disabled:
1) Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled.
2) By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled.
3) By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled.
1. Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled.
2. By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled.
3. By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled.
### Q: Why is X app detecting root?
@@ -20,3 +21,9 @@ Magisk no longer handles root hiding. There are plenty of Magisk/Zygisk modules
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.
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.
### Q: How to use Magisk in the emulator?
With the emulator running and accessible via ADB, run `./build.py emulator <path to Magisk APK>` to temporarily install Magisk on to the emulator. The patch is not persistent, meaning Magisk will be lost after a reboot, so re-execute the script to emulate a reboot if required.
The script is only tested on the official Android Virtual Device (AVD) shipped alongside Android Studio; other emulators may work, but the emulator must have SELinux enabled.

View File

@@ -30,5 +30,5 @@ android.nonFinalResIds=false
# Magisk
magisk.stubVersion=40
magisk.versionCode=28102
magisk.ondkVersion=r28.2
magisk.versionCode=28103
magisk.ondkVersion=r29.0

View File

@@ -1,7 +1,7 @@
[versions]
kotlin = "2.1.0"
android = "8.8.0"
ksp = "2.1.0-1.0.29"
kotlin = "2.1.10"
android = "8.9.0"
ksp = "2.1.10-1.0.31"
rikka = "1.3.0"
navigation = "2.8.4"
libsu = "6.0.0"

View File

@@ -8,5 +8,5 @@ target-dir = "../out/rust"
[unstable]
build-std = ["std", "panic_abort"]
build-std-features = ["panic_immediate_abort"]
build-std-features = ["panic_immediate_abort", "optimize_for_size"]
profile-rustflags = true

View File

@@ -18,8 +18,6 @@ LOCAL_SRC_FILES := \
core/applets.cpp \
core/magisk.cpp \
core/daemon.cpp \
core/bootstages.cpp \
core/socket.cpp \
core/scripting.cpp \
core/selinux.cpp \
core/sqlite.cpp \
@@ -30,9 +28,7 @@ LOCAL_SRC_FILES := \
core/su/su.cpp \
core/su/connect.cpp \
core/su/pts.cpp \
core/su/su_daemon.cpp \
core/zygisk/entry.cpp \
core/zygisk/main.cpp \
core/zygisk/module.cpp \
core/zygisk/hook.cpp \
core/deny/cli.cpp \
@@ -67,11 +63,9 @@ LOCAL_STATIC_LIBRARIES := \
libinit-rs
LOCAL_SRC_FILES := \
init/init.cpp \
init/mount.cpp \
init/rootdir.cpp \
init/getinfo.cpp \
init/twostage.cpp \
init/selinux.cpp \
init/init-rs.cpp
@@ -79,6 +73,7 @@ LOCAL_LDFLAGS := -static
ifdef B_CRT0
LOCAL_STATIC_LIBRARIES += crt0
LOCAL_LDFLAGS += -Wl,--defsym=vfprintf=tiny_vfprintf
endif
include $(BUILD_EXECUTABLE)
@@ -93,8 +88,6 @@ LOCAL_STATIC_LIBRARIES := \
libbase \
liblzma \
liblz4 \
libbz2 \
libz \
libzopfli \
libboot-rs
@@ -125,8 +118,6 @@ LOCAL_STATIC_LIBRARIES := \
libpolicy \
libpolicy-rs
LOCAL_SRC_FILES := sepolicy/main.cpp
include $(BUILD_EXECUTABLE)
endif

305
native/src/Cargo.lock generated
View File

@@ -10,17 +10,20 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "argh"
version = "0.1.12"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240"
dependencies = [
"argh_derive",
"argh_shared",
"rust-fuzzy-search",
]
[[package]]
name = "argh_derive"
version = "0.1.12"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803"
dependencies = [
"argh_shared",
"proc-macro2",
@@ -30,11 +33,9 @@ dependencies = [
[[package]]
name = "argh_shared"
version = "0.1.12"
source = "git+https://github.com/google/argh.git?rev=1c632b046d084e7bde86b82dfc969b30b4647c8c#1c632b046d084e7bde86b82dfc969b30b4647c8c"
dependencies = [
"serde",
]
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6"
[[package]]
name = "autocfg"
@@ -96,18 +97,18 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.20.0"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a"
dependencies = [
"proc-macro2",
"quote",
@@ -122,9 +123,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.3"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
@@ -137,18 +138,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.23"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [
"anstyle",
"clap_lex",
@@ -173,9 +174,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.10.0-rc.3"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ff6be19477a1bd5441f382916a89bc2a0b2c35db6d41e0f6e8538bf6d6463f"
checksum = "1cb3c4a0d3776f7535c32793be81d6d5fec0d48ac70955d9834e643aa249a52f"
[[package]]
name = "const_format"
@@ -199,22 +200,23 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-bigint"
version = "0.6.0-rc.6"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d748d1f5b807ee6d0df5a548d0130417295c3aaed1dcbbb3d6a2e7106e11fcca"
checksum = "96272c2ff28b807e09250b180ad1fb7889a3258f7455759b5c3c58b719467130"
dependencies = [
"hybrid-array",
"num-traits",
"rand_core",
"serdect",
"subtle",
"zeroize",
]
@@ -230,9 +232,19 @@ dependencies = [
"rand_core",
]
[[package]]
name = "crypto-primes"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76db6c35962f23f58ab08c156831c55caf69fe85a63ae3780d4fdb24c38b32b6"
dependencies = [
"crypto-bigint",
"rand_core",
]
[[package]]
name = "cxx"
version = "1.0.133"
version = "1.0.137"
dependencies = [
"cc",
"cxxbridge-cmd",
@@ -243,7 +255,7 @@ dependencies = [
[[package]]
name = "cxx-gen"
version = "0.7.133"
version = "0.7.137"
dependencies = [
"codespan-reporting",
"proc-macro2",
@@ -253,7 +265,7 @@ dependencies = [
[[package]]
name = "cxxbridge-cmd"
version = "1.0.133"
version = "1.0.137"
dependencies = [
"clap",
"codespan-reporting",
@@ -264,11 +276,11 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.133"
version = "1.0.137"
[[package]]
name = "cxxbridge-macro"
version = "1.0.133"
version = "1.0.137"
dependencies = [
"proc-macro2",
"quote",
@@ -300,6 +312,15 @@ dependencies = [
"syn",
]
[[package]]
name = "derive"
version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.11.0-pre.9"
@@ -371,9 +392,9 @@ checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
[[package]]
name = "foldhash"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "getrandom"
@@ -426,31 +447,34 @@ dependencies = [
]
[[package]]
name = "lazy_static"
version = "1.5.0"
name = "libbz2-rs-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
checksum = "0864a00c8d019e36216b69c2c4ce50b83b7bd966add3cf5ba554ec44f8bebcf5"
dependencies = [
"spin",
"libc",
]
[[package]]
name = "libc"
version = "0.2.168"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "libm"
version = "0.2.11"
name = "libz-rs-sys"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d"
dependencies = [
"zlib-rs",
]
[[package]]
name = "log"
version = "0.4.22"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "magisk"
@@ -461,6 +485,7 @@ dependencies = [
"bytemuck",
"cxx",
"cxx-gen",
"derive",
"num-derive",
"num-traits",
"pb-rs",
@@ -474,6 +499,7 @@ version = "0.0.0"
dependencies = [
"argh",
"base",
"block-buffer",
"bytemuck",
"byteorder",
"cxx",
@@ -481,6 +507,8 @@ dependencies = [
"der",
"digest",
"fdt",
"libbz2-rs-sys",
"libz-rs-sys",
"num-traits",
"p256",
"p384",
@@ -488,6 +516,7 @@ dependencies = [
"pb-rs",
"quick-protobuf",
"rsa",
"sec1",
"sha1",
"sha2",
"size",
@@ -508,6 +537,7 @@ dependencies = [
name = "magiskpolicy"
version = "0.0.0"
dependencies = [
"argh",
"base",
"cxx",
"cxx-gen",
@@ -535,23 +565,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand",
"smallvec",
"zeroize",
]
[[package]]
name = "num-derive"
version = "0.4.2"
@@ -563,26 +576,6 @@ dependencies = [
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -590,7 +583,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@@ -663,23 +655,14 @@ dependencies = [
[[package]]
name = "pkcs8"
version = "0.11.0-rc.1"
version = "0.11.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eacd2c7141f32aef1cfd1ad0defb5287a3d94592d7ab57c1ae20e3f9f1f0db1f"
checksum = "f22636de7c995e997ed3d8d2949b7414d4faba3efa7312a6c0e75d875a14bdd4"
dependencies = [
"der",
"spki",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "primefield"
version = "0.14.0-pre.0"
@@ -697,9 +680,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@@ -714,33 +697,13 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -762,15 +725,14 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.10.0-pre.3"
version = "0.10.0-pre.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07058e83b684989ab0559f9e22322f4e3f7e49147834ed0bae40486b9e70473c"
checksum = "e82e90f434676d49758cab5b3ff2cc1fd8f12bf7d79b70c6088bc5a4e7c63270"
dependencies = [
"const-oid",
"crypto-bigint",
"crypto-primes",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core",
@@ -782,10 +744,16 @@ dependencies = [
]
[[package]]
name = "rustversion"
version = "1.0.18"
name = "rust-fuzzy-search"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "sec1"
@@ -803,24 +771,34 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.215"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.215"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serdect"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
dependencies = [
"base16ct",
"serde",
]
[[package]]
name = "sha1"
version = "0.11.0-pre.4"
@@ -861,21 +839,9 @@ dependencies = [
[[package]]
name = "size"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b"
[[package]]
name = "spki"
@@ -901,9 +867,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.90"
version = "2.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
dependencies = [
"proc-macro2",
"quote",
@@ -921,18 +887,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.6"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.6"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
@@ -941,9 +907,9 @@ dependencies = [
[[package]]
name = "tls_codec"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a"
checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b"
dependencies = [
"tls_codec_derive",
"zeroize",
@@ -951,9 +917,9 @@ dependencies = [
[[package]]
name = "tls_codec_derive"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd"
dependencies = [
"proc-macro2",
"quote",
@@ -962,15 +928,15 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.17.0"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.14"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
@@ -1084,27 +1050,6 @@ dependencies = [
"tls_codec",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.1"
@@ -1124,3 +1069,9 @@ dependencies = [
"quote",
"syn",
]
[[package]]
name = "zlib-rs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"

View File

@@ -1,8 +1,12 @@
[workspace]
exclude = ["external"]
members = ["base", "boot", "core", "init", "sepolicy"]
members = ["base", "boot", "core", "core/derive", "init", "sepolicy"]
resolver = "2"
[workspace.package]
version = "0.0.0"
edition = "2024"
[workspace.dependencies]
cxx = { path = "external/cxx-rs" }
cxx-gen = { path = "external/cxx-rs/gen/lib" }
@@ -12,25 +16,30 @@ num-traits = "0.2"
num-derive = "0.4"
thiserror = "2.0"
byteorder = "1"
size = "0.4"
size = "0.5"
sha1 = "0.11.0-pre.4"
sha2 = "=0.11.0-pre.4"
sha2 = "0.11.0-pre.4"
digest = "0.11.0-pre.9"
p256 = "0.14.0-pre.2"
p384 = "0.14.0-pre.2"
p521 = "0.14.0-pre.2"
rsa = "0.10.0-pre.3"
rsa = "0.10.0-pre.4"
x509-cert = "0.3.0-pre.0"
der = "0.8.0-rc.1"
bytemuck = "1.16"
fdt = "0.1"
const_format = "0.2"
bit-set = "0.8"
syn = "2"
quote = "1"
proc-macro2 = "1"
argh = { version = "0.1.13", default-features = false }
libz-rs-sys = { version = "0.4.2", default-features = false, features=["c-allocator"] }
libbz2-rs-sys = { version = "0.1.3", default-features = false, features = ["c-allocator"] }
[workspace.dependencies.argh]
git = "https://github.com/google/argh.git"
rev = "1c632b046d084e7bde86b82dfc969b30b4647c8c"
default-features = false
# Pin version to prevent cargo update break builds
block-buffer = "=0.11.0-rc.3"
sec1 = "=0.8.0-rc.3"
[workspace.dependencies.pb-rs]
git = "https://github.com/tafia/quick-protobuf.git"

View File

@@ -1,7 +1,7 @@
[package]
name = "base"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
path = "lib.rs"

View File

@@ -1,7 +1,7 @@
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
gen_cxx_binding("base-rs");

View File

@@ -1,14 +1,14 @@
use cxx::{type_id, ExternType};
use libc::c_char;
use std::borrow::Borrow;
use std::cmp::min;
use std::ffi::{CStr, FromBytesWithNulError, OsStr};
use std::fmt::{Arguments, Debug, Display, Formatter, Write};
use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::{Deref, DerefMut};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::Utf8Error;
use std::{fmt, mem, slice, str};
use cxx::{type_id, ExternType};
use libc::c_char;
use thiserror::Error;
use crate::slice_from_ptr_mut;
@@ -18,23 +18,114 @@ use crate::slice_from_ptr_mut;
// Several Utf8CStr types:
//
// Utf8CStr: can only exist as reference, similar to &str
// Utf8CString: dynamically sized buffer allocated on the heap
// Utf8CString: dynamically sized buffer allocated on the heap, similar to String
// Utf8CStrBufRef: reference to a fixed sized buffer
// Utf8CStrBufArr<N>: fixed sized buffer allocated on the stack
//
// For easier usage, please use the helper functions in cstr_buf.
//
// In most cases, these are the types being used
//
// &Utf8CStr: whenever a printable null terminated string is needed
// &mut dyn Utf8CStrWrite: whenever we need a buffer that only needs to support appending
// strings to the end, and has to be null terminated
// &mut dyn Utf8CStrBuf: whenever we need a buffer that needs to support appending
// strings to the end, and has to be null terminated
// &mut dyn Utf8CStrBuf: whenever we need a pre-allocated buffer that is large enough to fit
// in the result, and has to be null terminated
//
// All types dereferences to &Utf8CStr.
// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrWrite.
// Utf8CStrBufRef and Utf8CStrBufArr<N> implements Utf8CStrBuf.
// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrBuf.
fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
// Public helper functions
pub mod cstr_buf {
use super::{Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString};
#[inline(always)]
pub fn with_capacity(capacity: usize) -> Utf8CString {
Utf8CString::with_capacity(capacity)
}
#[inline(always)]
pub fn default() -> Utf8CStrBufArr<4096> {
Utf8CStrBufArr::default()
}
#[inline(always)]
pub fn new<const N: usize>() -> Utf8CStrBufArr<N> {
Utf8CStrBufArr::new()
}
#[inline(always)]
pub fn wrap(buf: &mut [u8]) -> Utf8CStrBufRef {
Utf8CStrBufRef::from(buf)
}
#[inline(always)]
pub unsafe fn wrap_ptr<'a>(buf: *mut u8, len: usize) -> Utf8CStrBufRef<'a> {
unsafe { Utf8CStrBufRef::from_ptr(buf, len) }
}
}
// Trait definitions
pub trait Utf8CStrBuf:
Write + AsRef<Utf8CStr> + AsMut<Utf8CStr> + Deref<Target = Utf8CStr> + DerefMut
{
// The length of the string without the terminating null character.
// assert_true(len <= capacity - 1)
fn len(&self) -> usize;
// Set the length of the string
//
// It is your responsibility to:
// 1. Null terminate the string by setting the next byte after len to null
// 2. Ensure len <= capacity - 1
// 3. All bytes from 0 to len is valid UTF-8 and does not contain null
unsafe fn set_len(&mut self, len: usize);
fn push_str(&mut self, s: &str) -> usize;
fn push_lossy(&mut self, s: &[u8]) -> usize;
// The capacity of the internal buffer. The maximum string length this buffer can contain
// is capacity - 1, because the last byte is reserved for the terminating null character.
fn capacity(&self) -> usize;
fn clear(&mut self);
#[inline(always)]
fn is_empty(&self) -> bool {
self.len() == 0
}
}
trait Utf8CStrBufWithSlice: Utf8CStrBuf {
fn buf(&self) -> &[u8];
unsafe fn mut_buf(&mut self) -> &mut [u8];
}
trait AsUtf8CStr {
fn as_utf8_cstr(&self) -> &Utf8CStr;
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr;
}
impl<T: Utf8CStrBufWithSlice> AsUtf8CStr for T {
#[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe { Utf8CStr::from_bytes_unchecked(self.buf().get_unchecked(..(self.len() + 1))) }
}
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe {
let len = self.len() + 1;
Utf8CStr::from_bytes_unchecked_mut(self.mut_buf().get_unchecked_mut(..len))
}
}
}
// Implementation for Utf8CString
fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBufWithSlice, s: &[u8]) -> usize {
let mut used = buf.len();
if used >= buf.capacity() - 1 {
// Truncate
@@ -51,7 +142,7 @@ fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
len
}
fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrWrite, s: &[u8]) -> usize {
fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
let mut len = 0_usize;
for chunk in s.utf8_chunks() {
len += buf.push_str(chunk.valid());
@@ -62,44 +153,6 @@ fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrWrite, s: &[u8]) -> usize {
len
}
// Trait definitions
pub trait Utf8CStrWrite:
Write + AsRef<Utf8CStr> + AsMut<Utf8CStr> + Deref<Target = Utf8CStr> + DerefMut
{
fn len(&self) -> usize;
#[inline(always)]
fn is_empty(&self) -> bool {
self.len() == 0
}
fn push_str(&mut self, s: &str) -> usize;
fn push_lossy(&mut self, s: &[u8]) -> usize;
fn clear(&mut self);
}
pub trait Utf8CStrBuf: Utf8CStrWrite {
fn buf(&self) -> &[u8];
// Modifying the underlying buffer or length is unsafe because it can either:
// 1. Break null termination
// 2. Break UTF-8 validation
// 3. Introduce inner null byte in the string
unsafe fn mut_buf(&mut self) -> &mut [u8];
unsafe fn set_len(&mut self, len: usize);
#[inline(always)]
fn capacity(&self) -> usize {
self.buf().len()
}
}
trait AsUtf8CStr {
fn as_utf8_cstr(&self) -> &Utf8CStr;
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr;
}
// Implementation for Utf8CString
pub trait StringExt {
fn nul_terminate(&mut self) -> &mut [u8];
}
@@ -132,13 +185,24 @@ impl StringExt for PathBuf {
}
}
#[derive(Default)]
pub struct Utf8CString(String);
impl Default for Utf8CString {
fn default() -> Self {
Utf8CString::with_capacity(256)
}
}
impl Utf8CString {
#[inline(always)]
pub fn new() -> Self {
Self::default()
pub fn with_capacity(capacity: usize) -> Utf8CString {
Utf8CString::from(String::with_capacity(capacity))
}
pub fn ensure_capacity(&mut self, capacity: usize) {
if self.capacity() >= capacity {
return;
}
self.0.reserve(capacity - self.0.len())
}
}
@@ -151,16 +215,28 @@ impl AsUtf8CStr for Utf8CString {
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
Utf8CStr::from_string(&mut self.0)
// SAFETY: the internal string is always null terminated
unsafe {
mem::transmute(slice::from_raw_parts_mut(
self.0.as_mut_ptr(),
self.0.len() + 1,
))
}
}
}
impl Utf8CStrWrite for Utf8CString {
impl Utf8CStrBuf for Utf8CString {
#[inline(always)]
fn len(&self) -> usize {
self.0.len()
}
unsafe fn set_len(&mut self, len: usize) {
unsafe {
self.0.as_mut_vec().set_len(len);
}
}
fn push_str(&mut self, s: &str) -> usize {
self.0.push_str(s);
self.0.nul_terminate();
@@ -172,6 +248,10 @@ impl Utf8CStrWrite for Utf8CString {
utf8_cstr_append_lossy(self, s)
}
fn capacity(&self) -> usize {
self.0.capacity()
}
fn clear(&mut self) {
self.0.clear();
self.0.nul_terminate();
@@ -185,24 +265,9 @@ impl From<String> for Utf8CString {
}
}
// Implementations for Utf8CStrBuf
impl<T: Utf8CStrBuf> AsUtf8CStr for T {
#[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe { Utf8CStr::from_bytes_unchecked(self.buf().get_unchecked(..(self.len() + 1))) }
}
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe {
let len = self.len() + 1;
Utf8CStr::from_bytes_unchecked_mut(self.mut_buf().get_unchecked_mut(..len))
}
impl Borrow<Utf8CStr> for Utf8CString {
fn borrow(&self) -> &Utf8CStr {
self.deref()
}
}
@@ -214,7 +279,7 @@ pub struct Utf8CStrBufRef<'a> {
impl<'a> Utf8CStrBufRef<'a> {
pub unsafe fn from_ptr(buf: *mut u8, len: usize) -> Utf8CStrBufRef<'a> {
Self::from(slice_from_ptr_mut(buf, len))
unsafe { Self::from(slice_from_ptr_mut(buf, len)) }
}
}
@@ -225,7 +290,7 @@ impl<'a> From<&'a mut [u8]> for Utf8CStrBufRef<'a> {
}
}
impl Utf8CStrBuf for Utf8CStrBufRef<'_> {
impl Utf8CStrBufWithSlice for Utf8CStrBufRef<'_> {
#[inline(always)]
fn buf(&self) -> &[u8] {
self.buf
@@ -235,11 +300,6 @@ impl Utf8CStrBuf for Utf8CStrBufRef<'_> {
unsafe fn mut_buf(&mut self) -> &mut [u8] {
self.buf
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
}
// UTF-8 validated + null terminated buffer on the stack
@@ -257,7 +317,7 @@ impl<const N: usize> Utf8CStrBufArr<N> {
}
}
impl<const N: usize> Utf8CStrBuf for Utf8CStrBufArr<N> {
impl<const N: usize> Utf8CStrBufWithSlice for Utf8CStrBufArr<N> {
#[inline(always)]
fn buf(&self) -> &[u8] {
&self.buf
@@ -267,16 +327,6 @@ impl<const N: usize> Utf8CStrBuf for Utf8CStrBufArr<N> {
unsafe fn mut_buf(&mut self) -> &mut [u8] {
&mut self.buf
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
#[inline(always)]
fn capacity(&self) -> usize {
N
}
}
impl Default for Utf8CStrBufArr<4096> {
@@ -310,13 +360,6 @@ impl Utf8CStr {
Self::from_cstr(CStr::from_bytes_with_nul(buf)?)
}
pub fn from_bytes_mut(buf: &mut [u8]) -> Result<&mut Utf8CStr, StrErr> {
CStr::from_bytes_with_nul(buf)?;
str::from_utf8(buf)?;
// Both condition checked
unsafe { Ok(mem::transmute::<&mut [u8], &mut Utf8CStr>(buf)) }
}
pub fn from_string(s: &mut String) -> &mut Utf8CStr {
let buf = s.nul_terminate();
// SAFETY: the null byte is explicitly added to the buffer
@@ -324,13 +367,13 @@ impl Utf8CStr {
}
#[inline(always)]
pub unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr {
mem::transmute(buf)
pub const unsafe fn from_bytes_unchecked(buf: &[u8]) -> &Utf8CStr {
unsafe { mem::transmute(buf) }
}
#[inline(always)]
pub unsafe fn from_bytes_unchecked_mut(buf: &mut [u8]) -> &mut Utf8CStr {
mem::transmute(buf)
unsafe fn from_bytes_unchecked_mut(buf: &mut [u8]) -> &mut Utf8CStr {
unsafe { mem::transmute(buf) }
}
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> {
@@ -341,8 +384,10 @@ impl Utf8CStr {
}
pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a Utf8CStr {
let cstr = CStr::from_ptr(ptr);
Self::from_bytes_unchecked(cstr.to_bytes_with_nul())
unsafe {
let cstr = CStr::from_ptr(ptr);
Self::from_bytes_unchecked(cstr.to_bytes_with_nul())
}
}
#[inline(always)]
@@ -397,6 +442,16 @@ impl DerefMut for Utf8CStr {
}
}
impl ToOwned for Utf8CStr {
type Owned = Utf8CString;
fn to_owned(&self) -> Utf8CString {
let mut s = Utf8CString::with_capacity(self.len() + 1);
s.push_str(self.as_str());
s
}
}
// Notice that we only implement ExternType on Utf8CStr *reference*
unsafe impl ExternType for &Utf8CStr {
type Id = type_id!("rust::Utf8CStr");
@@ -446,47 +501,106 @@ impl DerefMut for FsPath {
}
}
pub struct FsPathBuf<'a>(&'a mut dyn Utf8CStrWrite);
#[repr(transparent)]
pub struct FsPathFollow(Utf8CStr);
impl<'a> FsPathBuf<'a> {
pub fn new(value: &'a mut dyn Utf8CStrWrite) -> Self {
value.clear();
FsPathBuf(value)
impl Deref for FsPathFollow {
type Target = Utf8CStr;
#[inline(always)]
fn deref(&self) -> &Utf8CStr {
&self.0
}
}
impl DerefMut for FsPathFollow {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Utf8CStr {
&mut self.0
}
}
enum Utf8CStrBufOwned<const N: usize> {
Dynamic(Utf8CString),
Fixed(Utf8CStrBufArr<N>),
}
impl<const N: usize> Deref for Utf8CStrBufOwned<N> {
type Target = dyn Utf8CStrBuf;
fn deref(&self) -> &Self::Target {
match self {
Utf8CStrBufOwned::Dynamic(s) => s,
Utf8CStrBufOwned::Fixed(arr) => arr,
}
}
}
impl<const N: usize> DerefMut for Utf8CStrBufOwned<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Utf8CStrBufOwned::Dynamic(s) => s,
Utf8CStrBufOwned::Fixed(arr) => arr,
}
}
}
pub struct FsPathBuf<const N: usize>(Utf8CStrBufOwned<N>);
impl FsPathBuf<0> {
pub fn new_dynamic(capacity: usize) -> Self {
FsPathBuf(Utf8CStrBufOwned::Dynamic(Utf8CString::with_capacity(
capacity,
)))
}
}
impl Default for FsPathBuf<4096> {
fn default() -> Self {
FsPathBuf(Utf8CStrBufOwned::Fixed(cstr_buf::default()))
}
}
impl<const N: usize> FsPathBuf<N> {
pub fn new() -> Self {
FsPathBuf(Utf8CStrBufOwned::Fixed(cstr_buf::new::<N>()))
}
pub fn join<T: AsRef<str>>(self, path: T) -> Self {
fn inner(buf: &mut dyn Utf8CStrWrite, path: &str) {
pub fn clear(&mut self) {
self.0.clear();
}
pub fn join<T: AsRef<str>>(mut self, path: T) -> Self {
fn inner(buf: &mut dyn Utf8CStrBuf, path: &str) {
if path.starts_with('/') {
buf.clear();
} else {
}
if !buf.is_empty() && !buf.ends_with('/') {
buf.push_str("/");
}
buf.push_str(path);
}
inner(self.0, path.as_ref());
inner(self.0.deref_mut(), path.as_ref());
self
}
pub fn join_fmt<T: Display>(self, name: T) -> Self {
fn inner(buf: &mut dyn Utf8CStrWrite, path: Arguments) {
buf.write_fmt(path).ok();
}
inner(self.0, format_args!("/{}", name));
pub fn join_fmt<T: Display>(mut self, name: T) -> Self {
self.0.write_fmt(format_args!("/{}", name)).ok();
self
}
}
impl Deref for FsPathBuf<'_> {
impl<const N: usize> Deref for FsPathBuf<N> {
type Target = FsPath;
fn deref(&self) -> &FsPath {
FsPath::from(&self.0)
FsPath::from(self.0.deref())
}
}
impl DerefMut for FsPathBuf<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
FsPath::from_mut(&mut self.0)
impl<const N: usize> DerefMut for FsPathBuf<N> {
fn deref_mut(&mut self) -> &mut FsPath {
FsPath::from_mut(self.0.deref_mut())
}
}
@@ -572,13 +686,14 @@ macro_rules! impl_str {
impl_str!(
(Utf8CStr,)
(FsPath,)
(FsPathBuf<'_>,)
(FsPathFollow,)
(FsPathBuf<N>, const N: usize)
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
);
macro_rules! impl_str_write {
macro_rules! impl_str_buf {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Write for $t {
#[inline(always)]
@@ -610,20 +725,24 @@ macro_rules! impl_str_write {
)*}
}
impl_str_write!(
impl_str_buf!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
);
macro_rules! impl_str_buf {
macro_rules! impl_str_buf_with_slice {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Utf8CStrWrite for $t {
impl<$($g)*> Utf8CStrBuf for $t {
#[inline(always)]
fn len(&self) -> usize {
self.used
}
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
#[inline(always)]
fn push_str(&mut self, s: &str) -> usize {
utf8_cstr_buf_append(self, s.as_bytes())
}
@@ -632,6 +751,10 @@ macro_rules! impl_str_buf {
utf8_cstr_append_lossy(self, s)
}
#[inline(always)]
fn capacity(&self) -> usize {
self.buf.len()
}
#[inline(always)]
fn clear(&mut self) {
self.buf[0] = b'\0';
self.used = 0;
@@ -640,31 +763,32 @@ macro_rules! impl_str_buf {
)*}
}
impl_str_buf!(
impl_str_buf_with_slice!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
);
// The cstr! macro is copied from https://github.com/bytecodealliance/rustix/blob/main/src/cstr.rs
#[macro_export]
macro_rules! cstr {
($($str:tt)*) => {{
assert!(
!($($str)*).bytes().any(|b| b == b'\0'),
"cstr argument contains embedded NUL bytes",
);
($str:expr) => {{
const NULL_STR: &str = $crate::const_format::concatcp!($str, "\0");
#[allow(unused_unsafe)]
unsafe {
$crate::Utf8CStr::from_bytes_unchecked($crate::const_format::concatcp!($($str)*, "\0")
.as_bytes())
$crate::Utf8CStr::from_bytes_unchecked(NULL_STR.as_bytes())
}
}};
}
#[macro_export]
macro_rules! raw_cstr {
($($str:tt)*) => {{
$crate::cstr!($($str)*).as_ptr()
($str:expr) => {{
$crate::cstr!($str).as_ptr()
}};
}
#[macro_export]
macro_rules! path {
($str:expr) => {{
$crate::FsPath::from($crate::cstr!($str))
}};
}

View File

@@ -9,47 +9,53 @@ use libc::{c_char, mode_t};
use crate::files::map_file_at;
pub(crate) use crate::xwrap::*;
use crate::{
clone_attr, cstr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr, CxxResultExt,
Directory, FsPath, Utf8CStr, Utf8CStrBufRef,
clone_attr, cstr, cstr_buf, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr,
CxxResultExt, Directory, FsPath, Utf8CStr,
};
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
let mut buf = Utf8CStrBufRef::from(buf);
let mut buf = cstr_buf::wrap(buf);
fd_path(fd, &mut buf)
.log_cxx_with_msg(|w| w.write_str("fd_path failed"))
.map_or(-1_isize, |_| buf.len() as isize)
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.map_or(-1, |_| buf.len() as isize)
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = cstr_buf::wrap_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
}
Err(_) => -1,
}
}
#[export_name = "mkdirs"]
#[unsafe(export_name = "mkdirs")]
unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).mkdirs(mode).map_or(-1, |_| 0),
Err(_) => -1,
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).mkdirs(mode).map_or(-1, |_| 0),
Err(_) => -1,
}
}
}
#[export_name = "rm_rf"]
#[unsafe(export_name = "rm_rf")]
unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).remove_all().is_ok(),
Err(_) => false,
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).remove_all().is_ok(),
Err(_) => false,
}
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
fn inner(fd: OwnedFd) -> io::Result<()> {
Directory::try_from(fd)?.remove_all()
@@ -83,110 +89,122 @@ pub(crate) unsafe fn readlinkat_for_cxx(
buf: *mut u8,
bufsz: usize,
) -> isize {
// readlinkat() may fail on x86 platform, returning random value
// instead of number of bytes placed in buf (length of link)
cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
libc::memset(buf.cast(), 0, bufsz);
let mut r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r > 0 {
r = libc::strlen(buf.cast()) as isize;
}
} else {
let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
unsafe {
// readlinkat() may fail on x86 platform, returning random value
// instead of number of bytes placed in buf (length of link)
cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
libc::memset(buf.cast(), 0, bufsz);
let mut r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r > 0 {
r = libc::strlen(buf.cast()) as isize;
}
} else {
let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);
if r >= 0 {
*buf.offset(r) = b'\0';
}
}
}
r
}
r
}
#[export_name = "cp_afc"]
#[unsafe(export_name = "cp_afc")]
unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.copy_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("cp_afc {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.copy_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("cp_afc {} -> {} failed", src, dest))
})
.is_ok();
}
}
false
}
false
}
#[export_name = "mv_path"]
#[unsafe(export_name = "mv_path")]
unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.move_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("mv_path {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.move_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("mv_path {} -> {} failed", src, dest))
})
.is_ok();
}
}
false
}
false
}
#[export_name = "link_path"]
#[unsafe(export_name = "link_path")]
unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.link_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("link_path {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.link_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("link_path {} -> {} failed", src, dest))
})
.is_ok();
}
}
false
}
false
}
#[export_name = "clone_attr"]
#[unsafe(export_name = "clone_attr")]
unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char) -> bool {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return clone_attr(src, dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("clone_attr {} -> {} failed", src, dest))
})
.is_ok();
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return clone_attr(src, dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("clone_attr {} -> {} failed", src, dest))
})
.is_ok();
}
}
false
}
false
}
#[export_name = "fclone_attr"]
#[unsafe(export_name = "fclone_attr")]
unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool {
fclone_attr(a, b)
.log_cxx_with_msg(|w| w.write_str("fclone_attr failed"))
.is_ok()
}
#[export_name = "cxx$utf8str$new"]
#[unsafe(export_name = "cxx$utf8str$new")]
unsafe extern "C" fn str_new(this: &mut &Utf8CStr, s: *const u8, len: usize) {
*this = Utf8CStr::from_bytes(slice_from_ptr(s, len)).unwrap_or(cstr!(""));
unsafe {
*this = Utf8CStr::from_bytes(slice_from_ptr(s, len)).unwrap_or(cstr!(""));
}
}
#[export_name = "cxx$utf8str$ptr"]
#[unsafe(export_name = "cxx$utf8str$ptr")]
unsafe extern "C" fn str_ptr(this: &&Utf8CStr) -> *const u8 {
this.as_ptr().cast()
}
#[export_name = "cxx$utf8str$len"]
#[unsafe(export_name = "cxx$utf8str$len")]
unsafe extern "C" fn str_len(this: &&Utf8CStr) -> usize {
this.len()
}

420
native/src/base/dir.rs Normal file
View File

@@ -0,0 +1,420 @@
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{
cstr, cstr_buf, errno, fd_path, fd_set_attr, FileAttr, FsPath, LibcReturn, Utf8CStr,
Utf8CStrBuf,
};
use libc::{dirent, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
use std::ffi::CStr;
use std::fs::File;
use std::ops::Deref;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::{io, mem, slice};
pub struct DirEntry<'a> {
dir: &'a Directory,
entry: &'a dirent,
d_name_len: usize,
}
impl DirEntry<'_> {
pub fn name(&self) -> &CStr {
unsafe {
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
self.d_name_len,
))
}
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
self.dir.path(buf)?;
buf.push_str("/");
buf.push_lossy(self.name().to_bytes());
Ok(())
}
pub fn is_dir(&self) -> bool {
self.d_type == libc::DT_DIR
}
pub fn is_file(&self) -> bool {
self.d_type == libc::DT_REG
}
pub fn is_symlink(&self) -> bool {
self.d_type == libc::DT_LNK
}
pub fn is_block_device(&self) -> bool {
self.d_type == libc::DT_BLK
}
pub fn is_char_device(&self) -> bool {
self.d_type == libc::DT_CHR
}
pub fn is_fifo(&self) -> bool {
self.d_type == libc::DT_FIFO
}
pub fn is_socket(&self) -> bool {
self.d_type == libc::DT_SOCK
}
pub fn unlink(&self) -> io::Result<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe {
libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?;
}
Ok(())
}
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
buf.clear();
unsafe {
let r = readlinkat_for_cxx(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
buf.as_mut_ptr().cast(),
buf.capacity(),
)
.check_os_err()? as usize;
buf.set_len(r);
}
Ok(())
}
unsafe fn open_fd(&self, flags: i32) -> io::Result<RawFd> {
unsafe { self.dir.open_raw_fd(self.name(), flags, 0) }
}
pub fn open_as_dir(&self) -> io::Result<Directory> {
if !self.is_dir() {
return Err(io::Error::from(io::ErrorKind::NotADirectory));
}
unsafe { Directory::try_from(OwnedFd::from_raw_fd(self.open_fd(O_RDONLY)?)) }
}
pub fn open_as_file(&self, flags: i32) -> io::Result<File> {
if self.is_dir() {
return Err(io::Error::from(io::ErrorKind::IsADirectory));
}
unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) }
}
pub fn get_attr(&self) -> io::Result<FileAttr> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).get_attr()
}
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).set_attr(attr)
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).get_secontext(con)
}
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).set_secontext(con)
}
}
impl Deref for DirEntry<'_> {
type Target = dirent;
fn deref(&self) -> &dirent {
self.entry
}
}
pub struct Directory {
dirp: *mut libc::DIR,
}
pub enum WalkResult {
Continue,
Abort,
Skip,
}
impl Directory {
pub fn open(path: &Utf8CStr) -> io::Result<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?;
Ok(Directory { dirp })
}
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.dirp) };
if e.is_null() {
return if *errno() != 0 {
Err(io::Error::last_os_error())
} else {
Ok(None)
};
}
// Skip both "." and ".."
unsafe {
let entry = &*e;
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
if d_name == cstr!(".") || d_name == cstr!("..") {
self.read()
} else {
let e = DirEntry {
dir: self,
entry,
d_name_len: d_name.to_bytes_with_nul().len(),
};
Ok(Some(e))
}
}
}
pub fn rewind(&mut self) {
unsafe { libc::rewinddir(self.dirp) }
}
unsafe fn open_raw_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result<RawFd> {
unsafe {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err()
}
}
pub fn open_fd(&self, name: &Utf8CStr, flags: i32, mode: i32) -> io::Result<OwnedFd> {
unsafe {
self.open_raw_fd(name.as_cstr(), flags, mode)
.map(|fd| OwnedFd::from_raw_fd(fd))
}
}
pub fn contains_path(&self, path: &CStr) -> bool {
// WARNING: Using faccessat is incorrect, because the raw linux kernel syscall
// does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.
// Use fstatat to check the existence of a file instead.
unsafe {
let mut st: libc::stat = mem::zeroed();
libc::fstatat(
self.as_raw_fd(),
path.as_ptr(),
&mut st,
libc::AT_SYMLINK_NOFOLLOW,
) == 0
}
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(&mut self) -> io::Result<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
})?;
Ok(())
}
pub fn copy_into(&mut self, dir: &Directory) -> io::Result<()> {
while let Some(ref e) = self.read()? {
let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
if e.is_dir() {
unsafe {
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?;
src.copy_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?;
let mut dest = unsafe {
File::from_raw_fd(dir.open_raw_fd(
e.name(),
O_WRONLY | O_CREAT | O_TRUNC,
0o777,
)?)
};
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() {
let mut path = cstr_buf::default();
e.read_link(&mut path)?;
unsafe {
libc::symlinkat(path.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr())
.as_os_err()?;
}
new_entry.set_attr(&attr)?;
}
}
Ok(())
}
pub fn move_into(&mut self, dir: &Directory) -> io::Result<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() && dir.contains_path(e.name()) {
// Destination folder exists, needs recursive move
let mut src = e.open_as_dir()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let dest = new_entry.open_as_dir()?;
src.move_into(&dest)?;
return e.unlink();
}
unsafe {
libc::renameat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
)
.as_os_err()?;
}
}
Ok(())
}
pub fn link_into(&mut self, dir: &Directory) -> io::Result<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() {
unsafe {
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?;
src.link_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else {
unsafe {
libc::linkat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
0,
)
.as_os_err()?;
}
}
}
Ok(())
}
}
impl Directory {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.post_order_walk_impl(f)? {
return Ok(Abort);
}
}
match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {}
}
}
}
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => match f(e)? {
Abort => return Ok(Abort),
Skip => continue,
Continue => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.pre_order_walk_impl(f)? {
return Ok(Abort);
}
}
}
},
}
}
}
}
impl TryFrom<OwnedFd> for Directory {
type Error = io::Error;
fn try_from(fd: OwnedFd) -> io::Result<Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }.check_os_err()?;
Ok(Directory { dirp })
}
}
impl AsRawFd for Directory {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.dirp) }
}
}
impl AsFd for Directory {
fn as_fd(&self) -> BorrowedFd {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl Drop for Directory {
fn drop(&mut self) {
unsafe {
libc::closedir(self.dirp);
}
}
}

View File

@@ -1,11 +1,10 @@
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{
cstr, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr,
Utf8CStrWrite,
cstr_buf, errno, error, Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, Utf8CStr,
Utf8CStrBuf,
};
use bytemuck::{bytes_of_mut, Pod};
use bytemuck::{bytes_of, bytes_of_mut, Pod};
use libc::{
c_uint, dirent, makedev, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY,
c_uint, makedev, mode_t, stat, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY,
O_RDWR, O_TRUNC, O_WRONLY,
};
use mem::MaybeUninit;
@@ -14,13 +13,10 @@ use std::cmp::min;
use std::ffi::CStr;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd};
use std::os::fd::{AsFd, BorrowedFd};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::path::Path;
use std::sync::Arc;
use std::{io, mem, ptr, slice};
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
@@ -41,8 +37,7 @@ macro_rules! open_fd {
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
let mut arr = Utf8CStrBufArr::<40>::new();
let path = FsPathBuf::new(&mut arr).join("/proc/self/fd").join_fmt(fd);
let path = FsPathBuf::default().join("/proc/self/fd").join_fmt(fd);
path.read_link(buf)
}
@@ -123,6 +118,7 @@ impl<T: BufRead> BufReadExt for T {
pub trait WriteExt {
fn write_zeros(&mut self, len: usize) -> io::Result<()>;
fn write_pod<F: Pod>(&mut self, data: &F) -> io::Result<()>;
}
impl<T: Write> WriteExt for T {
@@ -135,12 +131,16 @@ impl<T: Write> WriteExt for T {
}
Ok(())
}
fn write_pod<F: Pod>(&mut self, data: &F) -> io::Result<()> {
self.write_all(bytes_of(data))
}
}
pub struct FileAttr {
pub st: libc::stat,
#[cfg(feature = "selinux")]
pub con: Utf8CStrBufArr<128>,
pub con: crate::Utf8CStrBufArr<128>,
}
impl FileAttr {
@@ -148,7 +148,7 @@ impl FileAttr {
FileAttr {
st: unsafe { mem::zeroed() },
#[cfg(feature = "selinux")]
con: Utf8CStrBufArr::new(),
con: crate::Utf8CStrBufArr::new(),
}
}
@@ -187,398 +187,13 @@ impl FileAttr {
}
}
#[cfg(feature = "selinux")]
const XATTR_NAME_SELINUX: &[u8] = b"security.selinux\0";
pub struct DirEntry<'a> {
dir: &'a Directory,
entry: &'a dirent,
d_name_len: usize,
}
impl DirEntry<'_> {
pub fn d_name(&self) -> &CStr {
unsafe {
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
self.d_name_len,
))
}
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
self.dir.path(buf)?;
buf.push_str("/");
buf.push_lossy(self.d_name().to_bytes());
Ok(())
}
pub fn is_dir(&self) -> bool {
self.d_type == libc::DT_DIR
}
pub fn is_file(&self) -> bool {
self.d_type == libc::DT_REG
}
pub fn is_symlink(&self) -> bool {
self.d_type == libc::DT_LNK
}
pub fn is_block_device(&self) -> bool {
self.d_type == libc::DT_BLK
}
pub fn is_char_device(&self) -> bool {
self.d_type == libc::DT_CHR
}
pub fn is_fifo(&self) -> bool {
self.d_type == libc::DT_FIFO
}
pub fn is_socket(&self) -> bool {
self.d_type == libc::DT_SOCK
}
pub fn unlink(&self) -> io::Result<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe {
libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?;
}
Ok(())
}
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
buf.clear();
unsafe {
let r = readlinkat_for_cxx(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
buf.as_mut_ptr().cast(),
buf.capacity(),
)
.check_os_err()? as usize;
buf.set_len(r);
}
Ok(())
}
unsafe fn open_fd(&self, flags: i32) -> io::Result<RawFd> {
self.dir.open_fd(self.d_name(), flags, 0)
}
pub fn open_as_dir(&self) -> io::Result<Directory> {
if !self.is_dir() {
return Err(io::Error::from(io::ErrorKind::NotADirectory));
}
unsafe { Directory::try_from(OwnedFd::from_raw_fd(self.open_fd(O_RDONLY)?)) }
}
pub fn open_as_file(&self, flags: i32) -> io::Result<File> {
if self.is_dir() {
return Err(io::Error::from(io::ErrorKind::IsADirectory));
}
unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) }
}
pub fn get_attr(&self) -> io::Result<FileAttr> {
let mut path = Utf8CStrBufArr::default();
self.path(&mut path)?;
FsPath::from(&path).get_attr()
}
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
let mut path = Utf8CStrBufArr::default();
self.path(&mut path)?;
FsPath::from(&path).set_attr(attr)
}
}
impl Deref for DirEntry<'_> {
type Target = dirent;
fn deref(&self) -> &dirent {
self.entry
}
}
pub struct Directory {
dirp: *mut libc::DIR,
}
pub enum WalkResult {
Continue,
Abort,
Skip,
}
impl Directory {
pub fn open(path: &Utf8CStr) -> io::Result<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?;
Ok(Directory { dirp })
}
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.dirp) };
if e.is_null() {
return if *errno() != 0 {
Err(io::Error::last_os_error())
} else {
Ok(None)
};
}
// Skip both "." and ".."
unsafe {
let entry = &*e;
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
if d_name == cstr!(".") || d_name == cstr!("..") {
self.read()
} else {
let e = DirEntry {
dir: self,
entry,
d_name_len: d_name.to_bytes_with_nul().len(),
};
Ok(Some(e))
}
}
}
pub fn rewind(&mut self) {
unsafe { libc::rewinddir(self.dirp) }
}
unsafe fn open_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result<RawFd> {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err()
}
pub fn contains_path(&self, path: &CStr) -> bool {
// WARNING: Using faccessat is incorrect, because the raw linux kernel syscall
// does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.
// Use fstatat to check the existence of a file instead.
unsafe {
let mut st: libc::stat = mem::zeroed();
libc::fstatat(
self.as_raw_fd(),
path.as_ptr(),
&mut st,
libc::AT_SYMLINK_NOFOLLOW,
) == 0
}
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(&mut self) -> io::Result<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
})?;
Ok(())
}
pub fn copy_into(&mut self, dir: &Directory) -> io::Result<()> {
while let Some(ref e) = self.read()? {
let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
if e.is_dir() {
unsafe {
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?;
src.copy_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?;
let mut dest = unsafe {
File::from_raw_fd(dir.open_fd(
e.d_name(),
O_WRONLY | O_CREAT | O_TRUNC,
0o777,
)?)
};
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() {
let mut path = Utf8CStrBufArr::default();
e.read_link(&mut path)?;
unsafe {
libc::symlinkat(path.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr())
.as_os_err()?;
}
new_entry.set_attr(&attr)?;
}
}
Ok(())
}
pub fn move_into(&mut self, dir: &Directory) -> io::Result<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() && dir.contains_path(e.d_name()) {
// Destination folder exists, needs recursive move
let mut src = e.open_as_dir()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let dest = new_entry.open_as_dir()?;
src.move_into(&dest)?;
return e.unlink();
}
unsafe {
libc::renameat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
)
.as_os_err()?;
}
}
Ok(())
}
pub fn link_into(&mut self, dir: &Directory) -> io::Result<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() {
unsafe {
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?;
src.link_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else {
unsafe {
libc::linkat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
0,
)
.as_os_err()?;
}
}
}
Ok(())
}
}
impl Directory {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.post_order_walk_impl(f)? {
return Ok(Abort);
}
}
match f(e)? {
Abort => return Ok(Abort),
Skip => return Ok(Continue),
Continue => {}
}
}
}
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
None => return Ok(Continue),
Some(ref e) => match f(e)? {
Abort => return Ok(Abort),
Skip => continue,
Continue => {
if e.is_dir() {
let mut dir = e.open_as_dir()?;
if let Abort = dir.pre_order_walk_impl(f)? {
return Ok(Abort);
}
}
}
},
}
}
}
}
impl TryFrom<OwnedFd> for Directory {
type Error = io::Error;
fn try_from(fd: OwnedFd) -> io::Result<Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }.check_os_err()?;
Ok(Directory { dirp })
}
}
impl AsRawFd for Directory {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.dirp) }
}
}
impl AsFd for Directory {
fn as_fd(&self) -> BorrowedFd {
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
impl Drop for Directory {
fn drop(&mut self) {
unsafe {
libc::closedir(self.dirp);
}
}
}
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
impl FsPath {
pub fn follow_link(&self) -> &FsPathFollow {
unsafe { mem::transmute(self) }
}
pub fn open(&self, flags: i32) -> io::Result<File> {
Ok(File::from(open_fd!(self, flags)?))
}
@@ -588,7 +203,10 @@ impl FsPath {
}
pub fn exists(&self) -> bool {
unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
unsafe {
let mut st: stat = mem::zeroed();
libc::lstat(self.as_ptr(), &mut st) == 0
}
}
pub fn rename_to<T: AsRef<Utf8CStr>>(&self, name: T) -> io::Result<()> {
@@ -608,13 +226,14 @@ impl FsPath {
self.remove()
}
#[allow(clippy::unnecessary_cast)]
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
buf.clear();
unsafe {
let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr().cast(), buf.capacity() - 1)
.check_os_err()? as usize;
*buf.mut_buf().get_unchecked_mut(r) = b'\0';
buf.set_len(r);
let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1)
.check_os_err()? as isize;
*(buf.as_mut_ptr().offset(r) as *mut u8) = b'\0';
buf.set_len(r as usize);
}
Ok(())
}
@@ -636,7 +255,7 @@ impl FsPath {
if self.is_empty() {
return Ok(());
}
let mut arr = Utf8CStrBufArr::default();
let mut arr = cstr_buf::default();
arr.push_str(self);
let mut off = 1;
unsafe {
@@ -688,16 +307,7 @@ impl FsPath {
libc::lstat(self.as_ptr(), &mut attr.st).as_os_err()?;
#[cfg(feature = "selinux")]
{
let sz = libc::lgetxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr().cast(),
attr.con.as_mut_ptr().cast(),
attr.con.capacity(),
)
.check_os_err()?;
attr.con.set_len((sz - 1) as usize);
}
self.get_secontext(&mut attr.con)?;
}
Ok(attr)
}
@@ -711,19 +321,45 @@ impl FsPath {
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
libc::lsetxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr().cast(),
attr.con.as_ptr().cast(),
attr.con.len() + 1,
0,
)
.as_os_err()?;
self.set_secontext(&attr.con)?;
}
}
Ok(())
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> {
unsafe {
let sz = libc::lgetxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
con.clear();
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
}
} else {
con.set_len((sz - 1) as usize);
}
}
Ok(())
}
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> {
unsafe {
libc::lsetxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_ptr().cast(),
con.len() + 1,
0,
)
.as_os_err()
}
}
pub fn copy_to(&self, path: &FsPath) -> io::Result<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
@@ -739,7 +375,7 @@ impl FsPath {
let mut dest = path.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o777)?;
std::io::copy(&mut src, &mut dest)?;
} else if attr.is_symlink() {
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr_buf::default();
self.read_link(&mut buf)?;
unsafe {
libc::symlink(buf.as_ptr(), path.as_ptr()).as_os_err()?;
@@ -781,7 +417,7 @@ impl FsPath {
unsafe { libc::symlink(self.as_ptr(), path.as_ptr()).as_os_err() }
}
pub fn parent(&self, buf: &mut dyn Utf8CStrWrite) -> bool {
pub fn parent(&self, buf: &mut dyn Utf8CStrBuf) -> bool {
buf.clear();
if let Some(parent) = Path::new(self.as_str()).parent() {
let bytes = parent.as_os_str().as_bytes();
@@ -795,6 +431,69 @@ impl FsPath {
}
}
impl FsPathFollow {
pub fn exists(&self) -> bool {
unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
}
pub fn get_attr(&self) -> io::Result<FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::stat(self.as_ptr(), &mut attr.st).as_os_err()?;
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
}
Ok(attr)
}
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
unsafe {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?;
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?;
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
}
Ok(())
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> {
unsafe {
let sz = libc::getxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
con.clear();
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
}
} else {
con.set_len((sz - 1) as usize);
}
}
Ok(())
}
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> {
unsafe {
libc::setxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_ptr().cast(),
con.len() + 1,
0,
)
.as_os_err()
}
}
}
pub fn fd_get_attr(fd: RawFd) -> io::Result<FileAttr> {
let mut attr = FileAttr::new();
unsafe {
@@ -804,12 +503,17 @@ pub fn fd_get_attr(fd: RawFd) -> io::Result<FileAttr> {
{
let sz = libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr().cast(),
XATTR_NAME_SELINUX.as_ptr(),
attr.con.as_mut_ptr().cast(),
attr.con.capacity(),
)
.check_os_err()?;
attr.con.set_len((sz - 1) as usize);
);
if sz < 1 {
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
}
} else {
attr.con.set_len((sz - 1) as usize);
}
}
}
Ok(attr)
@@ -824,7 +528,7 @@ pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> io::Result<()> {
if !attr.con.is_empty() {
libc::fsetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr().cast(),
XATTR_NAME_SELINUX.as_ptr(),
attr.con.as_ptr().cast(),
attr.con.len() + 1,
0,
@@ -889,7 +593,7 @@ impl Drop for MappedFile {
}
}
extern "C" {
unsafe extern "C" {
// Don't use the declaration from the libc crate as request should be u32 not i32
fn ioctl(fd: RawFd, request: u32, ...) -> i32;
}
@@ -1030,32 +734,3 @@ pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
}
res
}
#[derive(Default, Clone)]
pub enum SharedFd {
#[default]
None,
Shared(Arc<OwnedFd>),
}
impl From<OwnedFd> for SharedFd {
fn from(fd: OwnedFd) -> Self {
SharedFd::Shared(Arc::new(fd))
}
}
impl SharedFd {
pub const fn new() -> Self {
SharedFd::None
}
// This is unsafe because we cannot create multiple mutable references to the same fd.
// This can only be safely used if and only if the underlying fd points to a pipe,
// and the read/write operations performed on the file involves bytes less than PIPE_BUF.
pub unsafe fn as_file(&self) -> Option<ManuallyDrop<File>> {
match self {
SharedFd::None => None,
SharedFd::Shared(arc) => Some(ManuallyDrop::new(File::from_raw_fd(arc.as_raw_fd()))),
}
}
}

View File

@@ -7,4 +7,5 @@
#include "../base-rs.hpp"
using rust::xpipe2;
using rust::fd_path;
using rust::fd_path;
using kv_pairs = std::vector<std::pair<std::string, std::string>>;

View File

@@ -8,6 +8,8 @@ use num_traits::FromPrimitive;
pub use cstr::*;
use cxx_extern::*;
pub use dir::*;
pub use ffi::fork_dont_care;
pub use files::*;
pub use logging::*;
pub use misc::*;
@@ -15,6 +17,7 @@ pub use result::*;
mod cstr;
mod cxx_extern;
mod dir;
mod files;
mod logging;
mod misc;
@@ -42,6 +45,7 @@ pub mod ffi {
type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr;
fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec<usize>;
fn fork_dont_care() -> i32;
}
extern "Rust" {

View File

@@ -7,7 +7,7 @@ use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::ffi::LogLevelCxx;
use crate::{Utf8CStr, Utf8CStrBufArr};
use crate::{cstr_buf, Utf8CStr};
// Ugly hack to avoid using enum
#[allow(non_snake_case, non_upper_case_globals)]
@@ -84,7 +84,7 @@ fn log_with_writer<F: FnOnce(LogWriter)>(level: LogLevel, f: F) {
}
f(logger.write);
if matches!(level, LogLevel::ErrorCxx) && (logger.flags & LogFlag::ExitOnError) != 0 {
exit(1);
exit(-1);
}
}
@@ -96,7 +96,7 @@ pub fn log_from_cxx(level: LogLevelCxx, msg: &Utf8CStr) {
pub fn log_with_formatter<F: FnOnce(Formatter) -> fmt::Result>(level: LogLevel, f: F) {
log_with_writer(level, |write| {
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr_buf::default();
f(&mut buf).ok();
write(level, &buf);
});

View File

@@ -1,12 +1,13 @@
use std::fmt::Arguments;
use std::io::Write;
use std::process::exit;
use std::{fmt, io, slice, str};
use crate::{ffi, StrErr, Utf8CStr};
use argh::EarlyExit;
use libc::c_char;
use crate::{ffi, StrErr, Utf8CStr};
use std::fmt::Arguments;
use std::io::Write;
use std::mem::ManuallyDrop;
use std::process::exit;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::Arc;
use std::{fmt, io, slice, str};
pub fn errno() -> &'static mut i32 {
unsafe { &mut *libc::__errno() }
@@ -15,20 +16,24 @@ pub fn errno() -> &'static mut i32 {
// When len is 0, don't care whether buf is null or not
#[inline]
pub unsafe fn slice_from_ptr<'a, T>(buf: *const T, len: usize) -> &'a [T] {
if len == 0 {
&[]
} else {
slice::from_raw_parts(buf, len)
unsafe {
if len == 0 {
&[]
} else {
slice::from_raw_parts(buf, len)
}
}
}
// When len is 0, don't care whether buf is null or not
#[inline]
pub unsafe fn slice_from_ptr_mut<'a, T>(buf: *mut T, len: usize) -> &'a mut [T] {
if len == 0 {
&mut []
} else {
slice::from_raw_parts_mut(buf, len)
unsafe {
if len == 0 {
&mut []
} else {
slice::from_raw_parts_mut(buf, len)
}
}
}
@@ -161,3 +166,52 @@ impl<T: Write> fmt::Write for FmtAdaptor<'_, T> {
self.0.write_fmt(args).map_err(|_| fmt::Error)
}
}
pub struct AtomicArc<T> {
ptr: AtomicPtr<T>,
}
impl<T> AtomicArc<T> {
pub fn new(arc: Arc<T>) -> AtomicArc<T> {
let raw = Arc::into_raw(arc);
Self {
ptr: AtomicPtr::new(raw as *mut _),
}
}
pub fn load(&self) -> Arc<T> {
let raw = self.ptr.load(Ordering::Acquire);
// SAFETY: the raw pointer is always created from Arc::into_raw
let arc = ManuallyDrop::new(unsafe { Arc::from_raw(raw) });
ManuallyDrop::into_inner(arc.clone())
}
fn swap_ptr(&self, raw: *const T) -> Arc<T> {
let prev = self.ptr.swap(raw as *mut _, Ordering::AcqRel);
// SAFETY: the raw pointer is always created from Arc::into_raw
unsafe { Arc::from_raw(prev) }
}
pub fn swap(&self, arc: Arc<T>) -> Arc<T> {
let raw = Arc::into_raw(arc);
self.swap_ptr(raw)
}
pub fn store(&self, arc: Arc<T>) {
// Drop the previous value
let _ = self.swap(arc);
}
}
impl<T> Drop for AtomicArc<T> {
fn drop(&mut self) {
// Drop the internal value
let _ = self.swap_ptr(std::ptr::null());
}
}
impl<T: Default> Default for AtomicArc<T> {
fn default() -> Self {
Self::new(Default::default())
}
}

View File

@@ -55,6 +55,7 @@ impl<T> SilentResultExt<T> for Option<T> {
pub trait ResultExt<T> {
fn log(self) -> LoggedResult<T>;
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
fn log_ok(self);
}
// Internal C++ bridging logging routines
@@ -105,6 +106,17 @@ impl<T, R: Loggable<T>> ResultExt<T> for R {
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::Error, Some(Location::caller()), f)
}
#[cfg(not(debug_assertions))]
fn log_ok(self) {
self.log().ok();
}
#[track_caller]
#[cfg(debug_assertions)]
fn log_ok(self) {
self.do_log(LogLevel::Error, Some(Location::caller())).ok();
}
}
impl<T> Loggable<T> for LoggedResult<T> {

View File

@@ -15,7 +15,6 @@ int xopenat(int dirfd, const char *pathname, int flags, mode_t mode = 0);
ssize_t xwrite(int fd, const void *buf, size_t count);
ssize_t xread(int fd, void *buf, size_t count);
ssize_t xxread(int fd, void *buf, size_t count);
off64_t xlseek64(int fd, off64_t offset, int whence);
int xsetns(int fd, int nstype);
int xunshare(int flags);
DIR *xopendir(const char *name);
@@ -26,33 +25,22 @@ int xsocket(int domain, int type, int protocol);
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int xlisten(int sockfd, int backlog);
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
ssize_t xsendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags);
int xaccess(const char *path, int mode);
int xfaccessat(int dirfd, const char *pathname, int mode, int flags);
int xstat(const char *pathname, struct stat *buf);
int xlstat(const char *pathname, struct stat *buf);
int xfstat(int fd, struct stat *buf);
int xfstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
int xdup(int fd);
int xdup2(int oldfd, int newfd);
int xdup3(int oldfd, int newfd, int flags);
ssize_t xreadlink(const char * __restrict__ pathname, char * __restrict__ buf, size_t bufsiz);
ssize_t xreadlinkat(
int dirfd, const char * __restrict__ pathname, char * __restrict__ buf, size_t bufsiz);
int xsymlink(const char *target, const char *linkpath);
int xsymlinkat(const char *target, int newdirfd, const char *linkpath);
int xlinkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags);
int xmount(const char *source, const char *target,
const char *filesystemtype, unsigned long mountflags,
const void *data);
int xumount(const char *target);
int xumount2(const char *target, int flags);
int xrename(const char *oldpath, const char *newpath);
int xmkdir(const char *pathname, mode_t mode);
int xmkdirs(const char *pathname, mode_t mode);
int xmkdirat(int dirfd, const char *pathname, mode_t mode);
void *xmmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
ssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count);
pid_t xfork();
int xpoll(pollfd *fds, nfds_t nfds, int timeout);

View File

@@ -2,15 +2,14 @@
use std::ffi::CStr;
use std::os::unix::io::RawFd;
use std::ptr;
use libc::{
c_char, c_uint, c_ulong, c_void, dev_t, mode_t, nfds_t, off_t, pollfd, sockaddr, socklen_t,
ssize_t, SYS_dup3,
ssize_t,
};
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{errno, raw_cstr, CxxResultExt, FsPath, Utf8CStr, Utf8CStrBufRef};
use crate::{CxxResultExt, FsPath, Utf8CStr, Utf8CStrBufRef, errno, raw_cstr};
fn ptr_to_str<'a, T>(ptr: *const T) -> &'a str {
if ptr.is_null() {
@@ -50,93 +49,107 @@ mod c_export {
use crate::{slice_from_ptr, slice_from_ptr_mut};
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
super::xwrite(fd, slice_from_ptr(buf, bufsz))
unsafe { super::xwrite(fd, slice_from_ptr(buf, bufsz)) }
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {
super::xxread(fd, slice_from_ptr_mut(buf, bufsz))
unsafe { super::xxread(fd, slice_from_ptr_mut(buf, bufsz)) }
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("realpath {} failed", p)))
.map_or(-1, |_| buf.len() as isize)
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("realpath {} failed", p)))
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
}
Err(_) => -1,
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xreadlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.read_link(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("readlink {} failed", p)))
.map_or(-1, |_| buf.len() as isize)
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.read_link(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("readlink {} failed", p)))
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
}
Err(_) => -1,
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xreadlinkat(
dirfd: RawFd,
path: *const c_char,
buf: *mut u8,
bufsz: usize,
) -> isize {
let r = readlinkat_for_cxx(dirfd, path, buf, bufsz);
if r < 0 {
perror!("readlinkat {}", ptr_to_str(path))
unsafe {
let r = readlinkat_for_cxx(dirfd, path, buf, bufsz);
if r < 0 {
perror!("readlinkat {}", ptr_to_str(path))
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {
let fp = libc::fopen(path, mode);
if fp.is_null() {
perror!("fopen {}", ptr_to_str(path));
unsafe {
let fp = libc::fopen(path, mode);
if fp.is_null() {
perror!("fopen {}", ptr_to_str(path));
}
fp
}
fp
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE {
let fp = libc::fdopen(fd, mode);
if fp.is_null() {
perror!("fdopen");
unsafe {
let fp = libc::fdopen(fd, mode);
if fp.is_null() {
perror!("fdopen");
}
fp
}
fp
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
let r = libc::open(path, flags, mode as c_uint);
if r < 0 {
perror!("open {}", ptr_to_str(path));
unsafe {
let r = libc::open(path, flags, mode as c_uint);
if r < 0 {
perror!("open {}", ptr_to_str(path));
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xopenat(dirfd: RawFd, path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
let r = libc::openat(dirfd, path, flags, mode as c_uint);
if r < 0 {
perror!("openat {}", ptr_to_str(path));
unsafe {
let r = libc::openat(dirfd, path, flags, mode as c_uint);
if r < 0 {
perror!("openat {}", ptr_to_str(path));
}
r
}
r
}
// Fully write data slice
@@ -168,7 +181,7 @@ fn xwrite(fd: RawFd, data: &[u8]) -> isize {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xread(fd: RawFd, buf: *mut c_void, bufsz: usize) -> isize {
unsafe {
let r = libc::read(fd, buf, bufsz);
@@ -208,17 +221,6 @@ fn xxread(fd: RawFd, data: &mut [u8]) -> isize {
}
}
#[no_mangle]
extern "C" fn xlseek64(fd: RawFd, offset: i64, whence: i32) -> i64 {
unsafe {
let r = libc::lseek64(fd, offset, whence);
if r < 0 {
perror!("lseek64");
}
r
}
}
pub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {
unsafe {
let r = libc::pipe2(fds.as_mut_ptr(), flags);
@@ -229,7 +231,7 @@ pub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xsetns(fd: RawFd, nstype: i32) -> i32 {
unsafe {
let r = libc::setns(fd, nstype);
@@ -240,7 +242,7 @@ extern "C" fn xsetns(fd: RawFd, nstype: i32) -> i32 {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xunshare(flags: i32) -> i32 {
unsafe {
let r = libc::unshare(flags);
@@ -251,16 +253,18 @@ extern "C" fn xunshare(flags: i32) -> i32 {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xopendir(path: *const c_char) -> *mut libc::DIR {
let dp = libc::opendir(path);
if dp.is_null() {
perror!("opendir {}", ptr_to_str(path));
unsafe {
let dp = libc::opendir(path);
if dp.is_null() {
perror!("opendir {}", ptr_to_str(path));
}
dp
}
dp
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xfdopendir(fd: RawFd) -> *mut libc::DIR {
unsafe {
let dp = libc::fdopendir(fd);
@@ -271,27 +275,29 @@ extern "C" fn xfdopendir(fd: RawFd) -> *mut libc::DIR {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xreaddir(dirp: *mut libc::DIR) -> *mut libc::dirent {
*errno() = 0;
loop {
let e = libc::readdir(dirp);
if e.is_null() {
if *errno() != 0 {
perror!("readdir")
}
} else {
// Filter out . and ..
let s = (*e).d_name.as_ptr();
if libc::strcmp(s, raw_cstr!(".")) == 0 || libc::strcmp(s, raw_cstr!("..")) == 0 {
continue;
}
};
return e;
unsafe {
*errno() = 0;
loop {
let e = libc::readdir(dirp);
if e.is_null() {
if *errno() != 0 {
perror!("readdir")
}
} else {
// Filter out . and ..
let s = (*e).d_name.as_ptr();
if libc::strcmp(s, raw_cstr!(".")) == 0 || libc::strcmp(s, raw_cstr!("..")) == 0 {
continue;
}
};
return e;
}
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xsetsid() -> i32 {
unsafe {
let r = libc::setsid();
@@ -302,7 +308,7 @@ extern "C" fn xsetsid() -> i32 {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xsocket(domain: i32, ty: i32, protocol: i32) -> RawFd {
unsafe {
let fd = libc::socket(domain, ty, protocol);
@@ -313,16 +319,18 @@ extern "C" fn xsocket(domain: i32, ty: i32, protocol: i32) -> RawFd {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xbind(socket: i32, address: *const sockaddr, len: socklen_t) -> i32 {
let r = libc::bind(socket, address, len);
if r < 0 {
perror!("bind");
unsafe {
let r = libc::bind(socket, address, len);
if r < 0 {
perror!("bind");
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xlisten(socket: i32, backlog: i32) -> i32 {
unsafe {
let r = libc::listen(socket, backlog);
@@ -333,103 +341,56 @@ extern "C" fn xlisten(socket: i32, backlog: i32) -> i32 {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xaccept4(
sockfd: RawFd,
addr: *mut sockaddr,
len: *mut socklen_t,
flg: i32,
) -> RawFd {
let fd = libc::accept4(sockfd, addr, len, flg);
if fd < 0 {
perror!("accept4");
unsafe {
let fd = libc::accept4(sockfd, addr, len, flg);
if fd < 0 {
perror!("accept4");
}
fd
}
fd
}
#[no_mangle]
unsafe extern "C" fn xsendmsg(fd: RawFd, msg: *const libc::msghdr, flags: i32) -> ssize_t {
let r = libc::sendmsg(fd, msg, flags);
if r < 0 {
perror!("sendmsg");
}
r
}
#[no_mangle]
unsafe extern "C" fn xrecvmsg(fd: RawFd, msg: *mut libc::msghdr, flags: i32) -> ssize_t {
let r = libc::recvmsg(fd, msg, flags);
if r < 0 {
perror!("recvmsg");
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xaccess(path: *const c_char, mode: i32) -> i32 {
let r = libc::access(path, mode);
if r < 0 {
perror!("access {}", ptr_to_str(path));
unsafe {
let r = libc::access(path, mode);
if r < 0 {
perror!("access {}", ptr_to_str(path));
}
r
}
r
}
#[no_mangle]
unsafe extern "C" fn xfaccessat(dirfd: RawFd, path: *const c_char, mode: i32, flags: i32) -> i32 {
#[allow(unused_mut)]
let mut r = libc::faccessat(dirfd, path, mode, flags);
if r < 0 {
perror!("faccessat {}", ptr_to_str(path));
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if r > 0 && *errno() == 0 {
r = 0
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
let r = libc::stat(path, buf);
if r < 0 {
perror!("stat {}", ptr_to_str(path));
unsafe {
let r = libc::stat(path, buf);
if r < 0 {
perror!("stat {}", ptr_to_str(path));
}
r
}
r
}
#[no_mangle]
unsafe extern "C" fn xlstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
let r = libc::lstat(path, buf);
if r < 0 {
perror!("lstat {}", ptr_to_str(path));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xfstat(fd: RawFd, buf: *mut libc::stat) -> i32 {
let r = libc::fstat(fd, buf);
if r < 0 {
perror!("fstat");
unsafe {
let r = libc::fstat(fd, buf);
if r < 0 {
perror!("fstat");
}
r
}
r
}
#[no_mangle]
unsafe extern "C" fn xfstatat(
dirfd: RawFd,
path: *const c_char,
buf: *mut libc::stat,
flags: i32,
) -> i32 {
let r = libc::fstatat(dirfd, path, buf, flags);
if r < 0 {
perror!("fstatat {}", ptr_to_str(path));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xdup(oldfd: RawFd) -> RawFd {
unsafe {
let fd = libc::dup(oldfd);
@@ -440,7 +401,7 @@ extern "C" fn xdup(oldfd: RawFd) -> RawFd {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {
unsafe {
let fd = libc::dup2(oldfd, newfd);
@@ -451,59 +412,18 @@ extern "C" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {
}
}
#[no_mangle]
extern "C" fn xdup3(oldfd: RawFd, newfd: RawFd, flags: i32) -> RawFd {
unsafe {
let fd = libc::syscall(SYS_dup3, oldfd, newfd, flags) as RawFd;
if fd < 0 {
perror!("dup3");
}
fd
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xsymlink(target: *const c_char, linkpath: *const c_char) -> i32 {
let r = libc::symlink(target, linkpath);
if r < 0 {
perror!("symlink {} -> {}", ptr_to_str(target), ptr_to_str(linkpath));
unsafe {
let r = libc::symlink(target, linkpath);
if r < 0 {
perror!("symlink {} -> {}", ptr_to_str(target), ptr_to_str(linkpath));
}
r
}
r
}
#[no_mangle]
unsafe extern "C" fn xsymlinkat(
target: *const c_char,
dirfd: RawFd,
linkpath: *const c_char,
) -> i32 {
let r = libc::symlinkat(target, dirfd, linkpath);
if r < 0 {
perror!(
"symlinkat {} -> {}",
ptr_to_str(target),
ptr_to_str(linkpath)
);
}
r
}
#[no_mangle]
unsafe extern "C" fn xlinkat(
olddirfd: RawFd,
target: *const c_char,
newdirfd: RawFd,
linkpath: *const c_char,
flags: i32,
) -> i32 {
let r = libc::linkat(olddirfd, target, newdirfd, linkpath, flags);
if r < 0 {
perror!("linkat {} -> {}", ptr_to_str(target), ptr_to_str(linkpath));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmount(
src: *const c_char,
target: *const c_char,
@@ -511,101 +431,78 @@ unsafe extern "C" fn xmount(
flags: c_ulong,
data: *const c_void,
) -> i32 {
let r = libc::mount(src, target, fstype, flags, data);
if r < 0 {
perror!("mount {} -> {}", ptr_to_str(src), ptr_to_str(target));
unsafe {
let r = libc::mount(src, target, fstype, flags, data);
if r < 0 {
perror!("mount {} -> {}", ptr_to_str(src), ptr_to_str(target));
}
r
}
r
}
#[no_mangle]
unsafe extern "C" fn xumount(target: *const c_char) -> i32 {
let r = libc::umount(target);
if r < 0 {
perror!("umount {}", ptr_to_str(target));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xumount2(target: *const c_char, flags: i32) -> i32 {
let r = libc::umount2(target, flags);
if r < 0 {
perror!("umount2 {}", ptr_to_str(target));
unsafe {
let r = libc::umount2(target, flags);
if r < 0 {
perror!("umount2 {}", ptr_to_str(target));
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xrename(oldname: *const c_char, newname: *const c_char) -> i32 {
let r = libc::rename(oldname, newname);
if r < 0 {
perror!("rename {} -> {}", ptr_to_str(oldname), ptr_to_str(newname));
unsafe {
let r = libc::rename(oldname, newname);
if r < 0 {
perror!("rename {} -> {}", ptr_to_str(oldname), ptr_to_str(newname));
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
let r = libc::mkdir(path, mode);
if r < 0 && *errno() != libc::EEXIST {
perror!("mkdir {}", ptr_to_str(path));
unsafe {
let r = libc::mkdir(path, mode);
if r < 0 && *errno() != libc::EEXIST {
perror!("mkdir {}", ptr_to_str(path));
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p)
.mkdirs(mode)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("mkdirs {} failed", p)))
.map_or(-1, |_| 0),
Err(_) => -1,
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p)
.mkdirs(mode)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("mkdirs {} failed", p)))
.map_or(-1, |_| 0),
Err(_) => -1,
}
}
}
#[no_mangle]
unsafe extern "C" fn xmkdirat(dirfd: RawFd, path: *const c_char, mode: mode_t) -> i32 {
let r = libc::mkdirat(dirfd, path, mode);
if r < 0 && *errno() != libc::EEXIST {
perror!("mkdirat {}", ptr_to_str(path));
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xsendfile(
out_fd: RawFd,
in_fd: RawFd,
offset: *mut off_t,
count: usize,
) -> isize {
let r = libc::sendfile(out_fd, in_fd, offset, count);
if r < 0 {
perror!("sendfile");
unsafe {
let r = libc::sendfile(out_fd, in_fd, offset, count);
if r < 0 {
perror!("sendfile");
}
r
}
r
}
#[no_mangle]
unsafe extern "C" fn xmmap(
addr: *mut c_void,
len: usize,
prot: i32,
flags: i32,
fd: RawFd,
offset: off_t,
) -> *mut c_void {
let r = libc::mmap(addr, len, prot, flags, fd, offset);
if r == libc::MAP_FAILED {
perror!("mmap");
return ptr::null_mut();
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
extern "C" fn xfork() -> i32 {
unsafe {
let r = libc::fork();
@@ -616,20 +513,24 @@ extern "C" fn xfork() -> i32 {
}
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xpoll(fds: *mut pollfd, nfds: nfds_t, timeout: i32) -> i32 {
let r = libc::poll(fds, nfds, timeout);
if r < 0 {
perror!("poll");
unsafe {
let r = libc::poll(fds, nfds, timeout);
if r < 0 {
perror!("poll");
}
r
}
r
}
#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 {
let r = libc::mknod(pathname, mode, dev);
if r < 0 {
perror!("mknod {}", ptr_to_str(pathname));
unsafe {
let r = libc::mknod(pathname, mode, dev);
if r < 0 {
perror!("mknod {}", ptr_to_str(pathname));
}
r
}
r
}

View File

@@ -1,7 +1,7 @@
[package]
name = "magiskboot"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["staticlib"]
@@ -25,8 +25,12 @@ p256 = { workspace = true }
p384 = { workspace = true }
p521 = { workspace = true }
rsa = { workspace = true, features = ["sha2"] }
block-buffer = { workspace = true }
sec1 = { workspace = true }
x509-cert = { workspace = true }
der = { workspace = true, features = ["derive", "pem"] }
fdt = { workspace = true }
bytemuck = { workspace = true, features = ["derive", "min_const_generics"] }
num-traits = { workspace = true }
libz-rs-sys = { workspace = true }
libbz2-rs-sys = { workspace = true }

View File

@@ -431,9 +431,20 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) {
}
if (k_fmt == ZIMAGE) {
z_hdr = reinterpret_cast<const zimage_hdr *>(kernel);
if (const void *gzip = memmem(kernel, hdr->kernel_size(), GZIP1_MAGIC "\x08\x00", 4)) {
const uint8_t* found_pos = 0;
for (const uint8_t* search_pos = kernel + 0x28; search_pos < kernel + hdr->kernel_size(); search_pos++) {
// ^^^^^^ +0x28 to search after zimage header and magic
if (check_fmt_lg(search_pos, hdr->kernel_size() - (search_pos - kernel)) != UNKNOWN) {
found_pos = search_pos;
search_pos = kernel + hdr->kernel_size();
}
}
if (found_pos != 0) {
fprintf(stderr, "ZIMAGE_KERNEL\n");
z_info.hdr_sz = (const uint8_t *) gzip - kernel;
z_info.hdr_sz = (const uint8_t *) found_pos - kernel;
// Find end of piggy
uint32_t zImage_size = z_hdr->end - z_hdr->start;
@@ -457,7 +468,7 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) {
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
}
} else {
fprintf(stderr, "! Could not find zImage gzip piggy, keeping raw kernel\n");
fprintf(stderr, "! Could not find zImage piggy, keeping raw kernel\n");
}
}
fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name[k_fmt]);

View File

@@ -1,9 +1,9 @@
use pb_rs::{types::FileDescriptor, ConfigBuilder};
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
println!("cargo:rerun-if-changed=proto/update_metadata.proto");

287
native/src/boot/bzlib.h Normal file
View File

@@ -0,0 +1,287 @@
/*-------------------------------------------------------------*/
/*--- Public header file for the library. ---*/
/*--- bzlib.h ---*/
/*-------------------------------------------------------------*/
/* ------------------------------------------------------------------
This file is part of bzip2/libbzip2, a program and library for
lossless, block-sorting data compression.
bzip2/libbzip2 version 1.1.0 of 6 September 2010
Copyright (C) 1996-2010 Julian Seward <jseward@acm.org>
Please read the WARNING, DISCLAIMER and PATENTS sections in the
README file.
This program is released under the terms of the license contained
in the file LICENSE.
------------------------------------------------------------------ */
#ifndef _BZLIB_H
#define _BZLIB_H
#ifdef __cplusplus
extern "C" {
#endif
#define BZ_RUN 0
#define BZ_FLUSH 1
#define BZ_FINISH 2
#define BZ_OK 0
#define BZ_RUN_OK 1
#define BZ_FLUSH_OK 2
#define BZ_FINISH_OK 3
#define BZ_STREAM_END 4
#define BZ_SEQUENCE_ERROR (-1)
#define BZ_PARAM_ERROR (-2)
#define BZ_MEM_ERROR (-3)
#define BZ_DATA_ERROR (-4)
#define BZ_DATA_ERROR_MAGIC (-5)
#define BZ_IO_ERROR (-6)
#define BZ_UNEXPECTED_EOF (-7)
#define BZ_OUTBUFF_FULL (-8)
#define BZ_CONFIG_ERROR (-9)
typedef
struct {
char *next_in;
unsigned int avail_in;
unsigned int total_in_lo32;
unsigned int total_in_hi32;
char *next_out;
unsigned int avail_out;
unsigned int total_out_lo32;
unsigned int total_out_hi32;
void *state;
void *(*bzalloc)(void *,int,int);
void (*bzfree)(void *,void *);
void *opaque;
}
bz_stream;
#ifndef BZ_IMPORT
#define BZ_EXPORT
#endif
#ifndef BZ_NO_STDIO
/* Need a definitition for FILE */
#include <stdio.h>
#endif
#ifdef _WIN32
# include <windows.h>
# ifdef small
/* windows.h define small to char */
# undef small
# endif
# ifndef WINAPI
# define WINAPI
# endif
# ifdef BZ_EXPORT
# define BZ_API(func) WINAPI func
# define BZ_EXTERN extern
# else
/* import windows dll dynamically */
# define BZ_API(func) (WINAPI * func)
# define BZ_EXTERN
# endif
#else
# define BZ_API(func) func
#endif
#ifndef BZ_EXTERN
#define BZ_EXTERN extern
#endif
/*-- Core (low-level) library functions --*/
BZ_EXTERN int BZ_API(BZ2_bzCompressInit) (
bz_stream* strm,
int blockSize100k,
int verbosity,
int workFactor
);
BZ_EXTERN int BZ_API(BZ2_bzCompress) (
bz_stream* strm,
int action
);
BZ_EXTERN int BZ_API(BZ2_bzCompressEnd) (
bz_stream* strm
);
BZ_EXTERN int BZ_API(BZ2_bzDecompressInit) (
bz_stream *strm,
int verbosity,
int small
);
BZ_EXTERN int BZ_API(BZ2_bzDecompress) (
bz_stream* strm
);
BZ_EXTERN int BZ_API(BZ2_bzDecompressEnd) (
bz_stream *strm
);
/*-- High(er) level library functions --*/
#ifndef BZ_NO_STDIO
#define BZ_MAX_UNUSED 5000
typedef void BZFILE;
BZ_EXTERN BZFILE* BZ_API(BZ2_bzReadOpen) (
int* bzerror,
FILE* f,
int verbosity,
int small,
void* unused,
int nUnused
);
BZ_EXTERN void BZ_API(BZ2_bzReadClose) (
int* bzerror,
BZFILE* b
);
BZ_EXTERN void BZ_API(BZ2_bzReadGetUnused) (
int* bzerror,
BZFILE* b,
void** unused,
int* nUnused
);
BZ_EXTERN int BZ_API(BZ2_bzRead) (
int* bzerror,
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN BZFILE* BZ_API(BZ2_bzWriteOpen) (
int* bzerror,
FILE* f,
int blockSize100k,
int verbosity,
int workFactor
);
BZ_EXTERN void BZ_API(BZ2_bzWrite) (
int* bzerror,
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN void BZ_API(BZ2_bzWriteClose) (
int* bzerror,
BZFILE* b,
int abandon,
unsigned int* nbytes_in,
unsigned int* nbytes_out
);
BZ_EXTERN void BZ_API(BZ2_bzWriteClose64) (
int* bzerror,
BZFILE* b,
int abandon,
unsigned int* nbytes_in_lo32,
unsigned int* nbytes_in_hi32,
unsigned int* nbytes_out_lo32,
unsigned int* nbytes_out_hi32
);
#endif
/*-- Utility functions --*/
BZ_EXTERN int BZ_API(BZ2_bzBuffToBuffCompress) (
char* dest,
unsigned int* destLen,
char* source,
unsigned int sourceLen,
int blockSize100k,
int verbosity,
int workFactor
);
BZ_EXTERN int BZ_API(BZ2_bzBuffToBuffDecompress) (
char* dest,
unsigned int* destLen,
char* source,
unsigned int sourceLen,
int small,
int verbosity
);
/*--
Code contributed by Yoshioka Tsuneo (tsuneo@rr.iij4u.or.jp)
to support better zlib compatibility.
This code is not _officially_ part of libbzip2 (yet);
I haven't tested it, documented it, or considered the
threading-safeness of it.
If this code breaks, please contact both Yoshioka and me.
--*/
BZ_EXTERN const char * BZ_API(BZ2_bzlibVersion) (
void
);
#ifndef BZ_NO_STDIO
BZ_EXTERN BZFILE * BZ_API(BZ2_bzopen) (
const char *path,
const char *mode
);
BZ_EXTERN BZFILE * BZ_API(BZ2_bzdopen) (
int fd,
const char *mode
);
BZ_EXTERN int BZ_API(BZ2_bzread) (
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN int BZ_API(BZ2_bzwrite) (
BZFILE* b,
void* buf,
int len
);
BZ_EXTERN int BZ_API(BZ2_bzflush) (
BZFILE* b
);
BZ_EXTERN void BZ_API(BZ2_bzclose) (
BZFILE* b
);
BZ_EXTERN const char * BZ_API(BZ2_bzerror) (
BZFILE *b,
int *errnum
);
#endif
#ifdef __cplusplus
}
#endif
#endif
/*-------------------------------------------------------------*/
/*--- end bzlib.h ---*/
/*-------------------------------------------------------------*/

View File

@@ -2,7 +2,7 @@
#include <functional>
#include <zlib.h>
#include <bzlib.h>
#include "bzlib.h"
#include <lzma.h>
#include <lz4.h>
#include <lz4frame.h>
@@ -153,7 +153,7 @@ class zopfli_encoder : public chunk_out_stream {
public:
explicit zopfli_encoder(out_strm_ptr &&base) :
chunk_out_stream(std::move(base), ZOPFLI_MASTER_BLOCK_SIZE),
zo{}, out(nullptr), outsize(0), crc(crc32_z(0L, Z_NULL, 0)), in_total(0), bp(0) {
zo{}, out(nullptr), outsize(0), crc(crc32(0L, Z_NULL, 0)), in_total(0), bp(0) {
ZopfliInitOptions(&zo);
// This config is already better than gzip -9
@@ -198,7 +198,7 @@ protected:
auto in = static_cast<const unsigned char *>(buf);
in_total += len;
crc = crc32_z(crc, in, len);
crc = crc32(crc, in, len);
ZopfliDeflatePart(&zo, 2, final, in, 0, len, &bp, &out, &outsize);

View File

@@ -20,8 +20,8 @@ use base::libc::{
S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
};
use base::{
log_err, map_args, BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile, ResultExt,
Utf8CStr, Utf8CStrBufArr, Utf8CStrWrite, WriteExt,
cstr_buf, log_err, map_args, BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile,
ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
};
use crate::check_env;
@@ -344,7 +344,7 @@ impl Cpio {
let out = Utf8CStr::from_string(out);
let out = FsPath::from(out);
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr_buf::default();
// Make sure its parent directories exist
if out.parent(&mut buf) {
@@ -746,8 +746,7 @@ impl Display for CpioEntry {
Size::from_bytes(self.data.len())
.format()
.with_style(Style::Abbreviated)
.with_base(Base::Base10)
.to_string(),
.with_base(Base::Base10),
self.rdevmajor,
self.rdevminor,
)
@@ -755,9 +754,9 @@ impl Display for CpioEntry {
}
pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
fn inner(argc: i32, argv: *const *const c_char) -> LoggedResult<()> {
let res: LoggedResult<()> = try {
if argc < 1 {
return Err(log_err!("No arguments"));
Err(log_err!("No arguments"))?;
}
let cmds = map_args(argc, argv)?;
@@ -807,7 +806,7 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?,
CpioAction::Extract(Extract { paths }) => {
if !paths.is_empty() && paths.len() != 2 {
return Err(log_err!("invalid arguments"));
Err(log_err!("invalid arguments"))?;
}
let mut it = paths.iter_mut();
cpio.extract(it.next(), it.next())?;
@@ -819,10 +818,8 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
};
}
cpio.dump(file)?;
Ok(())
}
inner(argc, argv)
.log_with_msg(|w| w.write_str("Failed to process cpio"))
};
res.log_with_msg(|w| w.write_str("Failed to process cpio"))
.is_ok()
}

View File

@@ -3,7 +3,7 @@ use std::{cell::UnsafeCell, process::exit};
use argh::FromArgs;
use fdt::{
node::{FdtNode, NodeProperty},
Fdt,
Fdt, FdtError,
};
use base::{
@@ -171,15 +171,17 @@ fn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(
if slice.len() < 40 {
break;
}
let fdt = Fdt::new(slice)?;
let fdt = match Fdt::new(slice) {
Err(FdtError::BufferTooSmall) => {
eprintln!("dtb.{:04} is truncated", dtb_num);
break;
}
Ok(fdt) => fdt,
e => e?,
};
let size = fdt.total_size();
if size > slice.len() {
eprintln!("dtb.{:04} is truncated", dtb_num);
break;
}
f(dtb_num, fdt)?;
dtb_num += 1;
@@ -273,9 +275,9 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
}
pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
fn inner(argc: i32, argv: *const *const c_char) -> LoggedResult<()> {
let res: LoggedResult<()> = try {
if argc < 1 {
return Err(log_err!("No arguments"));
Err(log_err!("No arguments"))?;
}
let cmds = map_args(argc, argv)?;
@@ -299,9 +301,7 @@ pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
}
}
}
Ok(())
}
inner(argc, argv)
.log_with_msg(|w| w.write_str("Failed to process dtb"))
};
res.log_with_msg(|w| w.write_str("Failed to process dtb"))
.is_ok()
}

View File

@@ -1,7 +1,10 @@
#![feature(format_args_nl)]
#![feature(btree_extract_if)]
#![feature(iter_intersperse)]
#![feature(try_blocks)]
pub use libz_rs_sys::*;
pub use libbz2_rs_sys::*;
pub use base;
use cpio::cpio_commands;
use dtb::dtb_commands;
@@ -21,6 +24,14 @@ mod sign;
#[cxx::bridge]
pub mod ffi {
unsafe extern "C++" {
include!("../base/include/base.hpp");
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>;
}
unsafe extern "C++" {
include!("compress.hpp");
fn decompress(buf: &[u8], fd: i32) -> bool;
@@ -51,10 +62,10 @@ pub mod ffi {
#[namespace = "rust"]
#[allow(unused_unsafe)]
extern "Rust" {
unsafe fn extract_boot_from_payload(
partition: *const c_char,
in_path: *const c_char,
out_path: *const c_char,
fn extract_boot_from_payload(
partition: Utf8CStrRef,
in_path: Utf8CStrRef,
out_path: Utf8CStrRef,
) -> bool;
unsafe fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool;
unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool;
@@ -70,5 +81,5 @@ pub mod ffi {
#[inline(always)]
pub(crate) fn check_env(env: &str) -> bool {
env::var(env).map_or(false, |var| var == "true")
env::var(env).is_ok_and(|var| var == "true")
}

View File

@@ -217,8 +217,8 @@ int main(int argc, char *argv[]) {
} else if (argc > 2 && action == "extract") {
return rust::extract_boot_from_payload(
argv[2],
argc > 3 ? argv[3] : nullptr,
argc > 4 ? argv[4] : nullptr
argc > 3 ? argv[3] : "",
argc > 4 ? argv[4] : ""
) ? 0 : 1;
} else {
usage(argv[0]);

View File

@@ -63,15 +63,17 @@ fn remove_pattern(buf: &mut [u8], pattern_matcher: unsafe fn(&[u8]) -> Option<us
pub fn patch_verity(buf: &mut [u8]) -> usize {
unsafe fn match_verity_pattern(buf: &[u8]) -> Option<usize> {
match_patterns!(
buf,
b"verifyatboot",
b"verify",
b"avb_keys",
b"avb",
b"support_scfs",
b"fsverity"
)
unsafe {
match_patterns!(
buf,
b"verifyatboot",
b"verify",
b"avb_keys",
b"avb",
b"support_scfs",
b"fsverity"
)
}
}
remove_pattern(buf, match_verity_pattern)
@@ -79,7 +81,7 @@ pub fn patch_verity(buf: &mut [u8]) -> usize {
pub fn patch_encryption(buf: &mut [u8]) -> usize {
unsafe fn match_encryption_pattern(buf: &[u8]) -> Option<usize> {
match_patterns!(buf, b"forceencrypt", b"forcefdeorfbe", b"fileencryption")
unsafe { match_patterns!(buf, b"forceencrypt", b"forcefdeorfbe", b"fileencryption") }
}
remove_pattern(buf, match_encryption_pattern)
@@ -95,13 +97,13 @@ fn hex2byte(hex: &[u8]) -> Vec<u8> {
let low = bytes[1].to_ascii_uppercase() - b'0';
let h = if high > 9 { high - 7 } else { high };
let l = if low > 9 { low - 7 } else { low };
v.push(h << 4 | l);
v.push((h << 4) | l);
}
v
}
pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
fn inner(file: &[u8], from: &[u8], to: &[u8]) -> LoggedResult<bool> {
let res: LoggedResult<bool> = try {
let file = Utf8CStr::from_bytes(file)?;
let from = Utf8CStr::from_bytes(from)?;
let to = Utf8CStr::from_bytes(to)?;
@@ -114,8 +116,7 @@ pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
for off in &v {
eprintln!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
}
Ok(!v.is_empty())
}
inner(file, from, to).unwrap_or(false)
!v.is_empty()
};
res.unwrap_or(false)
}

View File

@@ -1,17 +1,19 @@
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom, Write};
use std::os::fd::{AsRawFd, FromRawFd};
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom, Write},
os::fd::{AsRawFd, FromRawFd},
};
use byteorder::{BigEndian, ReadBytesExt};
use quick_protobuf::{BytesReader, MessageRead};
use base::libc::c_char;
use base::{error, LoggedError, LoggedResult, ReadSeekExt, StrErr, Utf8CStr};
use base::{ResultExt, WriteExt};
use crate::ffi;
use crate::proto::update_metadata::mod_InstallOperation::Type;
use crate::proto::update_metadata::DeltaArchiveManifest;
use crate::{
ffi,
proto::update_metadata::{mod_InstallOperation::Type, DeltaArchiveManifest},
};
use base::{
error, ffi::Utf8CStrRef, LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt,
};
macro_rules! bad_payload {
($msg:literal) => {{
@@ -178,28 +180,23 @@ fn do_extract_boot_from_payload(
}
pub fn extract_boot_from_payload(
in_path: *const c_char,
partition: *const c_char,
out_path: *const c_char,
in_path: Utf8CStrRef,
partition: Utf8CStrRef,
out_path: Utf8CStrRef,
) -> bool {
fn inner(
in_path: *const c_char,
partition: *const c_char,
out_path: *const c_char,
) -> LoggedResult<()> {
let in_path = unsafe { Utf8CStr::from_ptr(in_path) }?;
let partition = match unsafe { Utf8CStr::from_ptr(partition) } {
Ok(s) => Some(s),
Err(StrErr::NullPointerError) => None,
Err(e) => Err(e)?,
let res: LoggedResult<()> = try {
let partition = if partition.is_empty() {
None
} else {
Some(partition)
};
let out_path = match unsafe { Utf8CStr::from_ptr(out_path) } {
Ok(s) => Some(s),
Err(StrErr::NullPointerError) => None,
Err(e) => Err(e)?,
let out_path = if out_path.is_empty() {
None
} else {
Some(out_path)
};
do_extract_boot_from_payload(in_path, partition, out_path)
.log_with_msg(|w| w.write_str("Failed to extract from payload"))
}
inner(in_path, partition, out_path).is_ok()
do_extract_boot_from_payload(in_path, partition, out_path)?
};
res.log_with_msg(|w| w.write_str("Failed to extract from payload"))
.is_ok()
}

View File

@@ -157,20 +157,32 @@ struct Signer {
impl Signer {
fn from_private_key(key: &[u8]) -> LoggedResult<Signer> {
let digest: Box<dyn DynDigest>;
let key = if let Ok(rsa) = RsaPrivateKey::from_pkcs8_der(key) {
digest = Box::<Sha256>::default();
SigningKey::SHA256withRSA(RsaSigningKey::<Sha256>::new(rsa))
} else if let Ok(ec) = P256SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha256>::default();
SigningKey::SHA256withECDSA(ec)
} else if let Ok(ec) = P384SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha384>::default();
SigningKey::SHA384withECDSA(ec)
} else if let Ok(ec) = P521SigningKey::from_pkcs8_der(key) {
digest = Box::<Sha512>::default();
SigningKey::SHA521withECDSA(ec)
} else {
return Err(log_err!("Unsupported private key"));
let key = match RsaPrivateKey::from_pkcs8_der(key) {
Ok(rsa) => {
digest = Box::<Sha256>::default();
SigningKey::SHA256withRSA(RsaSigningKey::<Sha256>::new(rsa))
}
_ => match P256SigningKey::from_pkcs8_der(key) {
Ok(ec) => {
digest = Box::<Sha256>::default();
SigningKey::SHA256withECDSA(ec)
}
_ => match P384SigningKey::from_pkcs8_der(key) {
Ok(ec) => {
digest = Box::<Sha384>::default();
SigningKey::SHA384withECDSA(ec)
}
_ => match P521SigningKey::from_pkcs8_der(key) {
Ok(ec) => {
digest = Box::<Sha512>::default();
SigningKey::SHA521withECDSA(ec)
}
_ => {
return Err(log_err!("Unsupported private key"));
}
},
},
},
};
Ok(Signer { digest, key })
}
@@ -254,7 +266,7 @@ impl BootSignature {
}
pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool {
fn inner(img: &BootImage, cert: *const c_char) -> LoggedResult<()> {
let res: LoggedResult<()> = try {
let tail = img.tail();
// Don't use BootSignature::from_der because tail might have trailing zeros
let mut reader = SliceReader::new(tail)?;
@@ -268,9 +280,8 @@ pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool {
Err(e) => Err(e)?,
};
sig.verify(img.payload())?;
Ok(())
}
inner(img, cert).is_ok()
};
res.is_ok()
}
enum Bytes {
@@ -296,12 +307,7 @@ pub fn sign_boot_image(
cert: *const c_char,
key: *const c_char,
) -> Vec<u8> {
fn inner(
payload: &[u8],
name: *const c_char,
cert: *const c_char,
key: *const c_char,
) -> LoggedResult<Vec<u8>> {
let res: LoggedResult<Vec<u8>> = try {
// Process arguments
let name = unsafe { Utf8CStr::from_ptr(name) }?;
let cert = match unsafe { Utf8CStr::from_ptr(cert) } {
@@ -337,7 +343,7 @@ pub fn sign_boot_image(
authenticated_attributes: attr,
signature: OctetString::new(sig)?,
};
sig.to_der().log()
}
inner(payload, name, cert, key).unwrap_or_default()
sig.to_der()?
};
res.unwrap_or_default()
}

View File

@@ -1,7 +1,7 @@
[package]
name = "magisk"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["staticlib"]
@@ -17,6 +17,7 @@ pb-rs = { workspace = true }
[dependencies]
base = { path = "../base", features = ["selinux"] }
derive = { path = "derive" }
cxx = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }

View File

@@ -1,211 +0,0 @@
#include <sys/mount.h>
#include <sys/wait.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <libgen.h>
#include <set>
#include <string>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
using namespace std;
bool zygisk_enabled = false;
/*********
* Setup *
*********/
static bool magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME);
// Alternative binaries paths
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
for (auto alt : alt_bin) {
if (access(alt, F_OK) == 0) {
rm_rf(DATABIN);
cp_afc(alt, DATABIN);
rm_rf(alt);
}
}
rm_rf("/cache/data_adb");
// Directories in /data/adb
chmod(SECURE_DIR, 0700);
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restorecon();
if (access(DATABIN "/busybox", X_OK))
return false;
ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp());
mkdir(dirname(buf), 0755);
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
if (access(DATABIN "/magisk32", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp());
cp_afc(DATABIN "/magisk32", buf);
}
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp());
cp_afc(DATABIN "/magiskpolicy", buf);
}
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
auto dir = xopen_dir("/dev/block");
if (!dir)
return;
dev = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
if (entry->d_type == DT_BLK) {
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
continue;
if (ioctl(fd, BLKROSET, &OFF) < 0)
PLOGE("unlock %s", entry->d_name);
close(fd);
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
static bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
vector<int> events;
constexpr char name[] = "/dev/.ev";
// First collect candidate events that accepts volume down
for (int minor = 64; minor < 96; ++minor) {
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
continue;
int fd = open(name, O_RDONLY | O_CLOEXEC);
unlink(name);
if (fd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask))
events.push_back(fd);
else
close(fd);
}
if (events.empty())
return false;
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
// Check if volume down key is held continuously for more than 3 seconds
for (int i = 0; i < 300; ++i) {
bool pressed = false;
for (const int &fd : events) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
pressed = true;
break;
}
}
if (!pressed)
return false;
// Check every 10ms
usleep(10000);
}
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
return true;
}
/***********************
* Boot Stage Handlers *
***********************/
bool MagiskD::post_fs_data() const noexcept {
setup_logfile();
LOGI("** post-fs-data mode running\n");
preserve_stub_apk();
if (access(SECURE_DIR, F_OK) != 0) {
if (SDK_INT < 24) {
xmkdir(SECURE_DIR, 0700);
} else {
LOGE(SECURE_DIR " is not present, abort\n");
return true;
}
}
prune_su_access();
if (!magisk_env()) {
LOGE("* Magisk environment incomplete, abort\n");
return true;
}
// Check safe mode
int bootloop_cnt = get_db_setting(DbEntryKey::BootloopCount);
// Increment the boot counter
set_db_setting(DbEntryKey::BootloopCount, bootloop_cnt + 1);
bool safe_mode = bootloop_cnt >= 2 || get_prop("persist.sys.safemode", true) == "1" ||
get_prop("ro.sys.safemode") == "1" || check_key_combo();
if (safe_mode) {
LOGI("* Safe mode triggered\n");
// Disable all modules and zygisk so next boot will be clean
disable_modules();
set_db_setting(DbEntryKey::ZygiskConfig, false);
return true;
}
exec_common_scripts("post-fs-data");
zygisk_enabled = get_db_setting(DbEntryKey::ZygiskConfig);
initialize_denylist();
setup_mounts();
handle_modules();
load_modules();
return false;
}
void MagiskD::late_start() const noexcept {
setup_logfile();
LOGI("** late_start service mode running\n");
exec_common_scripts("service");
exec_module_scripts("service");
}
void MagiskD::boot_complete() const noexcept {
setup_logfile();
LOGI("** boot-complete triggered\n");
// Reset the bootloop counter once we have boot-complete
set_db_setting(DbEntryKey::BootloopCount, 0);
// At this point it's safe to create the folder
if (access(SECURE_DIR, F_OK) != 0)
xmkdir(SECURE_DIR, 0700);
// Ensure manager exists
get_manager(0, nullptr, true);
reset_zygisk(true);
}

View File

@@ -1,9 +1,9 @@
use pb_rs::{types::FileDescriptor, ConfigBuilder};
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
println!("cargo:rerun-if-changed=resetprop/proto/persistent_properties.proto");

View File

@@ -2,6 +2,8 @@
#include <libgen.h>
#include <sys/un.h>
#include <sys/mount.h>
#include <sys/sysmacros.h>
#include <linux/input.h>
#include <consts.hpp>
#include <base.hpp>
@@ -134,23 +136,57 @@ void MagiskD::reboot() const noexcept {
exec_command_sync("/system/bin/reboot");
}
bool get_client_cred(int fd, sock_cred *cred) {
socklen_t len = sizeof(ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
return false;
char buf[4096];
len = sizeof(buf);
if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0)
len = 0;
buf[len] = '\0';
cred->context = buf;
return true;
}
bool read_string(int fd, std::string &str) {
str.clear();
int len = read_int(fd);
str.resize(len);
return xxread(fd, str.data(), len) == len;
}
string read_string(int fd) {
string str;
read_string(fd, str);
return str;
}
void write_string(int fd, string_view str) {
if (fd < 0) return;
write_int(fd, str.size());
xwrite(fd, str.data(), str.size());
}
static void handle_request_async(int client, int code, const sock_cred &cred) {
auto &daemon = MagiskD::Get();
switch (code) {
case +RequestCode::DENYLIST:
denylist_handler(client, &cred);
break;
case +RequestCode::SUPERUSER:
su_daemon_handler(client, &cred);
daemon.su_daemon_handler(client, cred);
break;
case +RequestCode::ZYGOTE_RESTART:
case +RequestCode::ZYGOTE_RESTART: {
LOGI("** zygote restarted\n");
MagiskD().prune_su_access();
daemon.prune_su_access();
scan_deny_apps();
reset_zygisk(false);
daemon.zygisk_reset(false);
close(client);
break;
}
case +RequestCode::SQLITE_CMD:
MagiskD().db_exec(client);
daemon.db_exec(client);
break;
case +RequestCode::REMOVE_MODULES: {
int do_reboot = read_int(client);
@@ -158,12 +194,12 @@ static void handle_request_async(int client, int code, const sock_cred &cred) {
write_int(client, 0);
close(client);
if (do_reboot) {
MagiskD().reboot();
daemon.reboot();
}
break;
}
case +RequestCode::ZYGISK:
zygisk_handler(client, &cred);
daemon.zygisk_handler(client);
break;
default:
__builtin_unreachable();
@@ -285,7 +321,7 @@ static void handle_request(pollfd *pfd) {
exec_task([=, fd = client.release()] { handle_request_async(fd, code, cred); });
} else {
exec_task([=, fd = client.release()] {
MagiskD().boot_stage_handler(fd, code);
MagiskD::Get().boot_stage_handler(fd, code);
});
}
}
@@ -328,7 +364,7 @@ static void daemon_entry() {
setcon(MAGISK_PROC_CON);
rust::daemon_entry();
SDK_INT = MagiskD().sdk_int();
SDK_INT = MagiskD::Get().sdk_int();
// Escape from cgroup
int pid = getpid();
@@ -378,7 +414,6 @@ static void daemon_entry() {
default_new(poll_map);
default_new(poll_fds);
default_new(module_list);
// Register handler for main socket
pollfd main_socket_pfd = { fd, POLLIN, 0 };
@@ -455,3 +490,116 @@ int connect_daemon(int req, bool create) {
}
return fd;
}
bool setup_magisk_env() {
char buf[4096];
LOGI("* Initializing Magisk environment\n");
ssprintf(buf, sizeof(buf), "%s/0/%s/install", APP_DATA_DIR, JAVA_PACKAGE_NAME);
// Alternative binaries paths
const char *alt_bin[] = { "/cache/data_adb/magisk", "/data/magisk", buf };
for (auto alt : alt_bin) {
if (access(alt, F_OK) == 0) {
rm_rf(DATABIN);
cp_afc(alt, DATABIN);
rm_rf(alt);
}
}
rm_rf("/cache/data_adb");
// Directories in /data/adb
chmod(SECURE_DIR, 0700);
xmkdir(DATABIN, 0755);
xmkdir(MODULEROOT, 0755);
xmkdir(SECURE_DIR "/post-fs-data.d", 0755);
xmkdir(SECURE_DIR "/service.d", 0755);
restorecon();
if (access(DATABIN "/busybox", X_OK))
return false;
ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp());
mkdir(dirname(buf), 0755);
cp_afc(DATABIN "/busybox", buf);
exec_command_async(buf, "--install", "-s", dirname(buf));
// magisk32 and magiskpolicy are not installed into ramdisk and has to be copied
// from data to magisk tmp
if (access(DATABIN "/magisk32", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magisk32", get_magisk_tmp());
cp_afc(DATABIN "/magisk32", buf);
}
if (access(DATABIN "/magiskpolicy", X_OK) == 0) {
ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp());
cp_afc(DATABIN "/magiskpolicy", buf);
}
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
auto dir = xopen_dir("/dev/block");
if (!dir)
return;
dev = dirfd(dir.get());
for (dirent *entry; (entry = readdir(dir.get()));) {
if (entry->d_type == DT_BLK) {
if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)
continue;
if (ioctl(fd, BLKROSET, &OFF) < 0)
PLOGE("unlock %s", entry->d_name);
close(fd);
}
}
}
#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))
bool check_key_combo() {
uint8_t bitmask[(KEY_MAX + 1) / 8];
vector<int> events;
constexpr char name[] = "/dev/.ev";
// First collect candidate events that accepts volume down
for (int minor = 64; minor < 96; ++minor) {
if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))
continue;
int fd = open(name, O_RDONLY | O_CLOEXEC);
unlink(name);
if (fd < 0)
continue;
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask))
events.push_back(fd);
else
close(fd);
}
if (events.empty())
return false;
run_finally fin([&]{ std::for_each(events.begin(), events.end(), close); });
// Check if volume down key is held continuously for more than 3 seconds
for (int i = 0; i < 300; ++i) {
bool pressed = false;
for (const int &fd : events) {
memset(bitmask, 0, sizeof(bitmask));
ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);
if (test_bit(KEY_VOLUMEDOWN, bitmask)) {
pressed = true;
break;
}
}
if (!pressed)
return false;
// Check every 10ms
usleep(10000);
}
LOGD("KEY_VOLUMEDOWN detected: enter safe mode\n");
return true;
}

View File

@@ -1,19 +1,22 @@
use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG};
use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG, SECURE_DIR};
use crate::db::Sqlite3;
use crate::ffi::{get_magisk_tmp, RequestCode};
use crate::ffi::{
check_key_combo, disable_modules, exec_common_scripts, exec_module_scripts, get_magisk_tmp,
initialize_denylist, setup_magisk_env, DbEntryKey, ModuleInfo, RequestCode,
};
use crate::get_prop;
use crate::logging::{magisk_logging, start_log_daemon};
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
use crate::mount::{clean_mounts, setup_mounts};
use crate::package::ManagerInfo;
use crate::su::SuInfo;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{
cstr, info, libc, open_fd, BufReadExt, Directory, FsPath, FsPathBuf, LoggedResult, ReadExt,
Utf8CStr, Utf8CStrBufArr,
cstr, error, info, libc, open_fd, path, AtomicArc, BufReadExt, FsPathBuf, ResultExt, Utf8CStr,
};
use bit_set::BitSet;
use bytemuck::bytes_of;
use std::fs::File;
use std::io;
use std::io::{BufReader, Read, Write};
use std::io::BufReader;
use std::os::unix::net::UnixStream;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Mutex, OnceLock};
// Global magiskd singleton
@@ -60,16 +63,29 @@ pub struct MagiskD {
pub sql_connection: Mutex<Option<Sqlite3>>,
pub manager_info: Mutex<ManagerInfo>,
boot_stage_lock: Mutex<BootStateFlags>,
pub module_list: OnceLock<Vec<ModuleInfo>>,
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
pub zygisk_enabled: AtomicBool,
pub zygote_start_count: AtomicU32,
pub cached_su_info: AtomicArc<SuInfo>,
sdk_int: i32,
pub is_emulator: bool,
is_recovery: bool,
}
impl MagiskD {
pub fn get() -> &'static MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub fn is_recovery(&self) -> bool {
self.is_recovery
}
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn sdk_int(&self) -> i32 {
self.sdk_int
}
@@ -82,39 +98,87 @@ impl MagiskD {
}
}
// app_id = app_no + AID_APP_START
// app_no range: [0, 9999]
pub fn get_app_no_list(&self) -> BitSet {
let mut list = BitSet::new();
let _: LoggedResult<()> = try {
let mut app_data_dir = Directory::open(self.app_data_dir())?;
// For each user
loop {
let entry = match app_data_dir.read()? {
None => break,
Some(e) => e,
};
let mut user_dir = match entry.open_as_dir() {
Err(_) => continue,
Ok(dir) => dir,
};
// For each package
loop {
match user_dir.read()? {
None => break,
Some(e) => {
let attr = e.get_attr()?;
let app_id = to_app_id(attr.st.st_uid as i32);
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
let app_no = app_id - AID_APP_START;
list.insert(app_no as usize);
}
}
}
}
fn post_fs_data(&self) -> bool {
setup_logfile();
info!("** post-fs-data mode running");
self.preserve_stub_apk();
// Check secure dir
let secure_dir = path!(SECURE_DIR);
if !secure_dir.exists() {
if self.sdk_int < 24 {
secure_dir.mkdir(0o700).log_ok();
} else {
error!("* {} is not present, abort", SECURE_DIR);
return true;
}
};
list
}
self.prune_su_access();
if !setup_magisk_env() {
error!("* Magisk environment incomplete, abort");
return true;
}
// Check safe mode
let boot_cnt = self.get_db_setting(DbEntryKey::BootloopCount);
self.set_db_setting(DbEntryKey::BootloopCount, boot_cnt + 1)
.log()
.ok();
let safe_mode = boot_cnt >= 2
|| get_prop(cstr!("persist.sys.safemode"), true) == "1"
|| get_prop(cstr!("ro.sys.safemode"), false) == "1"
|| check_key_combo();
if safe_mode {
info!("* Safe mode triggered");
// Disable all modules and zygisk so next boot will be clean
disable_modules();
self.set_db_setting(DbEntryKey::ZygiskConfig, 0).log_ok();
return true;
}
exec_common_scripts(cstr!("post-fs-data"));
self.zygisk_enabled.store(
self.get_db_setting(DbEntryKey::ZygiskConfig) != 0,
Ordering::Release,
);
initialize_denylist();
setup_mounts();
let modules = self.handle_modules();
self.module_list.set(modules).ok();
clean_mounts();
false
}
fn late_start(&self) {
setup_logfile();
info!("** late_start service mode running");
exec_common_scripts(cstr!("service"));
if let Some(module_list) = self.module_list.get() {
exec_module_scripts(cstr!("service"), module_list);
}
}
fn boot_complete(&self) {
setup_logfile();
info!("** boot-complete triggered");
// Reset the bootloop counter once we have boot-complete
self.set_db_setting(DbEntryKey::BootloopCount, 0).log_ok();
// At this point it's safe to create the folder
let secure_dir = path!(SECURE_DIR);
if !secure_dir.exists() {
secure_dir.mkdir(0o700).log_ok();
}
self.ensure_manager();
self.zygisk_reset(true)
}
pub fn boot_stage_handler(&self, client: i32, code: i32) {
@@ -164,8 +228,7 @@ pub fn daemon_entry() {
|| get_prop(cstr!("ro.product.device"), false).contains("vsoc");
// Load config status
let mut buf = Utf8CStrBufArr::<64>::new();
let path = FsPathBuf::new(&mut buf)
let path = FsPathBuf::<64>::new()
.join(get_magisk_tmp())
.join(MAIN_CONFIG);
let mut is_recovery = false;
@@ -181,7 +244,7 @@ pub fn daemon_entry() {
}
let mut sdk_int = -1;
if let Ok(file) = FsPath::from(cstr!("/system/build.prop")).open(O_RDONLY | O_CLOEXEC) {
if let Ok(file) = path!("/system/build.prop").open(O_RDONLY | O_CLOEXEC) {
let mut file = BufReader::new(file);
file.foreach_props(|key, val| {
if key == "ro.build.version.sdk" {
@@ -203,6 +266,7 @@ pub fn daemon_entry() {
sdk_int,
is_emulator,
is_recovery,
zygote_start_count: AtomicU32::new(1),
..Default::default()
};
MAGISKD.set(magiskd).ok();
@@ -238,43 +302,3 @@ fn check_data() -> bool {
}
false
}
pub fn get_magiskd() -> &'static MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub trait IpcRead {
fn ipc_read_int(&mut self) -> io::Result<i32>;
fn ipc_read_string(&mut self) -> io::Result<String>;
}
impl<T: Read> IpcRead for T {
fn ipc_read_int(&mut self) -> io::Result<i32> {
let mut val: i32 = 0;
self.read_pod(&mut val)?;
Ok(val)
}
fn ipc_read_string(&mut self) -> io::Result<String> {
let len = self.ipc_read_int()?;
let mut val = "".to_string();
self.take(len as u64).read_to_string(&mut val)?;
Ok(val)
}
}
pub trait IpcWrite {
fn ipc_write_int(&mut self, val: i32) -> io::Result<()>;
fn ipc_write_string(&mut self, val: &str) -> io::Result<()>;
}
impl<T: Write> IpcWrite for T {
fn ipc_write_int(&mut self, val: i32) -> io::Result<()> {
self.write_all(bytes_of(&val))
}
fn ipc_write_string(&mut self, val: &str) -> io::Result<()> {
self.ipc_write_int(val.len() as i32)?;
self.write_all(val.as_bytes())
}
}

View File

@@ -1,10 +1,12 @@
#![allow(improper_ctypes, improper_ctypes_definitions)]
use crate::daemon::{IpcRead, IpcWrite, MagiskD, MAGISKD};
use crate::daemon::{MagiskD, MAGISKD};
use crate::ffi::{
open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbSettings, DbStatement, DbValues,
MntNsMode, MultiuserMode, RootAccess,
open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbStatement, DbValues, MntNsMode,
};
use crate::socket::{IpcRead, IpcWrite};
use base::{LoggedResult, ResultExt, Utf8CStr};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::ffi::c_void;
use std::fs::File;
use std::io::{BufReader, BufWriter};
@@ -54,16 +56,33 @@ where
}
}
impl Default for RootAccess {
fn default() -> Self {
RootAccess::AppsAndAdb
}
#[derive(Default)]
pub struct DbSettings {
pub root_access: RootAccess,
pub multiuser_mode: MultiuserMode,
pub mnt_ns: MntNsMode,
pub boot_count: i32,
pub denylist: bool,
pub zygisk: bool,
}
impl Default for MultiuserMode {
fn default() -> Self {
MultiuserMode::OwnerOnly
}
#[repr(i32)]
#[derive(Default, FromPrimitive)]
pub enum RootAccess {
Disabled,
AppsOnly,
AdbOnly,
#[default]
AppsAndAdb,
}
#[repr(i32)]
#[derive(Default, FromPrimitive)]
pub enum MultiuserMode {
#[default]
OwnerOnly,
OwnerManaged,
User,
}
impl Default for MntNsMode {
@@ -72,10 +91,6 @@ impl Default for MntNsMode {
}
}
pub fn get_default_db_settings() -> DbSettings {
DbSettings::default()
}
impl DbEntryKey {
fn to_str(self) -> &'static str {
match self {
@@ -103,8 +118,10 @@ impl SqlTable for DbSettings {
}
}
match key {
"root_access" => self.root_access = RootAccess { repr: value },
"multiuser_mode" => self.multiuser_mode = MultiuserMode { repr: value },
"root_access" => self.root_access = RootAccess::from_i32(value).unwrap_or_default(),
"multiuser_mode" => {
self.multiuser_mode = MultiuserMode::from_i32(value).unwrap_or_default()
}
"mnt_ns" => self.mnt_ns = MntNsMode { repr: value },
"denylist" => self.denylist = value != 0,
"zygisk" => self.zygisk = value != 0,
@@ -121,7 +138,7 @@ unsafe impl Send for Sqlite3 {}
type SqlBindCallback = Option<unsafe extern "C" fn(*mut c_void, i32, Pin<&mut DbStatement>) -> i32>;
type SqlExecCallback = Option<unsafe extern "C" fn(*mut c_void, &[String], &DbValues)>;
extern "C" {
unsafe extern "C" {
fn sql_exec_impl(
db: *mut sqlite3,
sql: &str,
@@ -143,16 +160,18 @@ struct DbArgs<'a> {
}
unsafe extern "C" fn bind_arguments(v: *mut c_void, idx: i32, stmt: Pin<&mut DbStatement>) -> i32 {
let args = &mut *(v as *mut DbArgs<'_>);
if args.curr < args.args.len() {
let arg = &args.args[args.curr];
args.curr += 1;
match *arg {
Text(v) => stmt.bind_text(idx, v),
Integer(v) => stmt.bind_int64(idx, v),
unsafe {
let args = &mut *(v as *mut DbArgs<'_>);
if args.curr < args.args.len() {
let arg = &args.args[args.curr];
args.curr += 1;
match *arg {
Text(v) => stmt.bind_text(idx, v),
Integer(v) => stmt.bind_int64(idx, v),
}
} else {
0
}
} else {
0
}
}
@@ -161,8 +180,10 @@ unsafe extern "C" fn read_db_row<T: SqlTable>(
columns: &[String],
values: &DbValues,
) {
let table = &mut *(v as *mut T);
table.on_row(columns, values);
unsafe {
let table = &mut *(v as *mut T);
table.on_row(columns, values);
}
}
impl MagiskD {
@@ -172,10 +193,9 @@ impl MagiskD {
let raw_db = open_and_init_db();
*db = NonNull::new(raw_db).map(Sqlite3);
}
if let Some(ref mut db) = *db {
f(db.0.as_ptr())
} else {
-1
match *db {
Some(ref mut db) => f(db.0.as_ptr()),
_ => -1,
}
}
@@ -229,8 +249,8 @@ impl MagiskD {
pub fn get_db_setting(&self, key: DbEntryKey) -> i32 {
// Get default values
let mut val = match key {
DbEntryKey::RootAccess => RootAccess::default().repr,
DbEntryKey::SuMultiuserMode => MultiuserMode::default().repr,
DbEntryKey::RootAccess => RootAccess::default() as i32,
DbEntryKey::SuMultiuserMode => MultiuserMode::default() as i32,
DbEntryKey::SuMntNs => MntNsMode::default().repr,
DbEntryKey::DenylistConfig => 0,
DbEntryKey::ZygiskConfig => self.is_emulator as i32,
@@ -285,7 +305,7 @@ impl MagiskD {
fn db_exec_for_client(&self, fd: OwnedFd) -> LoggedResult<()> {
let mut file = File::from(fd);
let mut reader = BufReader::new(&mut file);
let sql = reader.ipc_read_string()?;
let sql: String = reader.read_decodable()?;
let mut writer = BufWriter::new(&mut file);
let mut output_fn = |columns: &[String], values: &DbValues| {
let mut out = "".to_string();
@@ -297,22 +317,14 @@ impl MagiskD {
out.push('=');
out.push_str(values.get_text(i as i32));
}
writer.ipc_write_string(&out).log().ok();
writer.write_encodable(&out).log_ok();
};
self.db_exec_with_rows(&sql, &[], &mut output_fn);
writer.ipc_write_string("").log()
writer.write_encodable("").log()
}
}
impl MagiskD {
pub fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool {
cfg.zygisk = self.is_emulator;
self.db_exec_with_rows("SELECT * FROM settings", &[], cfg)
.sql_result()
.log()
.is_ok()
}
pub fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool {
self.set_db_setting(key, value).log().is_ok()
}
@@ -324,7 +336,7 @@ impl MagiskD {
}
}
#[export_name = "sql_exec_rs"]
#[unsafe(export_name = "sql_exec_rs")]
unsafe extern "C" fn sql_exec_for_cxx(
sql: &str,
bind_callback: SqlBindCallback,
@@ -332,14 +344,16 @@ unsafe extern "C" fn sql_exec_for_cxx(
exec_callback: SqlExecCallback,
exec_cookie: *mut c_void,
) -> i32 {
MAGISKD.get().unwrap_unchecked().with_db(|db| {
sql_exec_impl(
db,
sql,
bind_callback,
bind_cookie,
exec_callback,
exec_cookie,
)
})
unsafe {
MAGISKD.get().unwrap_unchecked().with_db(|db| {
sql_exec_impl(
db,
sql,
bind_callback,
bind_cookie,
exec_callback,
exec_cookie,
)
})
}
}

View File

@@ -10,6 +10,7 @@
#include <base.hpp>
#include <sqlite.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include "deny.hpp"
@@ -108,12 +109,11 @@ static bool proc_name_match(int pid, string_view name) {
bool proc_context_match(int pid, string_view context) {
char buf[PATH_MAX];
sprintf(buf, "/proc/%d/attr/current", pid);
if (auto fp = open_file(buf, "re")) {
fgets(buf, sizeof(buf), fp.get());
if (str_starts(buf, context)) {
return true;
}
char con[1024];
sprintf(buf, "/proc/%d", pid);
if (lgetfilecon(buf, { con, sizeof(con) }) >= 0) {
return str_starts(con, context);
}
return false;
}
@@ -370,7 +370,7 @@ int enable_deny() {
denylist_enforced = true;
if (!zygisk_enabled) {
if (!MagiskD::Get().zygisk_enabled()) {
if (new_daemon_thread(&logcat)) {
denylist_enforced = false;
return DenyResponse::ERROR;
@@ -385,7 +385,7 @@ int enable_deny() {
}
}
MagiskD().set_db_setting(DbEntryKey::DenylistConfig, true);
MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, true);
return DenyResponse::OK;
}
@@ -393,13 +393,13 @@ int disable_deny() {
if (denylist_enforced.exchange(false)) {
LOGI("* Disable DenyList\n");
}
MagiskD().set_db_setting(DbEntryKey::DenylistConfig, false);
MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, false);
return DenyResponse::OK;
}
void initialize_denylist() {
if (!denylist_enforced) {
if (MagiskD().get_db_setting(DbEntryKey::DenylistConfig))
if (MagiskD::Get().get_db_setting(DbEntryKey::DenylistConfig))
enable_deny();
}
}
@@ -429,3 +429,12 @@ bool is_deny_target(int uid, string_view process) {
}
return false;
}
void update_deny_flags(int uid, rust::Str process, uint32_t &flags) {
if (is_deny_target(uid, { process.begin(), process.end() })) {
flags |= +ZygiskStateFlags::ProcessOnDenyList;
}
if (denylist_enforced) {
flags |= +ZygiskStateFlags::DenyListEnforced;
}
}

View File

@@ -0,0 +1,13 @@
[package]
name = "derive"
version.workspace = true
edition.workspace = true
[lib]
path = "lib.rs"
proc-macro = true
[dependencies]
syn = { workspace = true }
quote = { workspace = true }
proc-macro2 = { workspace = true }

View File

@@ -0,0 +1,124 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam};
pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
// Add a bound `T: Decodable` to every type parameter T.
let mut generics = input.generics;
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param
.bounds
.push(parse_quote!(crate::socket::Decodable));
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let sum = gen_size_sum(&input.data);
let encode = gen_encode(&input.data);
let decode = gen_decode(&input.data);
let expanded = quote! {
// The generated impl.
impl #impl_generics crate::socket::Encodable for #name #ty_generics #where_clause {
fn encoded_len(&self) -> usize {
#sum
}
fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
#encode
Ok(())
}
}
impl #impl_generics crate::socket::Decodable for #name #ty_generics #where_clause {
fn decode(r: &mut impl std::io::Read) -> std::io::Result<Self> {
let val = #decode;
Ok(val)
}
}
};
proc_macro::TokenStream::from(expanded)
}
// Generate an expression to sum up the size of each field.
fn gen_size_sum(data: &Data) -> TokenStream {
match *data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) => {
// Expands to an expression like
//
// 0 + self.x.encoded_len() + self.y.encoded_len() + self.z.encoded_len()
let recurse = fields.named.iter().map(|f| {
let name = &f.ident;
quote_spanned! { f.span() =>
crate::socket::Encodable::encoded_len(&self.#name)
}
});
quote! {
0 #(+ #recurse)*
}
}
_ => unimplemented!(),
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
// Generate an expression to encode each field.
fn gen_encode(data: &Data) -> TokenStream {
match *data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) => {
// Expands to an expression like
//
// self.x.encode(w)?; self.y.encode(w)?; self.z.encode(w)?;
let recurse = fields.named.iter().map(|f| {
let name = &f.ident;
quote_spanned! { f.span() =>
crate::socket::Encodable::encode(&self.#name, w)?;
}
});
quote! {
#(#recurse)*
}
}
_ => unimplemented!(),
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
// Generate an expression to decode each field.
fn gen_decode(data: &Data) -> TokenStream {
match *data {
Data::Struct(ref data) => {
match data.fields {
Fields::Named(ref fields) => {
// Expands to an expression like
//
// Self { x: Decodable::decode(r)?, y: Decodable::decode(r)?, }
let recurse = fields.named.iter().map(|f| {
let name = &f.ident;
quote_spanned! { f.span() =>
#name: crate::socket::Decodable::decode(r)?,
}
});
quote! {
Self { #(#recurse)* }
}
}
_ => unimplemented!(),
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}

View File

@@ -0,0 +1,8 @@
use proc_macro::TokenStream;
mod decodable;
#[proc_macro_derive(Decodable)]
pub fn derive_decodable(input: TokenStream) -> TokenStream {
decodable::derive_decodable(input)
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <sys/socket.h>
#include <pthread.h>
#include <poll.h>
#include <string>
@@ -7,7 +8,8 @@
#include <atomic>
#include <functional>
#include "socket.hpp"
#include <base.hpp>
#include "../core-rs.hpp"
#define AID_ROOT 0
@@ -26,21 +28,56 @@ enum class RespondCode : int {
END
};
struct module_info {
std::string name;
int z32 = -1;
#if defined(__LP64__)
int z64 = -1;
#endif
};
struct ModuleInfo;
extern bool zygisk_enabled;
extern std::vector<module_info> *module_list;
extern std::string native_bridge;
void reset_zygisk(bool restore);
// Daemon
int connect_daemon(int req, bool create = false);
const char *get_magisk_tmp();
void unlock_blocks();
bool setup_magisk_env();
bool check_key_combo();
void restore_zygisk_prop();
// Sockets
struct sock_cred : public ucred {
std::string context;
};
template<typename T> requires(std::is_trivially_copyable_v<T>)
T read_any(int fd) {
T val;
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
return -1;
return val;
}
template<typename T> requires(std::is_trivially_copyable_v<T>)
void write_any(int fd, T val) {
if (fd < 0) return;
xwrite(fd, &val, sizeof(val));
}
bool get_client_cred(int fd, sock_cred *cred);
static inline int read_int(int fd) { return read_any<int>(fd); }
static inline void write_int(int fd, int val) { write_any(fd, val); }
std::string read_string(int fd);
bool read_string(int fd, std::string &str);
void write_string(int fd, std::string_view str);
template<typename T> requires(std::is_trivially_copyable_v<T>)
void write_vector(int fd, const std::vector<T> &vec) {
write_int(fd, vec.size());
xwrite(fd, vec.data(), vec.size() * sizeof(T));
}
template<typename T> requires(std::is_trivially_copyable_v<T>)
bool read_vector(int fd, std::vector<T> &vec) {
int size = read_int(fd);
vec.resize(size);
return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T);
}
// Poll control
using poll_callback = void(*)(pollfd*);
@@ -54,20 +91,17 @@ void exec_task(std::function<void()> &&task);
// Daemon handlers
void denylist_handler(int client, const sock_cred *cred);
void su_daemon_handler(int client, const sock_cred *cred);
void zygisk_handler(int client, const sock_cred *cred);
// Module stuffs
void handle_modules();
void load_modules();
void disable_modules();
void remove_modules();
void exec_module_scripts(const char *stage);
// Scripting
void install_apk(rust::Utf8CStr apk);
void uninstall_pkg(rust::Utf8CStr pkg);
void exec_common_scripts(rust::Utf8CStr stage);
void exec_module_scripts(rust::Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list);
void exec_script(const char *script);
void exec_common_scripts(const char *stage);
void exec_module_scripts(const char *stage, const std::vector<std::string_view> &modules);
void clear_pkg(const char *pkg, int user_id);
[[noreturn]] void install_module(const char *file);
@@ -78,3 +112,16 @@ void initialize_denylist();
void scan_deny_apps();
bool is_deny_target(int uid, std::string_view process);
void revert_unmount(int pid = -1) noexcept;
void update_deny_flags(int uid, rust::Str process, uint32_t &flags);
// MagiskSU
void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode);
void app_log(const SuAppRequest &info, SuPolicy policy, bool notify);
void app_notify(const SuAppRequest &info, SuPolicy policy);
int app_request(const SuAppRequest &info);
// Rust bindings
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) {
return resolve_preinit_dir(base_dir.c_str());
}

View File

@@ -1,13 +0,0 @@
#pragma once
#include <base.hpp>
const char *get_magisk_tmp();
void install_apk(rust::Utf8CStr apk);
void uninstall_pkg(rust::Utf8CStr pkg);
// Rust bindings
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) {
return resolve_preinit_dir(base_dir.c_str());
}

View File

@@ -1,38 +0,0 @@
#pragma once
#include <sys/un.h>
#include <sys/socket.h>
#include <string_view>
#include <string>
#include <vector>
struct sock_cred : public ucred {
std::string context;
};
bool get_client_cred(int fd, sock_cred *cred);
std::vector<int> recv_fds(int sockfd);
int recv_fd(int sockfd);
int send_fds(int sockfd, const int *fds, int cnt);
int send_fd(int sockfd, int fd);
int read_int(int fd);
int read_int_be(int fd);
void write_int(int fd, int val);
void write_int_be(int fd, int val);
std::string read_string(int fd);
bool read_string(int fd, std::string &str);
void write_string(int fd, std::string_view str);
template<typename T> requires(std::is_trivially_copyable_v<T>)
void write_vector(int fd, const std::vector<T> &vec) {
write_int(fd, static_cast<int>(vec.size()));
xwrite(fd, vec.data(), vec.size() * sizeof(T));
}
template<typename T> requires(std::is_trivially_copyable_v<T>)
bool read_vector(int fd, std::vector<T> &vec) {
int size = read_int(fd);
if (size == -1) return false;
vec.resize(size);
return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T);
}

View File

@@ -2,15 +2,25 @@
#![feature(try_blocks)]
#![feature(let_chains)]
#![feature(fn_traits)]
#![feature(unix_socket_ancillary_data)]
#![feature(unix_socket_peek)]
#![allow(clippy::missing_safety_doc)]
use base::Utf8CStr;
use daemon::{daemon_entry, get_magiskd, MagiskD};
use db::get_default_db_settings;
use crate::ffi::SuRequest;
use crate::socket::Encodable;
use base::{libc, Utf8CStr};
use cxx::{type_id, ExternType};
use daemon::{daemon_entry, MagiskD};
use derive::Decodable;
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
use mount::{clean_mounts, find_preinit_device, revert_unmount, setup_mounts};
use mount::{find_preinit_device, revert_unmount};
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
use su::get_default_root_settings;
use socket::{recv_fd, recv_fds, send_fd, send_fds};
use std::fs::File;
use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::os::fd::FromRawFd;
use zygisk::zygisk_should_load_module;
#[path = "../include/consts.rs"]
mod consts;
@@ -20,8 +30,11 @@ mod logging;
mod mount;
mod package;
mod resetprop;
mod socket;
mod su;
mod zygisk;
#[allow(clippy::needless_lifetimes)]
#[cxx::bridge]
pub mod ffi {
#[repr(i32)]
@@ -49,6 +62,72 @@ pub mod ffi {
END,
}
enum DbEntryKey {
RootAccess,
SuMultiuserMode,
SuMntNs,
DenylistConfig,
ZygiskConfig,
BootloopCount,
SuManager,
}
#[repr(i32)]
enum MntNsMode {
Global,
Requester,
Isolate,
}
#[repr(i32)]
enum SuPolicy {
Query,
Deny,
Allow,
}
struct ModuleInfo {
name: String,
z32: i32,
z64: i32,
}
#[repr(i32)]
enum ZygiskRequest {
GetInfo,
ConnectCompanion,
GetModDir,
}
#[repr(u32)]
enum ZygiskStateFlags {
ProcessGrantedRoot = 0x00000001,
ProcessOnDenyList = 0x00000002,
DenyListEnforced = 0x40000000,
ProcessIsMagiskApp = 0x80000000,
}
#[derive(Decodable)]
struct SuRequest {
target_uid: i32,
target_pid: i32,
login: bool,
keep_env: bool,
shell: String,
command: String,
context: String,
gids: Vec<u32>,
}
struct SuAppRequest<'a> {
uid: i32,
pid: i32,
eval_uid: i32,
mgr_pkg: &'a str,
mgr_uid: i32,
request: &'a SuRequest,
}
extern "C++" {
include!("include/resetprop.hpp");
@@ -67,152 +146,123 @@ pub mod ffi {
#[namespace = "rust"]
#[cxx_name = "Utf8CStr"]
type Utf8CStrRef<'a> = base::ffi::Utf8CStrRef<'a>;
#[cxx_name = "ucred"]
type UCred = crate::UCred;
include!("include/daemon.hpp");
include!("include/core.hpp");
#[cxx_name = "get_magisk_tmp_rs"]
fn get_magisk_tmp() -> Utf8CStrRef<'static>;
#[cxx_name = "resolve_preinit_dir_rs"]
fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String;
fn setup_magisk_env() -> bool;
fn check_key_combo() -> bool;
fn disable_modules();
fn exec_common_scripts(stage: Utf8CStrRef);
fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec<ModuleInfo>);
fn install_apk(apk: Utf8CStrRef);
fn uninstall_pkg(apk: Utf8CStrRef);
fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);
fn initialize_denylist();
fn restore_zygisk_prop();
fn switch_mnt_ns(pid: i32) -> i32;
}
fn app_request(req: &SuAppRequest) -> i32;
fn app_notify(req: &SuAppRequest, policy: SuPolicy);
fn app_log(req: &SuAppRequest, policy: SuPolicy, notify: bool);
fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode);
enum DbEntryKey {
RootAccess,
SuMultiuserMode,
SuMntNs,
DenylistConfig,
ZygiskConfig,
BootloopCount,
SuManager,
}
#[repr(i32)]
enum RootAccess {
Disabled,
AppsOnly,
AdbOnly,
AppsAndAdb,
}
#[repr(i32)]
enum MultiuserMode {
OwnerOnly,
OwnerManaged,
User,
}
#[repr(i32)]
enum MntNsMode {
Global,
Requester,
Isolate,
}
#[derive(Default)]
struct DbSettings {
root_access: RootAccess,
multiuser_mode: MultiuserMode,
mnt_ns: MntNsMode,
boot_count: i32,
denylist: bool,
zygisk: bool,
}
#[repr(i32)]
enum SuPolicy {
Query,
Deny,
Allow,
}
struct RootSettings {
policy: SuPolicy,
log: bool,
notify: bool,
}
unsafe extern "C++" {
include!("include/sqlite.hpp");
fn sqlite3_errstr(code: i32) -> *const c_char;
type sqlite3;
fn open_and_init_db() -> *mut sqlite3;
type DbValues;
type DbStatement;
fn sqlite3_errstr(code: i32) -> *const c_char;
fn open_and_init_db() -> *mut sqlite3;
fn get_int(self: &DbValues, index: i32) -> i32;
#[cxx_name = "get_str"]
fn get_text(self: &DbValues, index: i32) -> &str;
fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;
fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;
}
extern "Rust" {
fn rust_test_entry();
fn android_logging();
fn zygisk_logging();
fn zygisk_close_logd();
fn zygisk_get_logd() -> i32;
fn setup_logfile();
fn setup_mounts();
fn clean_mounts();
fn find_preinit_device() -> String;
fn revert_unmount(pid: i32);
fn zygisk_should_load_module(flags: u32) -> bool;
unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>);
unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>);
unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool;
unsafe fn persist_set_prop(name: Utf8CStrRef, value: Utf8CStrRef) -> bool;
fn send_fd(socket: i32, fd: i32) -> bool;
fn send_fds(socket: i32, fds: &[i32]) -> bool;
fn recv_fd(socket: i32) -> i32;
fn recv_fds(socket: i32) -> Vec<i32>;
unsafe fn write_to_fd(self: &SuRequest, fd: i32);
#[namespace = "rust"]
fn daemon_entry();
}
// Default constructors
extern "Rust" {
#[Self = SuRequest]
#[cxx_name = "New"]
fn default() -> SuRequest;
}
// FFI for MagiskD
extern "Rust" {
type MagiskD;
fn is_recovery(&self) -> bool;
fn sdk_int(&self) -> i32;
fn zygisk_enabled(&self) -> bool;
fn boot_stage_handler(&self, client: i32, code: i32);
fn preserve_stub_apk(&self);
fn zygisk_handler(&self, client: i32);
fn zygisk_reset(&self, restore: bool);
fn prune_su_access(&self);
fn uid_granted_root(&self, mut uid: i32) -> bool;
fn su_daemon_handler(&self, client: i32, cred: &UCred);
#[cxx_name = "get_manager"]
unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32;
#[cxx_name = "get_db_settings"]
fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool;
fn get_db_setting(&self, key: DbEntryKey) -> i32;
#[cxx_name = "set_db_setting"]
fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool;
#[cxx_name = "db_exec"]
fn db_exec_for_cxx(&self, client_fd: i32);
#[cxx_name = "get_root_settings"]
fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool;
#[cxx_name = "DbSettings"]
fn get_default_db_settings() -> DbSettings;
#[cxx_name = "RootSettings"]
fn get_default_root_settings() -> RootSettings;
#[cxx_name = "MagiskD"]
fn get_magiskd() -> &'static MagiskD;
#[Self = MagiskD]
#[cxx_name = "Get"]
fn get() -> &'static MagiskD;
}
unsafe extern "C++" {
#[allow(dead_code)]
fn reboot(self: &MagiskD);
fn post_fs_data(self: &MagiskD) -> bool;
fn late_start(self: &MagiskD);
fn boot_complete(self: &MagiskD);
fn handle_modules(self: &MagiskD) -> Vec<ModuleInfo>;
}
}
fn rust_test_entry() {}
#[repr(transparent)]
pub struct UCred(pub libc::ucred);
unsafe impl ExternType for UCred {
type Id = type_id!("ucred");
type Kind = cxx::kind::Trivial;
}
impl SuRequest {
unsafe fn write_to_fd(&self, fd: i32) {
unsafe {
let mut w = ManuallyDrop::new(File::from_raw_fd(fd));
self.encode(w.deref_mut()).ok();
}
}
}
pub fn get_prop(name: &Utf8CStr, persist: bool) -> String {
unsafe { ffi::get_prop_rs(name.as_ptr(), persist) }

View File

@@ -6,8 +6,8 @@ use base::libc::{
timespec, tm, O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIGPIPE, SIG_BLOCK, SIG_SETMASK,
};
use base::{
const_format::concatcp, libc, raw_cstr, FsPathBuf, LogLevel, Logger, ReadExt, SharedFd,
Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, LOGGER,
const_format::concatcp, cstr_buf, libc, raw_cstr, FsPathBuf, LogLevel, Logger, ReadExt,
Utf8CStr, Utf8CStrBuf, WriteExt, LOGGER,
};
use bytemuck::{bytes_of, write_zeroes, Pod, Zeroable};
use num_derive::{FromPrimitive, ToPrimitive};
@@ -18,10 +18,10 @@ use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::io::{IoSlice, Read, Write};
use std::mem::ManuallyDrop;
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
use std::os::fd::{FromRawFd, RawFd};
use std::ptr::null_mut;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{fs, io};
@@ -42,7 +42,7 @@ enum ALogPriority {
type ThreadEntry = extern "C" fn(*mut c_void) -> *mut c_void;
extern "C" {
unsafe extern "C" {
fn __android_log_write(prio: i32, tag: *const c_char, msg: *const c_char);
fn strftime(buf: *mut c_char, len: usize, fmt: *const c_char, tm: *const tm) -> usize;
fn new_daemon_thread(entry: ThreadEntry, arg: *mut c_void);
@@ -114,7 +114,7 @@ struct LogMeta {
const MAX_MSG_LEN: usize = PIPE_BUF - size_of::<LogMeta>();
fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result<usize> {
fn write_log_to_pipe(mut logd: &File, prio: i32, msg: &Utf8CStr) -> io::Result<usize> {
// Truncate message if needed
let len = min(MAX_MSG_LEN, msg.len());
let msg = &msg.as_bytes()[..len];
@@ -130,29 +130,28 @@ fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result<u
let io2 = IoSlice::new(msg);
let result = logd.write_vectored(&[io1, io2]);
if let Err(ref e) = result {
let mut buf = Utf8CStrBufArr::default();
buf.write_fmt(format_args_nl!("Cannot write_log_to_pipe: {}", e))
let mut buf = cstr_buf::default();
buf.write_fmt(format_args!("Cannot write_log_to_pipe: {}", e))
.ok();
android_log_write(LogLevel::Error, &buf);
}
result
}
static MAGISK_LOGD_FD: Mutex<SharedFd> = Mutex::new(SharedFd::new());
static MAGISK_LOGD_FD: Mutex<Option<Arc<File>>> = Mutex::new(None);
fn with_logd_fd<F: FnOnce(&mut File) -> io::Result<()>>(f: F) {
fn with_logd_fd<R, F: FnOnce(&File) -> io::Result<R>>(f: F) {
let fd = MAGISK_LOGD_FD.lock().unwrap().clone();
// SAFETY: writing less than PIPE_BUF bytes is guaranteed to be atomic on Linux
if let Some(mut logd) = unsafe { fd.as_file() } {
if f(&mut logd).is_err() {
if let Some(logd) = fd {
if f(&logd).is_err() {
// If any error occurs, shut down the logd pipe
*MAGISK_LOGD_FD.lock().unwrap() = SharedFd::default();
*MAGISK_LOGD_FD.lock().unwrap() = None;
}
}
}
fn magisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
with_logd_fd(|logd| write_log_to_pipe(logd, prio, msg).map(|_| ()));
with_logd_fd(|logd| write_log_to_pipe(logd, prio, msg));
}
// SAFETY: zygisk client code runs single threaded, so no need to prevent data race
@@ -181,10 +180,7 @@ pub fn zygisk_get_logd() -> i32 {
let mut fd = ZYGISK_LOGD.load(Ordering::Relaxed);
if fd < 0 {
android_logging();
let mut buf = Utf8CStrBufArr::default();
let path = FsPathBuf::new(&mut buf)
.join(get_magisk_tmp())
.join(LOG_PIPE);
let path = FsPathBuf::default().join(get_magisk_tmp()).join(LOG_PIPE);
// Open as RW as sometimes it may block
fd = unsafe { libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC) };
if fd >= 0 {
@@ -217,8 +213,8 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {
pthread_sigmask(SIG_BLOCK, &mask, &mut orig_mask);
}
let mut logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
let result = write_log_to_pipe(&mut logd, prio, msg);
let logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
let result = write_log_to_pipe(&logd, prio, msg);
// Consume SIGPIPE if exists, then restore mask
unsafe {
@@ -270,7 +266,7 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
let mut meta = LogMeta::zeroed();
let mut msg_buf = [0u8; MAX_MSG_LEN];
let mut aux = Utf8CStrBufArr::<64>::new();
let mut aux = cstr_buf::new::<64>();
loop {
// Read request
@@ -320,12 +316,7 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
if localtime_r(&secs, &mut tm).is_null() {
continue;
}
let len = strftime(
aux.mut_buf().as_mut_ptr().cast(),
aux.capacity(),
raw_cstr!("%m-%d %T"),
&tm,
);
let len = strftime(aux.as_mut_ptr(), aux.capacity(), raw_cstr!("%m-%d %T"), &tm);
aux.set_len(len);
aux.write_fmt(format_args!(
".{:03} {:5} {:5} {} : ",
@@ -347,34 +338,31 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
writer_loop(arg as RawFd).ok();
// If any error occurs, shut down the logd pipe
*MAGISK_LOGD_FD.lock().unwrap() = SharedFd::default();
*MAGISK_LOGD_FD.lock().unwrap() = None;
null_mut()
}
pub fn setup_logfile() {
with_logd_fd(|logd| {
with_logd_fd(|mut logd| {
let meta = LogMeta {
prio: -1,
len: 0,
pid: 0,
tid: 0,
};
logd.write_all(bytes_of(&meta))
(&mut logd).write_pod(&meta)
});
}
pub fn start_log_daemon() {
let mut buf = Utf8CStrBufArr::default();
let path = FsPathBuf::new(&mut buf)
.join(get_magisk_tmp())
.join(LOG_PIPE);
let path = FsPathBuf::default().join(get_magisk_tmp()).join(LOG_PIPE);
unsafe {
libc::mkfifo(path.as_ptr(), 0o666);
libc::chown(path.as_ptr(), 0, 0);
let read = libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC);
let write = libc::open(path.as_ptr(), O_WRONLY | O_CLOEXEC);
*MAGISK_LOGD_FD.lock().unwrap() = SharedFd::from(OwnedFd::from_raw_fd(write));
*MAGISK_LOGD_FD.lock().unwrap() = Some(Arc::new(File::from_raw_fd(write)));
new_daemon_thread(logfile_writer, read as *mut c_void);
}
}

View File

@@ -86,7 +86,7 @@ bool dir_node::prepare() {
return upgrade_to_tmpfs;
}
void dir_node::collect_module_files(const char *module, int dfd) {
void dir_node::collect_module_files(std::string_view module, int dfd) {
auto dir = xopen_dir(xopenat(dfd, name().data(), O_RDONLY | O_CLOEXEC));
if (!dir)
return;
@@ -149,7 +149,9 @@ void module_node::mount() {
VLOGD("delete", "null", node_path().data());
return;
}
std::string path = module + (parent()->root()->prefix + node_path());
std::string path{module.begin(), module.end()};
path += parent()->root()->prefix;
path += node_path();
string mnt_src = module_mnt + path;
{
string src = MODULEROOT "/" + path;
@@ -231,10 +233,21 @@ private:
};
static void inject_magisk_bins(root_node *system) {
auto bin = system->get_child<inter_node>("bin");
dir_node* bin = system->get_child<inter_node>("bin");
if (!bin) {
bin = new inter_node("bin");
system->insert(bin);
struct stat st{};
bin = system;
for (auto &item: split(getenv("PATH"), ":")) {
item.erase(0, item.starts_with("/system/") ? 8 : 1);
auto system_path = "/system/" + item;
if (stat(system_path.data(), &st) == 0 && st.st_mode & S_IXOTH) {
for (const auto &dir: split(item, "/")) {
auto node = bin->get_child<inter_node>(dir);
bin = node ? node : bin->emplace<inter_node>(dir, dir.data());
}
break;
}
}
}
// Insert binaries
@@ -267,9 +280,7 @@ static void inject_zygisk_libs(root_node *system) {
}
}
vector<module_info> *module_list;
void load_modules() {
static void load_modules(bool zygisk_enabled, const rust::Vec<ModuleInfo> &module_list) {
node_entry::module_mnt = get_magisk_tmp() + "/"s MODULEMNT "/";
auto root = make_unique<root_node>("");
@@ -278,14 +289,14 @@ void load_modules() {
char buf[4096];
LOGI("* Loading modules\n");
for (const auto &m : *module_list) {
const char *module = m.name.data();
char *b = buf + ssprintf(buf, sizeof(buf), "%s/" MODULEMNT "/%s/", get_magisk_tmp(), module);
for (const auto &m : module_list) {
char *b = buf + ssprintf(buf, sizeof(buf), "%s/" MODULEMNT "/%.*s/",
get_magisk_tmp(), (int) m.name.size(), m.name.data());
// Read props
strcpy(b, "system.prop");
if (access(buf, F_OK) == 0) {
LOGI("%s: loading [system.prop]\n", module);
LOGI("%.*s: loading [system.prop]\n", (int) m.name.size(), m.name.data());
// Do NOT go through property service as it could cause boot lock
load_prop_file(buf, true);
}
@@ -300,10 +311,10 @@ void load_modules() {
if (access(buf, F_OK) != 0)
continue;
LOGI("%s: loading mount files\n", module);
LOGI("%.*s: loading mount files\n", (int) m.name.size(), m.name.data());
b[-1] = '\0';
int fd = xopen(buf, O_RDONLY | O_CLOEXEC);
system->collect_module_files(module, fd);
system->collect_module_files({ m.name.begin(), m.name.end() }, fd);
close(fd);
}
if (get_magisk_tmp() != "/sbin"sv || !str_contains(getenv("PATH") ?: "", "/sbin")) {
@@ -341,9 +352,6 @@ void load_modules() {
root->prepare();
root->mount();
}
// cleanup mounts
clean_mounts();
}
/************************
@@ -392,8 +400,9 @@ static void foreach_module(Func fn) {
}
}
static void collect_modules(bool open_zygisk) {
foreach_module([=](int dfd, dirent *entry, int modfd) {
static rust::Vec<ModuleInfo> collect_modules(bool zygisk_enabled, bool open_zygisk) {
rust::Vec<ModuleInfo> modules;
foreach_module([&](int dfd, dirent *entry, int modfd) {
if (faccessat(modfd, "remove", F_OK, 0) == 0) {
LOGI("%s: remove\n", entry->d_name);
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
@@ -407,7 +416,7 @@ static void collect_modules(bool open_zygisk) {
if (faccessat(modfd, "disable", F_OK, 0) == 0)
return;
module_info info;
ModuleInfo info{{}, -1, -1};
if (zygisk_enabled) {
// Riru and its modules are not compatible with zygisk
if (entry->d_name == "riru-core"sv || faccessat(modfd, "riru", F_OK, 0) == 0) {
@@ -417,11 +426,13 @@ static void collect_modules(bool open_zygisk) {
if (open_zygisk) {
#if defined(__arm__)
info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC);
info.z64 = -1;
#elif defined(__aarch64__)
info.z32 = openat(modfd, "zygisk/armeabi-v7a.so", O_RDONLY | O_CLOEXEC);
info.z64 = openat(modfd, "zygisk/arm64-v8a.so", O_RDONLY | O_CLOEXEC);
#elif defined(__i386__)
info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC);
info.z64 = -1;
#elif defined(__x86_64__)
info.z32 = openat(modfd, "zygisk/x86.so", O_RDONLY | O_CLOEXEC);
info.z64 = openat(modfd, "zygisk/x86_64.so", O_RDONLY | O_CLOEXEC);
@@ -441,7 +452,7 @@ static void collect_modules(bool open_zygisk) {
}
}
info.name = entry->d_name;
module_list->push_back(info);
modules.push_back(std::move(info));
});
if (zygisk_enabled) {
bool use_memfd = true;
@@ -461,23 +472,22 @@ static void collect_modules(bool open_zygisk) {
}
return fd;
};
std::for_each(module_list->begin(), module_list->end(), [&](module_info &info) {
std::for_each(modules.begin(),modules.end(), [&](ModuleInfo &info) {
info.z32 = convert_to_memfd(info.z32);
#if defined(__LP64__)
info.z64 = convert_to_memfd(info.z64);
#endif
});
}
return modules;
}
void handle_modules() {
rust::Vec<ModuleInfo> MagiskD::handle_modules() const noexcept {
bool zygisk = zygisk_enabled();
prepare_modules();
collect_modules(false);
exec_module_scripts("post-fs-data");
exec_module_scripts("post-fs-data", collect_modules(zygisk, false));
// Recollect modules (module scripts could remove itself)
module_list->clear();
collect_modules(true);
auto list = collect_modules(zygisk, true);
load_modules(zygisk, list);
return list;
}
static int check_rules_dir(char *buf, size_t sz) {
@@ -517,10 +527,3 @@ void remove_modules() {
});
rm_rf(MODULEROOT);
}
void exec_module_scripts(const char *stage) {
vector<string_view> module_names;
std::transform(module_list->begin(), module_list->end(), std::back_inserter(module_names),
[](const module_info &info) -> string_view { return info.name; });
exec_module_scripts(stage, module_names);
}

View File

@@ -8,8 +8,8 @@ use num_traits::AsPrimitive;
use base::libc::{c_uint, dev_t};
use base::{
cstr, debug, info, libc, parse_mount_info, raw_cstr, warn, FsPath, FsPathBuf, LibcReturn,
LoggedResult, MountInfo, ResultExt, Utf8CStr, Utf8CStrBufArr,
cstr, cstr_buf, debug, info, libc, parse_mount_info, raw_cstr, warn, FsPath, FsPathBuf,
LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr,
};
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
@@ -20,13 +20,9 @@ pub fn setup_mounts() {
info!("* Setup internal mounts");
let magisk_tmp = get_magisk_tmp();
let mut buf = Utf8CStrBufArr::default();
// Mount preinit directory
let mut dev_buf = Utf8CStrBufArr::<64>::new();
let dev_path = FsPathBuf::new(&mut dev_buf)
.join(magisk_tmp)
.join(PREINITDEV);
let dev_path = FsPathBuf::<64>::new().join(magisk_tmp).join(PREINITDEV);
let mut linked = false;
if let Ok(attr) = dev_path.get_attr() {
if attr.st.st_mode & libc::S_IFMT as c_uint == libc::S_IFBLK.as_() {
@@ -36,7 +32,7 @@ pub fn setup_mounts() {
// 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 = FsPathBuf::new(&mut buf).join(magisk_tmp).join(PREINITMIRR);
let mnt_path = FsPathBuf::default().join(magisk_tmp).join(PREINITMIRR);
for info in parse_mount_info("self") {
if info.root == "/" && info.device == preinit_dev {
if !info.fs_option.split(',').any(|s| s == "rw") {
@@ -49,7 +45,7 @@ pub fn setup_mounts() {
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
let r: LoggedResult<()> = try {
FsPath::from(preinit_dir).mkdir(0o700)?;
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr_buf::default();
if mnt_path.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
@@ -74,7 +70,7 @@ pub fn setup_mounts() {
}
// Bind remount module root to clear nosuid
let module_mnt = FsPathBuf::new(&mut buf).join(magisk_tmp).join(MODULEMNT);
let module_mnt = FsPathBuf::default().join(magisk_tmp).join(MODULEMNT);
let _: LoggedResult<()> = try {
module_mnt.mkdir(0o755)?;
unsafe {
@@ -101,20 +97,15 @@ pub fn setup_mounts() {
pub fn clean_mounts() {
let magisk_tmp = get_magisk_tmp();
let mut buf = Utf8CStrBufArr::default();
let module_mnt = FsPathBuf::new(&mut buf).join(magisk_tmp).join(MODULEMNT);
let mut module_mnt = FsPathBuf::default().join(magisk_tmp).join(MODULEMNT);
let _: LoggedResult<()> = try {
unsafe {
libc::umount2(
module_mnt.as_ptr(),
libc::MNT_DETACH,
)
.as_os_err()?;
libc::umount2(module_mnt.as_ptr(), libc::MNT_DETACH).as_os_err()?;
}
};
let worker_dir = FsPathBuf::new(&mut buf).join(magisk_tmp).join(WORKERDIR);
module_mnt.clear();
let worker_dir = module_mnt.join(magisk_tmp).join(WORKERDIR);
let _: LoggedResult<()> = try {
unsafe {
libc::mount(
@@ -125,11 +116,7 @@ pub fn clean_mounts() {
ptr::null(),
)
.as_os_err()?;
libc::umount2(
worker_dir.as_ptr(),
libc::MNT_DETACH,
)
.as_os_err()?;
libc::umount2(worker_dir.as_ptr(), libc::MNT_DETACH).as_os_err()?;
}
};
}
@@ -226,12 +213,11 @@ pub fn find_preinit_device() -> String {
&& let Ok(tmp) = std::env::var("MAGISKTMP")
&& !tmp.is_empty()
{
let mut buf = Utf8CStrBufArr::default();
let mirror_dir = FsPathBuf::new(&mut buf).join(&tmp).join(PREINITMIRR);
let mut mirror_dir = FsPathBuf::default().join(&tmp).join(PREINITMIRR);
let preinit_dir = FsPath::from(Utf8CStr::from_string(&mut preinit_dir));
let _: LoggedResult<()> = try {
preinit_dir.mkdirs(0o700)?;
let mut buf = Utf8CStrBufArr::default();
let mut buf = cstr_buf::default();
if mirror_dir.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
@@ -244,7 +230,8 @@ pub fn find_preinit_device() -> String {
}
};
if std::env::var_os("MAKEDEV").is_some() {
let dev_path = FsPathBuf::new(&mut buf).join(&tmp).join(PREINITDEV);
mirror_dir.clear();
let dev_path = mirror_dir.join(&tmp).join(PREINITDEV);
unsafe {
libc::mknod(
dev_path.as_ptr(),

View File

@@ -105,7 +105,7 @@ public:
**************/
// Traverse through module directories to generate a tree of module files
void collect_module_files(const char *module, int dfd);
void collect_module_files(std::string_view module, int dfd);
// Traverse through the real filesystem and prepare the tree for magic mount.
// Return true to indicate that this node needs to be upgraded to tmpfs_node.
@@ -279,16 +279,16 @@ public:
class module_node : public node_entry {
public:
module_node(const char *module, dirent *entry)
module_node(std::string_view module, dirent *entry)
: node_entry(entry->d_name, entry->d_type, this), module(module) {}
module_node(node_entry *node, const char *module) : node_entry(this), module(module) {
module_node(node_entry *node, std::string_view module) : node_entry(this), module(module) {
node_entry::consume(node);
}
void mount() override;
private:
const char *module;
std::string_view module;
};
// Don't create tmpfs_node before prepare

View File

@@ -1,12 +1,13 @@
use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE};
use crate::daemon::{to_app_id, MagiskD, AID_USER_OFFSET};
use crate::daemon::{to_app_id, MagiskD, AID_APP_END, AID_APP_START, AID_USER_OFFSET};
use crate::ffi::{get_magisk_tmp, install_apk, uninstall_pkg, DbEntryKey};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
use base::WalkResult::{Abort, Continue, Skip};
use base::{
cstr, error, fd_get_attr, open_fd, warn, BufReadExt, Directory, FsPath, FsPathBuf,
LoggedResult, ReadExt, ResultExt, Utf8CStrBuf, Utf8CStrBufArr,
cstr, cstr_buf, error, fd_get_attr, open_fd, warn, BufReadExt, Directory, FsPath, FsPathBuf,
LoggedResult, ReadExt, ResultExt, Utf8CStrBuf,
};
use bit_set::BitSet;
use cxx::CxxString;
use std::collections::BTreeMap;
use std::fs::File;
@@ -51,7 +52,7 @@ macro_rules! bad_apk {
* within the APK v2 signature block.
*/
fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
fn inner(apk: &mut File, version: i32) -> io::Result<Vec<u8>> {
let res: io::Result<Vec<u8>> = try {
let mut u32_val = 0u32;
let mut u64_val = 0u64;
@@ -70,7 +71,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
}
}
if i == 0xffff {
return Err(bad_apk!("invalid APK format"));
Err(bad_apk!("invalid APK format"))?;
}
}
@@ -97,7 +98,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
}
});
if version > apk_ver {
return Err(bad_apk!("APK version too low"));
Err(bad_apk!("APK version too low"))?;
}
}
@@ -107,7 +108,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
let mut magic = [0u8; 16];
apk.read_exact(&mut magic)?;
if magic != APK_SIGNING_BLOCK_MAGIC {
return Err(bad_apk!("invalid signing block magic"));
Err(bad_apk!("invalid signing block magic"))?;
}
let mut signing_blk_sz = 0u64;
apk.seek(SeekFrom::Current(
@@ -115,14 +116,14 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
))?;
apk.read_pod(&mut signing_blk_sz)?;
if signing_blk_sz != u64_val {
return Err(bad_apk!("invalid signing block size"));
Err(bad_apk!("invalid signing block size"))?;
}
// Finally, we are now at the beginning of the id-value pair sequence
loop {
apk.read_pod(&mut u64_val)?; // id-value pair length
if u64_val == signing_blk_sz {
break;
Err(bad_apk!("cannot find certificate"))?;
}
let mut id = 0u32;
@@ -139,7 +140,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
let mut cert = vec![0; u32_val as usize];
apk.read_exact(cert.as_mut())?;
return Ok(cert);
break cert;
} else {
// Skip this id-value pair
apk.seek(SeekFrom::Current(
@@ -147,10 +148,8 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
))?;
}
}
Err(bad_apk!("cannot find certificate"))
}
inner(apk, version).log().unwrap_or(vec![])
};
res.log().unwrap_or(vec![])
}
fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
@@ -158,7 +157,7 @@ fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
if !e.is_dir() {
return Ok(Skip);
}
let name_bytes = e.d_name().to_bytes();
let name_bytes = e.name().to_bytes();
if name_bytes.starts_with(pkg.as_bytes()) && name_bytes[pkg.len()] == b'-' {
// Found the APK path, we can abort now
e.path(buf)?;
@@ -240,8 +239,7 @@ impl TrackedFile {
impl ManagerInfo {
fn check_dyn(&mut self, daemon: &MagiskD, user: i32, pkg: &str) -> Status {
let mut arr = Utf8CStrBufArr::default();
let apk = FsPathBuf::new(&mut arr)
let apk = FsPathBuf::default()
.join(daemon.app_data_dir())
.join_fmt(user)
.join(pkg)
@@ -275,7 +273,7 @@ impl ManagerInfo {
}
fn check_stub(&mut self, user: i32, pkg: &str) -> Status {
let mut arr = Utf8CStrBufArr::default();
let mut arr = cstr_buf::default();
if find_apk_path(pkg, &mut arr).is_err() {
return Status::NotInstalled;
}
@@ -300,7 +298,7 @@ impl ManagerInfo {
}
fn check_orig(&mut self, user: i32) -> Status {
let mut arr = Utf8CStrBufArr::default();
let mut arr = cstr_buf::default();
if find_apk_path(APP_PACKAGE_NAME, &mut arr).is_err() {
return Status::NotInstalled;
}
@@ -445,8 +443,7 @@ impl ManagerInfo {
impl MagiskD {
fn get_package_uid(&self, user: i32, pkg: &str) -> i32 {
let mut arr = Utf8CStrBufArr::default();
let path = FsPathBuf::new(&mut arr)
let path = FsPathBuf::default()
.join(self.app_data_dir())
.join_fmt(user)
.join(pkg);
@@ -458,29 +455,80 @@ impl MagiskD {
pub fn preserve_stub_apk(&self) {
let mut info = self.manager_info.lock().unwrap();
let mut arr = Utf8CStrBufArr::default();
let apk = FsPathBuf::new(&mut arr)
.join(get_magisk_tmp())
.join("stub.apk");
let apk = FsPathBuf::default().join(get_magisk_tmp()).join("stub.apk");
if let Ok(mut fd) = apk.open(O_RDONLY | O_CLOEXEC) {
info.trusted_cert = read_certificate(&mut fd, MAGISK_VER_CODE);
// Seek the fd back to start
fd.seek(SeekFrom::Start(0)).log().ok();
fd.seek(SeekFrom::Start(0)).log_ok();
info.stub_apk_fd = Some(fd);
}
apk.remove().log().ok();
apk.remove().log_ok();
}
pub fn get_manager_uid(&self, user: i32) -> i32 {
let mut info = self.manager_info.lock().unwrap();
let (uid, _) = info.get_manager(self, user, false);
uid
}
pub fn get_manager(&self, user: i32, install: bool) -> (i32, String) {
let mut info = self.manager_info.lock().unwrap();
let (uid, pkg) = info.get_manager(self, user, install);
(uid, pkg.to_string())
}
pub fn ensure_manager(&self) {
let mut info = self.manager_info.lock().unwrap();
let _ = info.get_manager(self, 0, true);
}
pub unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32 {
let mut info = self.manager_info.lock().unwrap();
let (uid, pkg) = info.get_manager(self, user, install);
if let Some(str) = ptr.as_mut() {
let mut str = Pin::new_unchecked(str);
str.as_mut().clear();
str.push_str(pkg);
unsafe {
let mut info = self.manager_info.lock().unwrap();
let (uid, pkg) = info.get_manager(self, user, install);
if let Some(str) = ptr.as_mut() {
let mut str = Pin::new_unchecked(str);
str.as_mut().clear();
str.push_str(pkg);
}
uid
}
uid
}
// app_id = app_no + AID_APP_START
// app_no range: [0, 9999]
pub fn get_app_no_list(&self) -> BitSet {
let mut list = BitSet::new();
let _: LoggedResult<()> = try {
let mut app_data_dir = Directory::open(self.app_data_dir())?;
// For each user
loop {
let entry = match app_data_dir.read()? {
None => break,
Some(e) => e,
};
let mut user_dir = match entry.open_as_dir() {
Err(_) => continue,
Ok(dir) => dir,
};
// For each package
loop {
match user_dir.read()? {
None => break,
Some(e) => {
let attr = e.get_attr()?;
let app_id = to_app_id(attr.st.st_uid as i32);
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
let app_no = app_id - AID_APP_START;
list.insert(app_no as usize);
}
}
}
}
}
};
list
}
}

View File

@@ -9,28 +9,19 @@ use std::{
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{
clone_attr, cstr, debug, libc::mkstemp, Directory, FsPath, FsPathBuf, LibcReturn, LoggedResult,
MappedFile, SilentResultExt, Utf8CStr, Utf8CStrBufArr, WalkResult,
};
use crate::ffi::{prop_cb_exec, PropCb};
use crate::resetprop::proto::persistent_properties::{
mod_PersistentProperties::PersistentPropertyRecord, PersistentProperties,
};
use base::const_format::concatcp;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{
clone_attr, cstr, debug, libc::mkstemp, path, Directory, FsPathBuf, LibcReturn, LoggedResult,
MappedFile, SilentResultExt, Utf8CStr, WalkResult,
};
macro_rules! PERSIST_PROP_DIR {
() => {
"/data/property"
};
}
macro_rules! PERSIST_PROP {
() => {
concat!(PERSIST_PROP_DIR!(), "/persistent_properties")
};
}
const PERSIST_PROP_DIR: &str = "/data/property";
const PERSIST_PROP: &str = concatcp!(PERSIST_PROP_DIR, "/persistent_properties");
trait PropCbExec {
fn exec(&mut self, name: &Utf8CStr, value: &Utf8CStr);
@@ -73,14 +64,11 @@ impl PropExt for PersistentProperties {
}
fn check_proto() -> bool {
FsPath::from(cstr!(PERSIST_PROP!())).exists()
path!(PERSIST_PROP).exists()
}
fn file_get_prop(name: &Utf8CStr) -> LoggedResult<String> {
let mut buf = Utf8CStrBufArr::default();
let path = FsPathBuf::new(&mut buf)
.join(PERSIST_PROP_DIR!())
.join(name);
let path = FsPathBuf::default().join(PERSIST_PROP_DIR).join(name);
let mut file = path.open(O_RDONLY | O_CLOEXEC).silent()?;
debug!("resetprop: read prop from [{}]", path);
let mut s = String::new();
@@ -89,14 +77,10 @@ fn file_get_prop(name: &Utf8CStr) -> LoggedResult<String> {
}
fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()> {
let mut buf = Utf8CStrBufArr::default();
let path = FsPathBuf::new(&mut buf)
.join(PERSIST_PROP_DIR!())
.join(name);
let path = FsPathBuf::default().join(PERSIST_PROP_DIR).join(name);
if let Some(value) = value {
let mut buf = Utf8CStrBufArr::default();
let mut tmp = FsPathBuf::new(&mut buf)
.join(PERSIST_PROP_DIR!())
let mut tmp = FsPathBuf::default()
.join(PERSIST_PROP_DIR)
.join("prop.XXXXXX");
{
let mut f = unsafe {
@@ -115,8 +99,8 @@ fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()>
}
fn proto_read_props() -> LoggedResult<PersistentProperties> {
debug!("resetprop: decode with protobuf [{}]", PERSIST_PROP!());
let m = MappedFile::open(cstr!(PERSIST_PROP!()))?;
debug!("resetprop: decode with protobuf [{}]", PERSIST_PROP);
let m = MappedFile::open(cstr!(PERSIST_PROP))?;
let m = m.as_ref();
let mut r = BytesReader::from_bytes(m);
let mut props = PersistentProperties::from_reader(&mut r, m)?;
@@ -126,8 +110,7 @@ fn proto_read_props() -> LoggedResult<PersistentProperties> {
}
fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
let mut buf = Utf8CStrBufArr::default();
let mut tmp = FsPathBuf::new(&mut buf).join(concat!(PERSIST_PROP!(), ".XXXXXX"));
let mut tmp = FsPathBuf::default().join(concatcp!(PERSIST_PROP, ".XXXXXX"));
{
let f = unsafe {
let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?;
@@ -136,19 +119,19 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
debug!("resetprop: encode with protobuf [{}]", tmp);
props.write_message(&mut Writer::new(BufWriter::new(f)))?;
}
clone_attr(FsPath::from(cstr!(PERSIST_PROP!())), &tmp)?;
tmp.rename_to(cstr!(PERSIST_PROP!()))?;
clone_attr(path!(PERSIST_PROP), &tmp)?;
tmp.rename_to(cstr!(PERSIST_PROP))?;
Ok(())
}
pub unsafe fn persist_get_prop(name: &Utf8CStr, prop_cb: Pin<&mut PropCb>) {
fn inner(name: &Utf8CStr, mut prop_cb: Pin<&mut PropCb>) -> LoggedResult<()> {
pub fn persist_get_prop(name: &Utf8CStr, mut prop_cb: Pin<&mut PropCb>) {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
let prop = props.find(name)?;
if let PersistentPropertyRecord {
name: Some(ref mut n),
value: Some(ref mut v),
name: Some(n),
value: Some(v),
} = prop
{
prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v));
@@ -158,29 +141,28 @@ pub unsafe fn persist_get_prop(name: &Utf8CStr, prop_cb: Pin<&mut PropCb>) {
prop_cb.exec(name, Utf8CStr::from_string(&mut value));
debug!("resetprop: found prop [{}] = [{}]", name, value);
}
Ok(())
}
inner(name, prop_cb).ok();
};
res.ok();
}
pub unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>) {
fn inner(mut prop_cb: Pin<&mut PropCb>) -> LoggedResult<()> {
pub fn persist_get_props(mut prop_cb: Pin<&mut PropCb>) {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
props.iter_mut().for_each(|prop| {
if let PersistentPropertyRecord {
name: Some(ref mut n),
value: Some(ref mut v),
name: Some(n),
value: Some(v),
} = prop
{
prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v));
}
});
} else {
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR!()))?;
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
dir.pre_order_walk(|e| {
if e.is_file() {
if let Ok(name) = Utf8CStr::from_cstr(e.d_name()) {
if let Ok(name) = Utf8CStr::from_cstr(e.name()) {
if let Ok(mut value) = file_get_prop(name) {
prop_cb.exec(name, Utf8CStr::from_string(&mut value));
}
@@ -190,27 +172,26 @@ pub unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>) {
Ok(WalkResult::Skip)
})?;
}
Ok(())
}
inner(prop_cb).ok();
};
res.ok();
}
pub unsafe fn persist_delete_prop(name: &Utf8CStr) -> bool {
fn inner(name: &Utf8CStr) -> LoggedResult<()> {
pub fn persist_delete_prop(name: &Utf8CStr) -> bool {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
let idx = props.find_index(name).silent()?;
props.remove(idx);
proto_write_props(&props)
proto_write_props(&props)?;
} else {
file_set_prop(name, None)
file_set_prop(name, None)?;
}
}
inner(name).is_ok()
};
res.is_ok()
}
pub unsafe fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool {
unsafe fn inner(name: &Utf8CStr, value: &Utf8CStr) -> LoggedResult<()> {
pub fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool {
let res: LoggedResult<()> = try {
if check_proto() {
let mut props = proto_read_props()?;
match props.find_index(name) {
@@ -223,10 +204,10 @@ pub unsafe fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool {
},
),
}
proto_write_props(&props)
proto_write_props(&props)?;
} else {
file_set_prop(name, Some(value))
file_set_prop(name, Some(value))?;
}
}
inner(name, value).is_ok()
};
res.is_ok()
}

View File

@@ -26,7 +26,7 @@ static void set_script_env() {
char new_path[4096];
ssprintf(new_path, sizeof(new_path), "%s:%s", getenv("PATH"), get_magisk_tmp());
setenv("PATH", new_path, 1);
if (zygisk_enabled)
if (MagiskD::Get().zygisk_enabled())
setenv("ZYGISK_ENABLED", "1", 1);
};
@@ -75,10 +75,10 @@ if (pfs) { \
exit(0); \
}
void exec_common_scripts(const char *stage) {
LOGI("* Running %s.d scripts\n", stage);
void exec_common_scripts(rust::Utf8CStr stage) {
LOGI("* Running %s.d scripts\n", stage.c_str());
char path[4096];
char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage);
char *name = path + sprintf(path, SECURE_DIR "/%s.d", stage.c_str());
auto dir = xopen_dir(path);
if (!dir) return;
@@ -97,7 +97,7 @@ void exec_common_scripts(const char *stage) {
if (entry->d_type == DT_REG) {
if (faccessat(dfd, entry->d_name, X_OK, 0) != 0)
continue;
LOGI("%s.d: exec [%s]\n", stage, entry->d_name);
LOGI("%s.d: exec [%s]\n", stage.c_str(), entry->d_name);
strcpy(name, entry->d_name);
exec_t exec {
.pre_exec = set_script_env,
@@ -117,12 +117,12 @@ static bool operator>(const timespec &a, const timespec &b) {
return a.tv_nsec > b.tv_nsec;
}
void exec_module_scripts(const char *stage, const vector<string_view> &modules) {
LOGI("* Running module %s scripts\n", stage);
if (modules.empty())
void exec_module_scripts(rust::Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list) {
LOGI("* Running module %s scripts\n", stage.c_str());
if (module_list.empty())
return;
bool pfs = stage == "post-fs-data"sv;
bool pfs = (string_view) stage == "post-fs-data";
if (pfs) {
timespec now{};
clock_gettime(CLOCK_MONOTONIC, &now);
@@ -134,12 +134,11 @@ void exec_module_scripts(const char *stage, const vector<string_view> &modules)
PFS_SETUP()
char path[4096];
for (auto &m : modules) {
const char *module = m.data();
sprintf(path, MODULEROOT "/%s/%s.sh", module, stage);
for (auto &m : module_list) {
sprintf(path, MODULEROOT "/%.*s/%s.sh", (int) m.name.size(), m.name.data(), stage.c_str());
if (access(path, F_OK) == -1)
continue;
LOGI("%s: exec [%s.sh]\n", module, stage);
LOGI("%.*s: exec [%s.sh]\n", (int) m.name.size(), m.name.data(), stage.c_str());
exec_t exec {
.pre_exec = set_script_env,
.fork = pfs ? xfork : fork_dont_care

View File

@@ -1,188 +0,0 @@
#include <fcntl.h>
#include <endian.h>
#include <socket.hpp>
#include <base.hpp>
using namespace std;
bool get_client_cred(int fd, sock_cred *cred) {
socklen_t len = sizeof(ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
return false;
char buf[4096];
len = sizeof(buf);
if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &len) != 0)
len = 0;
buf[len] = '\0';
cred->context = buf;
return true;
}
static int send_fds(int sockfd, void *cmsgbuf, size_t bufsz, const int *fds, int cnt) {
iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt),
};
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
};
if (cnt) {
msg.msg_control = cmsgbuf;
msg.msg_controllen = bufsz;
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * cnt);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * cnt);
}
return xsendmsg(sockfd, &msg, 0);
}
int send_fds(int sockfd, const int *fds, int cnt) {
if (cnt == 0) {
return send_fds(sockfd, nullptr, 0, nullptr, 0);
}
vector<char> cmsgbuf;
cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt));
return send_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), fds, cnt);
}
int send_fd(int sockfd, int fd) {
if (fd < 0) {
return send_fds(sockfd, nullptr, 0, nullptr, 0);
}
char cmsgbuf[CMSG_SPACE(sizeof(int))];
return send_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), &fd, 1);
}
static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) {
iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt),
};
msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cmsgbuf,
.msg_controllen = bufsz
};
xrecvmsg(sockfd, &msg, MSG_WAITALL);
if (msg.msg_controllen != bufsz) {
LOGE("recv_fd: msg_flags = %d, msg_controllen(%zu) != %zu\n",
msg.msg_flags, msg.msg_controllen, bufsz);
return nullptr;
}
cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg == nullptr) {
LOGE("recv_fd: cmsg == nullptr\n");
return nullptr;
}
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt)) {
LOGE("recv_fd: cmsg_len(%zu) != %zu\n", cmsg->cmsg_len, CMSG_LEN(sizeof(int) * cnt));
return nullptr;
}
if (cmsg->cmsg_level != SOL_SOCKET) {
LOGE("recv_fd: cmsg_level != SOL_SOCKET\n");
return nullptr;
}
if (cmsg->cmsg_type != SCM_RIGHTS) {
LOGE("recv_fd: cmsg_type != SCM_RIGHTS\n");
return nullptr;
}
return CMSG_DATA(cmsg);
}
vector<int> recv_fds(int sockfd) {
vector<int> results;
// Peek fd count to allocate proper buffer
int cnt;
recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK);
if (cnt == 0) {
// Consume data
recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL);
return results;
}
vector<char> cmsgbuf;
cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt));
void *data = recv_fds(sockfd, cmsgbuf.data(), cmsgbuf.size(), cnt);
if (data == nullptr)
return results;
results.resize(cnt);
memcpy(results.data(), data, sizeof(int) * cnt);
return results;
}
int recv_fd(int sockfd) {
// Peek fd count
int cnt;
recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK);
if (cnt == 0) {
// Consume data
recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL);
return -1;
}
char cmsgbuf[CMSG_SPACE(sizeof(int))];
void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
if (data == nullptr)
return -1;
int result;
memcpy(&result, data, sizeof(int));
return result;
}
int read_int(int fd) {
int val;
if (xxread(fd, &val, sizeof(val)) != sizeof(val))
return -1;
return val;
}
int read_int_be(int fd) {
return ntohl(read_int(fd));
}
void write_int(int fd, int val) {
if (fd < 0) return;
xwrite(fd, &val, sizeof(val));
}
void write_int_be(int fd, int val) {
write_int(fd, htonl(val));
}
bool read_string(int fd, std::string &str) {
int len = read_int(fd);
str.clear();
if (len < 0)
return false;
str.resize(len);
return xxread(fd, str.data(), len) == len;
}
string read_string(int fd) {
string str;
read_string(fd, str);
return str;
}
void write_string(int fd, string_view str) {
if (fd < 0) return;
write_int(fd, str.size());
xwrite(fd, str.data(), str.size());
}

257
native/src/core/socket.rs Normal file
View File

@@ -0,0 +1,257 @@
use base::{libc, warn, ReadExt, ResultExt, WriteExt};
use bytemuck::{bytes_of, bytes_of_mut, Zeroable};
use std::io;
use std::io::{ErrorKind, IoSlice, IoSliceMut, Read, Write};
use std::mem::ManuallyDrop;
use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::os::unix::net::{AncillaryData, SocketAncillary, UnixStream};
pub trait Encodable {
fn encoded_len(&self) -> usize;
fn encode(&self, w: &mut impl Write) -> io::Result<()>;
}
pub trait Decodable: Sized + Encodable {
fn decode(r: &mut impl Read) -> io::Result<Self>;
}
macro_rules! impl_pod_encodable {
($($t:ty)*) => ($(
impl Encodable for $t {
#[inline(always)]
fn encoded_len(&self) -> usize {
size_of::<Self>()
}
#[inline(always)]
fn encode(&self, w: &mut impl Write) -> io::Result<()> {
w.write_pod(self)
}
}
impl Decodable for $t {
#[inline(always)]
fn decode(r: &mut impl Read) -> io::Result<Self> {
let mut val = Self::zeroed();
r.read_pod(&mut val)?;
Ok(val)
}
}
)*)
}
impl_pod_encodable! { u8 u32 i32 usize }
impl Encodable for bool {
#[inline(always)]
fn encoded_len(&self) -> usize {
size_of::<u8>()
}
#[inline(always)]
fn encode(&self, w: &mut impl Write) -> io::Result<()> {
match *self {
true => 1u8.encode(w),
false => 0u8.encode(w),
}
}
}
impl Decodable for bool {
#[inline(always)]
fn decode(r: &mut impl Read) -> io::Result<Self> {
Ok(u8::decode(r)? != 0)
}
}
impl<T: Decodable> Encodable for Vec<T> {
fn encoded_len(&self) -> usize {
size_of::<i32>() + size_of::<T>() * self.len()
}
fn encode(&self, w: &mut impl Write) -> io::Result<()> {
(self.len() as i32).encode(w)?;
self.iter().try_for_each(|e| e.encode(w))
}
}
impl<T: Decodable> Decodable for Vec<T> {
fn decode(r: &mut impl Read) -> io::Result<Self> {
let len = i32::decode(r)?;
let mut val = Vec::with_capacity(len as usize);
for _ in 0..len {
val.push(T::decode(r)?);
}
Ok(val)
}
}
impl Encodable for str {
fn encoded_len(&self) -> usize {
size_of::<i32>() + self.len()
}
fn encode(&self, w: &mut impl Write) -> io::Result<()> {
(self.len() as i32).encode(w)?;
w.write_all(self.as_bytes())
}
}
impl Encodable for String {
fn encoded_len(&self) -> usize {
self.as_str().encoded_len()
}
fn encode(&self, w: &mut impl Write) -> io::Result<()> {
self.as_str().encode(w)
}
}
impl Decodable for String {
fn decode(r: &mut impl Read) -> io::Result<String> {
let len = i32::decode(r)?;
let mut val = String::with_capacity(len as usize);
r.take(len as u64).read_to_string(&mut val)?;
Ok(val)
}
}
pub trait IpcRead {
fn read_decodable<E: Decodable>(&mut self) -> io::Result<E>;
}
impl<T: Read> IpcRead for T {
#[inline(always)]
fn read_decodable<E: Decodable>(&mut self) -> io::Result<E> {
E::decode(self)
}
}
pub trait IpcWrite {
fn write_encodable<E: Encodable + ?Sized>(&mut self, val: &E) -> io::Result<()>;
}
impl<T: Write> IpcWrite for T {
#[inline(always)]
fn write_encodable<E: Encodable + ?Sized>(&mut self, val: &E) -> io::Result<()> {
val.encode(self)
}
}
pub trait UnixSocketExt {
fn send_fds(&mut self, fd: &[RawFd]) -> io::Result<()>;
fn recv_fd(&mut self) -> io::Result<Option<OwnedFd>>;
fn recv_fds(&mut self) -> io::Result<Vec<OwnedFd>>;
}
impl UnixSocketExt for UnixStream {
fn send_fds(&mut self, fds: &[RawFd]) -> io::Result<()> {
match fds.len() {
0 => self.write_pod(&0)?,
len => {
// 4k buffer is reasonable enough
let mut buf = [0u8; 4096];
let mut ancillary = SocketAncillary::new(&mut buf);
if !ancillary.add_fds(fds) {
return Err(ErrorKind::OutOfMemory.into());
}
let fd_count = len as i32;
let iov = IoSlice::new(bytes_of(&fd_count));
self.send_vectored_with_ancillary(&[iov], &mut ancillary)?;
}
};
Ok(())
}
fn recv_fd(&mut self) -> io::Result<Option<OwnedFd>> {
let mut fd_count = 0;
self.peek(bytes_of_mut(&mut fd_count))?;
if fd_count < 1 {
// Actually consume the data
self.read_pod(&mut fd_count)?;
return Ok(None);
}
if fd_count > 1 {
warn!(
"Received unexpected number of fds: expected=1 actual={}",
fd_count
);
}
// 4k buffer is reasonable enough
let mut buf = [0u8; 4096];
let mut ancillary = SocketAncillary::new(&mut buf);
let iov = IoSliceMut::new(bytes_of_mut(&mut fd_count));
self.recv_vectored_with_ancillary(&mut [iov], &mut ancillary)?;
for msg in ancillary.messages().flatten() {
if let AncillaryData::ScmRights(mut scm_rights) = msg {
// We only want the first one
let fd = if let Some(fd) = scm_rights.next() {
unsafe { OwnedFd::from_raw_fd(fd) }
} else {
return Ok(None);
};
// Close all others
for fd in scm_rights {
unsafe { libc::close(fd) };
}
return Ok(Some(fd));
}
}
Ok(None)
}
fn recv_fds(&mut self) -> io::Result<Vec<OwnedFd>> {
let mut fd_count = 0;
// 4k buffer is reasonable enough
let mut buf = [0u8; 4096];
let mut ancillary = SocketAncillary::new(&mut buf);
let iov = IoSliceMut::new(bytes_of_mut(&mut fd_count));
self.recv_vectored_with_ancillary(&mut [iov], &mut ancillary)?;
let mut fds: Vec<OwnedFd> = Vec::new();
for msg in ancillary.messages().flatten() {
if let AncillaryData::ScmRights(scm_rights) = msg {
fds = scm_rights
.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })
.collect();
}
}
if fd_count as usize != fds.len() {
warn!(
"Received unexpected number of fds: expected={} actual={}",
fd_count,
fds.len()
);
}
Ok(fds)
}
}
pub fn send_fd(socket: RawFd, fd: RawFd) -> bool {
let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });
if fd < 0 {
socket.send_fds(&[]).log().is_ok()
} else {
socket.send_fds(&[fd]).log().is_ok()
}
}
pub fn send_fds(socket: RawFd, fds: &[RawFd]) -> bool {
let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });
socket.send_fds(fds).log().is_ok()
}
pub fn recv_fd(socket: RawFd) -> RawFd {
let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });
socket
.recv_fd()
.log()
.unwrap_or(None)
.map_or(-1, IntoRawFd::into_raw_fd)
}
pub fn recv_fds(socket: RawFd) -> Vec<RawFd> {
let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });
let fds = socket.recv_fds().log().unwrap_or(Vec::new());
// SAFETY: OwnedFd and RawFd has the same layout
unsafe { std::mem::transmute(fds) }
}

View File

@@ -165,15 +165,17 @@ int DbStatement::bind_text(int index, rust::Str val) {
return sqlite3_bind_text(reinterpret_cast<sqlite3_stmt*>(this), index, val.data(), val.size(), nullptr);
}
#define sql_chk_log(fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) { \
LOGE("sqlite3(line:%d): %s\n", __LINE__, sqlite3_errstr(rc)); \
return false; \
#define sql_chk_log_ret(ret, fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) { \
LOGE("sqlite3(line:%d): %s\n", __LINE__, sqlite3_errstr(rc)); \
return ret; \
}
static bool open_and_init_db_impl(sqlite3 **dbOut) {
#define sql_chk_log(fn, ...) sql_chk_log_ret(nullptr, fn, __VA_ARGS__)
sqlite3 *open_and_init_db() {
if (!load_sqlite()) {
LOGE("sqlite3: Cannot load libsqlite.so\n");
return false;
return nullptr;
}
unique_ptr<sqlite3, decltype(sqlite3_close)> db(nullptr, sqlite3_close);
@@ -192,9 +194,10 @@ static bool open_and_init_db_impl(sqlite3 **dbOut) {
};
sql_chk_log(sql_exec_impl, db.get(), "PRAGMA user_version", nullptr, nullptr, ver_cb, &ver);
if (ver > DB_VERSION) {
// Don't support downgrading database
// Don't support downgrading database, delete and retry
LOGE("sqlite3: Downgrading database is not supported\n");
return false;
unlink(MAGISKDB);
return open_and_init_db();
}
auto create_policy = [&] {
@@ -298,13 +301,7 @@ static bool open_and_init_db_impl(sqlite3 **dbOut) {
sql_chk_log(sql_exec_impl, db.get(), "PRAGMA user_version=" DB_VERSION_STR);
}
*dbOut = db.release();
return true;
}
sqlite3 *open_and_init_db() {
sqlite3 *db = nullptr;
return open_and_init_db_impl(&db) ? db : nullptr;
return db.release();
}
// Exported from Rust
@@ -332,7 +329,7 @@ bool db_exec(const char *sql, DbArgs args, db_exec_callback exec_fn) {
fn->operator()(columns, values);
};
}
sql_chk_log(sql_exec_rs, sql, bind_cb, &bind_fn, exec_cb, &exec_fn);
sql_chk_log_ret(false, sql_exec_rs, sql, bind_cb, &bind_fn, exec_cb, &exec_fn);
return true;
}

View File

@@ -4,8 +4,7 @@
#include <base.hpp>
#include <selinux.hpp>
#include <consts.hpp>
#include "su.hpp"
#include <core.hpp>
using namespace std;
@@ -21,11 +20,6 @@ using namespace std;
// 0x18800020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
// FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES
#define get_cmd(to) \
((to).command.empty() ? \
((to).shell.empty() ? DEFAULT_SHELL : (to).shell.data()) : \
(to).command.data())
class Extra {
const char *key;
enum {
@@ -128,14 +122,15 @@ static bool check_no_error(int fd) {
}
static void exec_cmd(const char *action, vector<Extra> &data,
const shared_ptr<su_info> &info, bool provider = true) {
const SuAppRequest &info, bool provider = true) {
char target[128];
char user[4];
ssprintf(user, sizeof(user), "%d", to_user_id(info->eval_uid));
ssprintf(user, sizeof(user), "%d", to_user_id(info.eval_uid));
// First try content provider call method
if (provider) {
ssprintf(target, sizeof(target), "content://%s.provider", info->mgr_pkg.data());
ssprintf(target, sizeof(target), "content://%.*s.provider",
(int) info.mgr_pkg.size(), info.mgr_pkg.data());
vector<const char *> args{ CALL_PROVIDER };
for (auto &e : data) {
e.add_bind(args);
@@ -153,7 +148,7 @@ static void exec_cmd(const char *action, vector<Extra> &data,
}
// Then try start activity with package name
strscpy(target, info->mgr_pkg.data(), sizeof(target));
ssprintf(target, sizeof(target), "%.*s", (int) info.mgr_pkg.size(), info.mgr_pkg.data());
vector<const char *> args{ START_ACTIVITY };
for (auto &e : data) {
e.add_intent(args);
@@ -168,53 +163,58 @@ static void exec_cmd(const char *action, vector<Extra> &data,
exec_command(exec);
}
void app_log(const su_context &ctx) {
void app_log(const SuAppRequest &info, SuPolicy policy, bool notify) {
if (fork_dont_care() == 0) {
string context = (string) info.request.context;
string command = info.request.command.empty()
? (string) info.request.shell
: (string) info.request.command;
vector<Extra> extras;
extras.reserve(9);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("to.uid", static_cast<int>(ctx.req.uid));
extras.emplace_back("pid", ctx.pid);
extras.emplace_back("policy", +ctx.info->access.policy);
extras.emplace_back("target", ctx.req.target);
extras.emplace_back("context", ctx.req.context.data());
extras.emplace_back("gids", &ctx.req.gids);
extras.emplace_back("command", get_cmd(ctx.req));
extras.emplace_back("notify", (bool) ctx.info->access.notify);
extras.emplace_back("from.uid", info.uid);
extras.emplace_back("to.uid", info.request.target_uid);
extras.emplace_back("pid", info.pid);
extras.emplace_back("policy", +policy);
extras.emplace_back("target", info.request.target_pid);
extras.emplace_back("context", context.data());
extras.emplace_back("gids", &info.request.gids);
extras.emplace_back("command", command.data());
extras.emplace_back("notify", notify);
exec_cmd("log", extras, ctx.info);
exec_cmd("log", extras, info);
exit(0);
}
}
void app_notify(const su_context &ctx) {
void app_notify(const SuAppRequest &info, SuPolicy policy) {
if (fork_dont_care() == 0) {
vector<Extra> extras;
extras.reserve(3);
extras.emplace_back("from.uid", ctx.info->uid);
extras.emplace_back("pid", ctx.pid);
extras.emplace_back("policy", +ctx.info->access.policy);
extras.emplace_back("from.uid", info.uid);
extras.emplace_back("pid", info.pid);
extras.emplace_back("policy", +policy);
exec_cmd("notify", extras, ctx.info);
exec_cmd("notify", extras, info);
exit(0);
}
}
int app_request(const su_context &ctx) {
int app_request(const SuAppRequest &info) {
// Create FIFO
char fifo[64];
ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), ctx.pid);
ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), info.pid);
mkfifo(fifo, 0600);
chown(fifo, ctx.info->mgr_uid, ctx.info->mgr_uid);
chown(fifo, info.mgr_uid, info.mgr_uid);
setfilecon(fifo, MAGISK_FILE_CON);
// Send request
vector<Extra> extras;
extras.reserve(3);
extras.emplace_back("fifo", fifo);
extras.emplace_back("uid", ctx.info->eval_uid);
extras.emplace_back("pid", ctx.pid);
exec_cmd("request", extras, ctx.info, false);
extras.emplace_back("uid", info.eval_uid);
extras.emplace_back("pid", info.pid);
exec_cmd("request", extras, info, false);
// Wait for data input for at most 70 seconds
// Open with O_RDWR to prevent FIFO open block

View File

@@ -0,0 +1,299 @@
use crate::daemon::{to_app_id, to_user_id, MagiskD, AID_ROOT, AID_SHELL};
use crate::db::{DbSettings, MultiuserMode, RootAccess};
use crate::ffi::{
app_log, app_notify, app_request, exec_root_shell, SuAppRequest, SuPolicy, SuRequest,
};
use crate::socket::IpcRead;
use crate::su::db::RootSettings;
use crate::UCred;
use base::{debug, error, exit_on_error, libc, warn, LoggedResult, ResultExt, WriteExt};
use std::fs::File;
use std::os::fd::{FromRawFd, IntoRawFd};
use std::os::unix::net::UnixStream;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
const DEFAULT_SHELL: &str = "/system/bin/sh";
impl Default for SuRequest {
fn default() -> Self {
SuRequest {
target_uid: AID_ROOT,
target_pid: -1,
login: false,
keep_env: false,
shell: DEFAULT_SHELL.to_string(),
command: "".to_string(),
context: "".to_string(),
gids: vec![],
}
}
}
pub struct SuInfo {
uid: i32,
eval_uid: i32,
cfg: DbSettings,
mgr_pkg: String,
mgr_uid: i32,
access: Mutex<AccessInfo>,
}
struct AccessInfo {
settings: RootSettings,
timestamp: Instant,
}
impl Default for SuInfo {
fn default() -> Self {
SuInfo {
uid: -1,
eval_uid: -1,
cfg: Default::default(),
mgr_pkg: Default::default(),
mgr_uid: -1,
access: Default::default(),
}
}
}
impl Default for AccessInfo {
fn default() -> Self {
AccessInfo {
settings: Default::default(),
timestamp: Instant::now(),
}
}
}
impl SuInfo {
fn allow(uid: i32) -> SuInfo {
let access = RootSettings {
policy: SuPolicy::Allow,
log: false,
notify: false,
};
SuInfo {
uid,
access: Mutex::new(AccessInfo::new(access)),
..Default::default()
}
}
fn deny(uid: i32) -> SuInfo {
let access = RootSettings {
policy: SuPolicy::Deny,
log: false,
notify: false,
};
SuInfo {
uid,
access: Mutex::new(AccessInfo::new(access)),
..Default::default()
}
}
}
impl AccessInfo {
fn new(settings: RootSettings) -> AccessInfo {
AccessInfo {
settings,
timestamp: Instant::now(),
}
}
fn is_fresh(&self) -> bool {
self.timestamp.elapsed() < Duration::from_secs(3)
}
fn refresh(&mut self) {
self.timestamp = Instant::now();
}
}
impl MagiskD {
pub fn su_daemon_handler(&self, client: i32, cred: &UCred) {
let cred = cred.0;
debug!(
"su: request from uid=[{}], pid=[{}], client=[{}]",
cred.uid, cred.pid, client
);
let mut client = unsafe { UnixStream::from_raw_fd(client) };
let mut req = match client.read_decodable::<SuRequest>().log() {
Ok(req) => req,
Err(_) => {
warn!("su: remote process probably died, abort");
client.write_pod(&SuPolicy::Deny.repr).ok();
return;
}
};
let info = self.get_su_info(cred.uid as i32);
let app_req = SuAppRequest {
uid: cred.uid as i32,
pid: cred.pid,
eval_uid: info.eval_uid,
mgr_pkg: &info.mgr_pkg,
mgr_uid: info.mgr_uid,
request: &req,
};
{
let mut access = info.access.lock().unwrap();
if access.settings.policy == SuPolicy::Query {
let fd = app_request(&app_req);
if fd < 0 {
access.settings.policy = SuPolicy::Deny;
} else {
let mut fd = unsafe { File::from_raw_fd(fd) };
access.settings.policy = SuPolicy {
repr: fd
.read_decodable::<i32>()
.log()
.map(i32::from_be)
.unwrap_or(SuPolicy::Deny.repr),
};
}
}
if access.settings.log {
app_log(&app_req, access.settings.policy, access.settings.notify);
} else if access.settings.notify {
app_notify(&app_req, access.settings.policy);
}
// Before unlocking, refresh the timestamp
access.refresh();
// Fail fast
if access.settings.policy == SuPolicy::Deny {
warn!("su: request rejected ({})", info.uid);
client.write_pod(&SuPolicy::Deny.repr).ok();
return;
}
}
// At this point, the root access is granted.
// Fork a child root process and monitor its exit value.
let child = unsafe { libc::fork() };
if child == 0 {
debug!("su: fork handler");
// Abort upon any error occurred
exit_on_error(true);
// ack
client.write_pod(&0).ok();
exec_root_shell(client.into_raw_fd(), cred.pid, &mut req, info.cfg.mnt_ns);
return;
}
if child < 0 {
error!("su: fork failed, abort");
return;
}
// Wait result
debug!("su: waiting child pid=[{}]", child);
let mut status = 0;
let code = unsafe {
if libc::waitpid(child, &mut status, 0) > 0 {
libc::WEXITSTATUS(status)
} else {
-1
}
};
debug!("su: return code=[{}]", code);
client.write_pod(&code).ok();
}
fn get_su_info(&self, uid: i32) -> Arc<SuInfo> {
if uid == AID_ROOT {
return Arc::new(SuInfo::allow(AID_ROOT));
}
let cached = self.cached_su_info.load();
if cached.uid == uid && cached.access.lock().unwrap().is_fresh() {
return cached;
}
let info = self.build_su_info(uid);
self.cached_su_info.store(info.clone());
info
}
fn build_su_info(&self, uid: i32) -> Arc<SuInfo> {
let result: LoggedResult<Arc<SuInfo>> = try {
let cfg = self.get_db_settings()?;
// Check multiuser settings
let eval_uid = match cfg.multiuser_mode {
MultiuserMode::OwnerOnly => {
if to_user_id(uid) != 0 {
return Arc::new(SuInfo::deny(uid));
}
uid
}
MultiuserMode::OwnerManaged => to_app_id(uid),
_ => uid,
};
// Check su access settings
match cfg.root_access {
RootAccess::Disabled => {
warn!("Root access is disabled!");
return Arc::new(SuInfo::deny(uid));
}
RootAccess::AdbOnly => {
if uid != AID_SHELL {
warn!("Root access limited to ADB only!");
return Arc::new(SuInfo::deny(uid));
}
}
RootAccess::AppsOnly => {
if uid == AID_SHELL {
warn!("Root access is disabled for ADB!");
return Arc::new(SuInfo::deny(uid));
}
}
_ => {}
};
let mut access = RootSettings::default();
self.get_root_settings(eval_uid, &mut access)?;
// We need to talk to the manager, get the app info
let (mgr_uid, mgr_pkg) =
if access.policy == SuPolicy::Query || access.log || access.notify {
self.get_manager(to_user_id(eval_uid), true)
} else {
(-1, String::new())
};
// If it's the manager, allow it silently
if to_app_id(uid) == to_app_id(mgr_uid) {
return Arc::new(SuInfo::allow(uid));
}
// If still not determined, check if manager exists
if access.policy == SuPolicy::Query && mgr_uid < 0 {
return Arc::new(SuInfo::deny(uid));
}
// Finally, the SuInfo
Arc::new(SuInfo {
uid,
eval_uid,
cfg,
mgr_pkg,
mgr_uid,
access: Mutex::new(AccessInfo::new(access)),
})
};
result.unwrap_or(Arc::new(SuInfo::deny(uid)))
}
}

133
native/src/core/su/db.rs Normal file
View File

@@ -0,0 +1,133 @@
use crate::daemon::{
to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL,
};
use crate::db::DbArg::Integer;
use crate::db::{MultiuserMode, RootAccess, SqlTable, SqliteResult, SqliteReturn};
use crate::ffi::{DbValues, SuPolicy};
use base::ResultExt;
impl Default for SuPolicy {
fn default() -> Self {
SuPolicy::Query
}
}
#[derive(Default)]
pub struct RootSettings {
pub policy: SuPolicy,
pub log: bool,
pub notify: bool,
}
impl SqlTable for RootSettings {
fn on_row(&mut self, columns: &[String], values: &DbValues) {
for (i, column) in columns.iter().enumerate() {
let val = values.get_int(i as i32);
match column.as_str() {
"policy" => self.policy.repr = val,
"logging" => self.log = val != 0,
"notification" => self.notify = val != 0,
_ => {}
}
}
}
}
struct UidList(Vec<i32>);
impl SqlTable for UidList {
fn on_row(&mut self, _: &[String], values: &DbValues) {
self.0.push(values.get_int(0));
}
}
impl MagiskD {
pub fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> {
self.db_exec_with_rows(
"SELECT policy, logging, notification FROM policies \
WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
&[Integer(uid as i64)],
settings,
)
.sql_result()
}
pub fn prune_su_access(&self) {
let mut list = UidList(Vec::new());
if self
.db_exec_with_rows("SELECT uid FROM policies", &[], &mut list)
.sql_result()
.log()
.is_err()
{
return;
}
let app_list = self.get_app_no_list();
let mut rm_uids = Vec::new();
for uid in list.0 {
let app_id = to_app_id(uid);
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
let app_no = app_id - AID_APP_START;
if !app_list.contains(app_no as usize) {
// The app_id is no longer installed
rm_uids.push(uid);
}
}
}
for uid in rm_uids {
self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]);
}
}
pub fn uid_granted_root(&self, mut uid: i32) -> bool {
if uid == AID_ROOT {
return true;
}
let cfg = match self.get_db_settings().log() {
Ok(cfg) => cfg,
Err(_) => return false,
};
// Check user root access settings
match cfg.root_access {
RootAccess::Disabled => return false,
RootAccess::AppsOnly => {
if uid == AID_SHELL {
return false;
}
}
RootAccess::AdbOnly => {
if uid != AID_SHELL {
return false;
}
}
_ => {}
}
// Check multiuser settings
match cfg.multiuser_mode {
MultiuserMode::OwnerOnly => {
if to_user_id(uid) != 0 {
return false;
}
}
MultiuserMode::OwnerManaged => uid = to_app_id(uid),
_ => {}
}
let mut granted = false;
let mut output_fn =
|_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr;
self.db_exec_with_rows(
"SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
&[Integer(uid as i64)],
&mut output_fn,
);
granted
}
}

View File

@@ -1,145 +1,4 @@
use crate::daemon::{
to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL,
};
use crate::db::DbArg::Integer;
use crate::db::{SqlTable, SqliteResult, SqliteReturn};
use crate::ffi::{DbValues, MultiuserMode, RootAccess, RootSettings, SuPolicy};
use base::ResultExt;
mod daemon;
mod db;
impl Default for SuPolicy {
fn default() -> Self {
SuPolicy::Query
}
}
impl Default for RootSettings {
fn default() -> Self {
RootSettings {
policy: Default::default(),
log: true,
notify: true,
}
}
}
impl SqlTable for RootSettings {
fn on_row(&mut self, columns: &[String], values: &DbValues) {
for (i, column) in columns.iter().enumerate() {
let val = values.get_int(i as i32);
if column == "policy" {
self.policy.repr = val;
} else if column == "logging" {
self.log = val != 0;
} else if column == "notify" {
self.notify = val != 0;
}
}
}
}
struct UidList(Vec<i32>);
impl SqlTable for UidList {
fn on_row(&mut self, _: &[String], values: &DbValues) {
self.0.push(values.get_int(0));
}
}
impl MagiskD {
fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> {
self.db_exec_with_rows(
"SELECT policy, logging, notification FROM policies \
WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
&[Integer(uid as i64)],
settings,
)
.sql_result()
}
pub fn get_root_settings_for_cxx(&self, uid: i32, settings: &mut RootSettings) -> bool {
self.get_root_settings(uid, settings).log().is_ok()
}
pub fn prune_su_access(&self) {
let mut list = UidList(Vec::new());
if self
.db_exec_with_rows("SELECT uid FROM policies", &[], &mut list)
.sql_result()
.log()
.is_err()
{
return;
}
let app_list = self.get_app_no_list();
let mut rm_uids = Vec::new();
for uid in list.0 {
let app_id = to_app_id(uid);
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
let app_no = app_id - AID_APP_START;
if !app_list.contains(app_no as usize) {
// The app_id is no longer installed
rm_uids.push(uid);
}
}
}
for uid in rm_uids {
self.db_exec("DELETE FROM policies WHERE uid=?", &[Integer(uid as i64)]);
}
}
pub fn uid_granted_root(&self, mut uid: i32) -> bool {
if uid == AID_ROOT {
return true;
}
let cfg = match self.get_db_settings().log() {
Ok(cfg) => cfg,
Err(_) => return false,
};
// Check user root access settings
match cfg.root_access {
RootAccess::Disabled => return false,
RootAccess::AppsOnly => {
if uid == AID_SHELL {
return false;
}
}
RootAccess::AdbOnly => {
if uid != AID_SHELL {
return false;
}
}
_ => {}
}
// Check multiuser settings
match cfg.multiuser_mode {
MultiuserMode::OwnerOnly => {
if to_user_id(uid) != 0 {
return false;
}
}
MultiuserMode::OwnerManaged => uid = to_app_id(uid),
_ => {}
}
let mut granted = false;
let mut output_fn =
|_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr;
self.db_exec_with_rows(
"SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
&[Integer(uid as i64)],
&mut output_fn,
);
granted
}
}
pub fn get_default_root_settings() -> RootSettings {
RootSettings::default()
}
pub use daemon::SuInfo;

View File

@@ -13,13 +13,24 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <algorithm>
#include <consts.hpp>
#include <base.hpp>
#include <flags.h>
#include <core.hpp>
#include "su.hpp"
#include "pts.hpp"
using namespace std;
#define DEFAULT_SHELL "/system/bin/sh"
// Constants for atty
#define ATTY_IN (1 << 0)
#define ATTY_OUT (1 << 1)
#define ATTY_ERR (1 << 2)
int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };
[[noreturn]] static void usage(int status) {
@@ -97,7 +108,7 @@ int su_client_main(int argc, char *argv[]) {
{ nullptr, 0, nullptr, 0 },
};
su_request su_req;
auto req = SuRequest::New();
for (int i = 0; i < argc; i++) {
// Replace -cn and -z with -Z for backwards compatibility
@@ -110,25 +121,28 @@ int su_client_main(int argc, char *argv[]) {
while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
switch (c) {
case 'c':
case 'c': {
string command;
for (int i = optind - 1; i < argc; ++i) {
if (!su_req.command.empty())
su_req.command += ' ';
su_req.command += argv[i];
if (!command.empty())
command += ' ';
command += argv[i];
}
req.command = command;
optind = argc;
break;
}
case 'h':
usage(EXIT_SUCCESS);
case 'l':
su_req.login = true;
req.login = true;
break;
case 'm':
case 'p':
su_req.keepenv = true;
req.keep_env = true;
break;
case 's':
su_req.shell = optarg;
req.shell = optarg;
break;
case 'V':
printf("%d\n", MAGISK_VER_CODE);
@@ -137,33 +151,36 @@ int su_client_main(int argc, char *argv[]) {
printf("%s\n", MAGISK_VERSION ":MAGISKSU");
exit(EXIT_SUCCESS);
case 'Z':
su_req.context = optarg;
req.context = optarg;
break;
case 'M':
case 't':
if (su_req.target != -1) {
if (req.target_pid != -1) {
fprintf(stderr, "Can't use -M and -t at the same time\n");
usage(EXIT_FAILURE);
}
if (optarg == nullptr) {
su_req.target = 0;
req.target_pid = 0;
} else {
su_req.target = parse_int(optarg);
if (*optarg == '-' || su_req.target == -1) {
req.target_pid = parse_int(optarg);
if (*optarg == '-' || req.target_pid == -1) {
fprintf(stderr, "Invalid PID: %s\n", optarg);
usage(EXIT_FAILURE);
}
}
break;
case 'g':
case 'G':
case 'G': {
vector<gid_t> gids;
if (int gid = parse_int(optarg); gid >= 0) {
su_req.gids.insert(c == 'g' ? su_req.gids.begin() : su_req.gids.end(), gid);
gids.insert(c == 'g' ? gids.begin() : gids.end(), gid);
} else {
fprintf(stderr, "Invalid GID: %s\n", optarg);
usage(EXIT_FAILURE);
}
std::copy(gids.begin(), gids.end(), std::back_inserter(req.gids));
break;
}
default:
/* Bionic getopt_long doesn't terminate its error output by newline */
fprintf(stderr, "\n");
@@ -172,7 +189,7 @@ int su_client_main(int argc, char *argv[]) {
}
if (optind < argc && strcmp(argv[optind], "-") == 0) {
su_req.login = true;
req.login = true;
optind++;
}
/* username or uid */
@@ -180,9 +197,9 @@ int su_client_main(int argc, char *argv[]) {
struct passwd *pw;
pw = getpwnam(argv[optind]);
if (pw)
su_req.uid = pw->pw_uid;
req.target_uid = pw->pw_uid;
else
su_req.uid = parse_int(argv[optind]);
req.target_uid = parse_int(argv[optind]);
optind++;
}
@@ -191,12 +208,8 @@ int su_client_main(int argc, char *argv[]) {
// Connect to client
fd = connect_daemon(+RequestCode::SUPERUSER);
// Send su_request
xwrite(fd, &su_req, sizeof(su_req_base));
write_string(fd, su_req.shell);
write_string(fd, su_req.command);
write_string(fd, su_req.context);
write_vector(fd, su_req.gids);
// Send request
req.write_to_fd(fd);
// Wait for ack from daemon
if (read_int(fd)) {
@@ -239,3 +252,162 @@ 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)");
}
gid_t gid;
if (!groups.empty()) {
if (setgroups(groups.size(), groups.data())) {
PLOGE("setgroups");
}
gid = groups[0];
} else {
gid = uid;
}
if (setresgid(gid, gid, gid)) {
PLOGE("setresgid (%u)", uid);
}
if (setresuid(uid, uid, uid)) {
PLOGE("setresuid (%u)", uid);
}
}
void exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) {
// Become session leader
xsetsid();
// The FDs for each of the streams
int infd = recv_fd(client);
int outfd = recv_fd(client);
int errfd = recv_fd(client);
// App need a PTY
if (read_int(client)) {
string pts;
string ptmx;
auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS;
if (access(magiskpts.data(), F_OK)) {
pts = "/dev/pts";
ptmx = "/dev/ptmx";
} else {
pts = magiskpts;
ptmx = magiskpts + "/ptmx";
}
int ptmx_fd = xopen(ptmx.data(), O_RDWR);
grantpt(ptmx_fd);
unlockpt(ptmx_fd);
int pty_num = get_pty_num(ptmx_fd);
if (pty_num < 0) {
// Kernel issue? Fallback to /dev/pts
close(ptmx_fd);
pts = "/dev/pts";
ptmx_fd = xopen("/dev/ptmx", O_RDWR);
grantpt(ptmx_fd);
unlockpt(ptmx_fd);
pty_num = get_pty_num(ptmx_fd);
}
send_fd(client, ptmx_fd);
close(ptmx_fd);
string pts_slave = pts + "/" + to_string(pty_num);
LOGD("su: pts_slave=[%s]\n", pts_slave.data());
// Opening the TTY has to occur after the
// fork() and setsid() so that it becomes
// our controlling TTY and not the daemon's
int ptsfd = xopen(pts_slave.data(), O_RDWR);
if (infd < 0)
infd = ptsfd;
if (outfd < 0)
outfd = ptsfd;
if (errfd < 0)
errfd = ptsfd;
}
// Swap out stdin, stdout, stderr
xdup2(infd, STDIN_FILENO);
xdup2(outfd, STDOUT_FILENO);
xdup2(errfd, STDERR_FILENO);
close(infd);
close(outfd);
close(errfd);
close(client);
// Handle namespaces
if (req.target_pid == -1)
req.target_pid = pid;
else if (req.target_pid == 0)
mode = MntNsMode::Global;
else if (mode == MntNsMode::Global)
mode = MntNsMode::Requester;
switch (mode) {
case MntNsMode::Global:
LOGD("su: use global namespace\n");
break;
case MntNsMode::Requester:
LOGD("su: use namespace of pid=[%d]\n", req.target_pid);
switch_mnt_ns(req.target_pid);
break;
case MntNsMode::Isolate:
LOGD("su: use new isolated namespace\n");
switch_mnt_ns(req.target_pid);
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
break;
}
const char *argv[4] = { nullptr };
argv[0] = req.login ? "-" : req.shell.c_str();
if (!req.command.empty()) {
argv[1] = "-c";
argv[2] = req.command.c_str();
}
// Setup environment
umask(022);
char path[32];
ssprintf(path, sizeof(path), "/proc/%d/cwd", pid);
char cwd[4096];
if (realpath(path, cwd, sizeof(cwd)) > 0)
chdir(cwd);
ssprintf(path, sizeof(path), "/proc/%d/environ", pid);
auto env = full_read(path);
clearenv();
for (size_t pos = 0; pos < env.size(); ++pos) {
putenv(env.data() + pos);
pos = env.find_first_of('\0', pos);
if (pos == std::string::npos)
break;
}
if (!req.keep_env) {
struct passwd *pw;
pw = getpwuid(req.target_uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
setenv("SHELL", req.shell.c_str(), 1);
}
}
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
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);
execvp(req.shell.c_str(), (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", req.shell.c_str(), strerror(errno));
PLOGE("exec");
}

View File

@@ -1,69 +0,0 @@
#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <memory>
#include <sqlite.hpp>
#include <core.hpp>
#define DEFAULT_SHELL "/system/bin/sh"
// Constants for atty
#define ATTY_IN (1 << 0)
#define ATTY_OUT (1 << 1)
#define ATTY_ERR (1 << 2)
#define SILENT_ALLOW { SuPolicy::Allow, false, false }
#define SILENT_DENY { SuPolicy::Deny, false, false }
class su_info {
public:
// Unique key
const int uid;
// These should be guarded with internal lock
int eval_uid; // The effective UID, taking multiuser settings into consideration
struct DbSettings cfg;
struct RootSettings access;
std::string mgr_pkg;
int mgr_uid;
void check_db();
// These should be guarded with global cache lock
bool is_fresh();
void refresh();
su_info(int uid);
~su_info();
mutex_guard lock();
private:
long timestamp;
// Internal lock
pthread_mutex_t _lock;
};
struct su_req_base {
uid_t uid = AID_ROOT;
bool login = false;
bool keepenv = false;
pid_t target = -1;
} __attribute__((packed));
struct su_request : public su_req_base {
std::string shell = DEFAULT_SHELL;
std::string command;
std::string context;
std::vector<gid_t> gids;
};
struct su_context {
std::shared_ptr<su_info> info;
su_request req;
int pid;
};
void app_log(const su_context &ctx);
void app_notify(const su_context &ctx);
int app_request(const su_context &ctx);

View File

@@ -1,377 +0,0 @@
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <consts.hpp>
#include <base.hpp>
#include <selinux.hpp>
#include "su.hpp"
#include "pts.hpp"
using namespace std;
static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
static shared_ptr<su_info> cached;
su_info::su_info(int uid) :
uid(uid), eval_uid(-1), cfg(DbSettings()), access(RootSettings()),
mgr_uid(-1), timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {}
su_info::~su_info() {
pthread_mutex_destroy(&_lock);
}
mutex_guard su_info::lock() {
return mutex_guard(_lock);
}
bool su_info::is_fresh() {
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
long current = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
return current - timestamp < 3000; /* 3 seconds */
}
void su_info::refresh() {
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
timestamp = ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
}
void su_info::check_db() {
eval_uid = uid;
auto &daemon = MagiskD();
daemon.get_db_settings(cfg);
// Check multiuser settings
switch (cfg.multiuser_mode) {
case MultiuserMode::OwnerOnly:
if (to_user_id(uid) != 0) {
eval_uid = -1;
access = SILENT_DENY;
}
break;
case MultiuserMode::OwnerManaged:
eval_uid = to_app_id(uid);
break;
case MultiuserMode::User:
default:
break;
}
if (eval_uid > 0) {
if (!daemon.get_root_settings(eval_uid, access))
return;
}
// We need to check our manager
if (access.policy == SuPolicy::Query || access.log || access.notify) {
mgr_uid = daemon.get_manager(to_user_id(eval_uid), &mgr_pkg, true);
}
}
static shared_ptr<su_info> get_su_info(unsigned uid) {
if (uid == AID_ROOT) {
auto info = make_shared<su_info>(uid);
info->access = SILENT_ALLOW;
return info;
}
shared_ptr<su_info> info;
{
mutex_guard lock(cache_lock);
if (!cached || cached->uid != uid || !cached->is_fresh())
cached = make_shared<su_info>(uid);
cached->refresh();
info = cached;
}
mutex_guard lock = info->lock();
if (info->access.policy == SuPolicy::Query) {
// Not cached, get data from database
info->check_db();
// If it's the manager, allow it silently
if (to_app_id(info->uid) == to_app_id(info->mgr_uid)) {
info->access = SILENT_ALLOW;
return info;
}
// Check su access settings
switch (info->cfg.root_access) {
case RootAccess::Disabled:
LOGW("Root access is disabled!\n");
info->access = SILENT_DENY;
break;
case RootAccess::AdbOnly:
if (info->uid != AID_SHELL) {
LOGW("Root access limited to ADB only!\n");
info->access = SILENT_DENY;
}
break;
case RootAccess::AppsOnly:
if (info->uid == AID_SHELL) {
LOGW("Root access is disabled for ADB!\n");
info->access = SILENT_DENY;
}
break;
case RootAccess::AppsAndAdb:
default:
break;
}
if (info->access.policy != SuPolicy::Query)
return info;
// If still not determined, check if manager exists
if (info->mgr_uid < 0) {
info->access = SILENT_DENY;
return info;
}
}
return info;
}
// Set effective uid back to root, otherwise setres[ug]id will fail if uid isn't root
static void set_identity(uid_t uid, const std::vector<uid_t> &groups) {
if (seteuid(0)) {
PLOGE("seteuid (root)");
}
gid_t gid;
if (groups.size() > 0) {
if (setgroups(groups.size(), groups.data())) {
PLOGE("setgroups");
}
gid = groups[0];
} else {
gid = uid;
}
if (setresgid(gid, gid, gid)) {
PLOGE("setresgid (%u)", uid);
}
if (setresuid(uid, uid, uid)) {
PLOGE("setresuid (%u)", uid);
}
}
void su_daemon_handler(int client, const sock_cred *cred) {
LOGD("su: request from uid=[%d], pid=[%d], client=[%d]\n", cred->uid, cred->pid, client);
su_context ctx = {
.info = get_su_info(cred->uid),
.req = su_request(),
.pid = cred->pid
};
// Read su_request
if (xxread(client, &ctx.req, sizeof(su_req_base)) < 0
|| !read_string(client, ctx.req.shell)
|| !read_string(client, ctx.req.command)
|| !read_string(client, ctx.req.context)
|| !read_vector(client, ctx.req.gids)) {
LOGW("su: remote process probably died, abort\n");
ctx.info.reset();
write_int(client, +SuPolicy::Deny);
close(client);
return;
}
// If still not determined, ask manager
if (ctx.info->access.policy == SuPolicy::Query) {
int fd = app_request(ctx);
if (fd < 0) {
ctx.info->access.policy = SuPolicy::Deny;
} else {
int ret = read_int_be(fd);
ctx.info->access.policy = ret < 0 ? SuPolicy::Deny : static_cast<SuPolicy>(ret);
close(fd);
}
}
if (ctx.info->access.log)
app_log(ctx);
else if (ctx.info->access.notify)
app_notify(ctx);
// Fail fast
if (ctx.info->access.policy == SuPolicy::Deny) {
LOGW("su: request rejected (%u)\n", ctx.info->uid);
ctx.info.reset();
write_int(client, +SuPolicy::Deny);
close(client);
return;
}
// Fork a child root process
//
// The child process will need to setsid, open a pseudo-terminal
// if needed, and eventually exec shell.
// The parent process will wait for the result and
// send the return code back to our client.
if (int child = xfork(); child) {
ctx.info.reset();
// Wait result
LOGD("su: waiting child pid=[%d]\n", child);
int status, code;
if (waitpid(child, &status, 0) > 0)
code = WEXITSTATUS(status);
else
code = -1;
LOGD("su: return code=[%d]\n", code);
write(client, &code, sizeof(code));
close(client);
return;
}
LOGD("su: fork handler\n");
// Abort upon any error occurred
exit_on_error(true);
// ack
write_int(client, 0);
// Become session leader
xsetsid();
// The FDs for each of the streams
int infd = recv_fd(client);
int outfd = recv_fd(client);
int errfd = recv_fd(client);
// App need a PTY
if (read_int(client)) {
string pts;
string ptmx;
auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS;
if (access(magiskpts.data(), F_OK)) {
pts = "/dev/pts";
ptmx = "/dev/ptmx";
} else {
pts = magiskpts;
ptmx = magiskpts + "/ptmx";
}
int ptmx_fd = xopen(ptmx.data(), O_RDWR);
grantpt(ptmx_fd);
unlockpt(ptmx_fd);
int pty_num = get_pty_num(ptmx_fd);
if (pty_num < 0) {
// Kernel issue? Fallback to /dev/pts
close(ptmx_fd);
pts = "/dev/pts";
ptmx_fd = xopen("/dev/ptmx", O_RDWR);
grantpt(ptmx_fd);
unlockpt(ptmx_fd);
pty_num = get_pty_num(ptmx_fd);
}
send_fd(client, ptmx_fd);
close(ptmx_fd);
string pts_slave = pts + "/" + to_string(pty_num);
LOGD("su: pts_slave=[%s]\n", pts_slave.data());
// Opening the TTY has to occur after the
// fork() and setsid() so that it becomes
// our controlling TTY and not the daemon's
int ptsfd = xopen(pts_slave.data(), O_RDWR);
if (infd < 0)
infd = ptsfd;
if (outfd < 0)
outfd = ptsfd;
if (errfd < 0)
errfd = ptsfd;
}
// Swap out stdin, stdout, stderr
xdup2(infd, STDIN_FILENO);
xdup2(outfd, STDOUT_FILENO);
xdup2(errfd, STDERR_FILENO);
close(infd);
close(outfd);
close(errfd);
close(client);
// Handle namespaces
if (ctx.req.target == -1)
ctx.req.target = ctx.pid;
else if (ctx.req.target == 0)
ctx.info->cfg.mnt_ns = MntNsMode::Global;
else if (ctx.info->cfg.mnt_ns == MntNsMode::Global)
ctx.info->cfg.mnt_ns = MntNsMode::Requester;
switch (ctx.info->cfg.mnt_ns) {
case MntNsMode::Global:
LOGD("su: use global namespace\n");
break;
case MntNsMode::Requester:
LOGD("su: use namespace of pid=[%d]\n", ctx.req.target);
if (switch_mnt_ns(ctx.req.target))
LOGD("su: setns failed, fallback to global\n");
break;
case MntNsMode::Isolate:
LOGD("su: use new isolated namespace\n");
switch_mnt_ns(ctx.req.target);
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
break;
}
const char *argv[4] = { nullptr };
argv[0] = ctx.req.login ? "-" : ctx.req.shell.data();
if (!ctx.req.command.empty()) {
argv[1] = "-c";
argv[2] = ctx.req.command.data();
}
// Setup environment
umask(022);
char path[32];
ssprintf(path, sizeof(path), "/proc/%d/cwd", ctx.pid);
char cwd[4096];
if (realpath(path, cwd, sizeof(cwd)) > 0)
chdir(cwd);
ssprintf(path, sizeof(path), "/proc/%d/environ", ctx.pid);
auto env = full_read(path);
clearenv();
for (size_t pos = 0; pos < env.size(); ++pos) {
putenv(env.data() + pos);
pos = env.find_first_of('\0', pos);
if (pos == std::string::npos)
break;
}
if (!ctx.req.keepenv) {
struct passwd *pw;
pw = getpwuid(ctx.req.uid);
if (pw) {
setenv("HOME", pw->pw_dir, 1);
setenv("USER", pw->pw_name, 1);
setenv("LOGNAME", pw->pw_name, 1);
setenv("SHELL", ctx.req.shell.data(), 1);
}
}
// Unblock all signals
sigset_t block_set;
sigemptyset(&block_set);
sigprocmask(SIG_SETMASK, &block_set, nullptr);
if (!ctx.req.context.empty()) {
auto f = xopen_file("/proc/self/attr/exec", "we");
if (f) fprintf(f.get(), "%s", ctx.req.context.data());
}
set_identity(ctx.req.uid, ctx.req.gids);
execvp(ctx.req.shell.data(), (char **) argv);
fprintf(stderr, "Cannot execute %s: %s\n", ctx.req.shell.data(), strerror(errno));
PLOGE("exec");
}

View File

@@ -0,0 +1,210 @@
use crate::consts::MODULEROOT;
use crate::daemon::{to_user_id, MagiskD};
use crate::ffi::{
get_magisk_tmp, restore_zygisk_prop, update_deny_flags, ZygiskRequest, ZygiskStateFlags,
};
use crate::socket::{IpcRead, UnixSocketExt};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO};
use base::{
cstr, cstr_buf, error, fork_dont_care, libc, open_fd, raw_cstr, warn, Directory, FsPathBuf,
LoggedError, LoggedResult, ResultExt, WriteExt,
};
use std::fmt::Write;
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::ptr;
use std::sync::atomic::Ordering;
const UNMOUNT_MASK: u32 =
ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr;
pub fn zygisk_should_load_module(flags: u32) -> bool {
flags & UNMOUNT_MASK != UNMOUNT_MASK && flags & ZygiskStateFlags::ProcessIsMagiskApp.repr == 0
}
#[allow(unused_variables)]
fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) {
// This fd has to survive exec
unsafe {
libc::fcntl(remote.as_raw_fd(), libc::F_SETFD, 0);
}
// Start building the exec arguments
#[cfg(target_pointer_width = "64")]
let magisk = if is_64_bit { "magisk" } else { "magisk32" };
#[cfg(target_pointer_width = "32")]
let magisk = "magisk";
let exe = FsPathBuf::<64>::new().join(get_magisk_tmp()).join(magisk);
let mut fd_str = cstr_buf::new::<16>();
write!(fd_str, "{}", remote.as_raw_fd()).ok();
unsafe {
libc::execl(
exe.as_ptr(),
raw_cstr!(""),
raw_cstr!("zygisk"),
raw_cstr!("companion"),
fd_str.as_ptr(),
ptr::null() as *const libc::c_char,
);
libc::exit(-1);
}
}
impl MagiskD {
pub fn zygisk_handler(&self, client: i32) {
let mut client = unsafe { UnixStream::from_raw_fd(client) };
let _: LoggedResult<()> = try {
let code = ZygiskRequest {
repr: client.read_decodable()?,
};
match code {
ZygiskRequest::GetInfo => self.get_process_info(client)?,
ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client),
ZygiskRequest::GetModDir => self.get_mod_dir(client)?,
_ => {}
}
};
}
pub fn zygisk_reset(&self, mut restore: bool) {
if !self.zygisk_enabled.load(Ordering::Acquire) {
return;
}
if restore {
self.zygote_start_count.store(1, Ordering::Release);
} else {
*self.zygiskd_sockets.lock().unwrap() = (None, None);
if self.zygote_start_count.fetch_add(1, Ordering::AcqRel) > 3 {
warn!("zygote crashes too many times, rolling-back");
restore = true;
}
}
if restore {
restore_zygisk_prop();
}
}
fn get_module_fds(&self, is_64_bit: bool) -> Option<Vec<RawFd>> {
self.module_list.get().map(|module_list| {
module_list
.iter()
.map(|m| if is_64_bit { m.z64 } else { m.z32 })
// All fds passed over sockets have to be valid file descriptors.
// To work around this issue, send over STDOUT_FILENO as an indicator of an
// invalid fd as it will always be /dev/null in magiskd.
.map(|fd| if fd < 0 { STDOUT_FILENO } else { fd })
.collect()
})
}
fn connect_zygiskd(&self, mut client: UnixStream) {
let mut zygiskd_sockets = self.zygiskd_sockets.lock().unwrap();
let result: LoggedResult<()> = try {
let is_64_bit: bool = client.read_decodable()?;
let socket = if is_64_bit {
&mut zygiskd_sockets.1
} else {
&mut zygiskd_sockets.0
};
if let Some(fd) = socket {
// Make sure the socket is still valid
let mut pfd = libc::pollfd {
fd: fd.as_raw_fd(),
events: 0,
revents: 0,
};
if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 {
// Any revent means error
*socket = None;
}
}
let socket = if let Some(fd) = socket {
fd
} else {
// Create a new socket pair and fork zygiskd process
let (local, remote) = UnixStream::pair()?;
if fork_dont_care() == 0 {
exec_zygiskd(is_64_bit, remote);
}
*socket = Some(local);
let local = socket.as_mut().unwrap();
if let Some(module_fds) = self.get_module_fds(is_64_bit) {
local.send_fds(&module_fds)?;
}
if local.read_decodable::<i32>()? != 0 {
Err(LoggedError::default())?;
}
local
};
socket.send_fds(&[client.as_raw_fd()])?;
};
if result.is_err() {
error!("zygiskd startup error");
}
}
fn get_process_info(&self, mut client: UnixStream) -> LoggedResult<()> {
let uid: i32 = client.read_decodable()?;
let process: String = client.read_decodable()?;
let is_64_bit: bool = client.read_decodable()?;
let mut flags: u32 = 0;
update_deny_flags(uid, &process, &mut flags);
if self.get_manager_uid(to_user_id(uid)) == uid {
flags |= ZygiskStateFlags::ProcessIsMagiskApp.repr
}
if self.uid_granted_root(uid) {
flags |= ZygiskStateFlags::ProcessGrantedRoot.repr
}
// First send flags
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 we're not in system_server, we are done
if uid != 1000 || process != "system_server" {
return Ok(());
}
// Read all failed modules
let failed_ids: Vec<i32> = client.read_decodable()?;
if let Some(module_list) = self.module_list.get() {
for id in failed_ids {
let path = FsPathBuf::default()
.join(MODULEROOT)
.join(&module_list[id as usize].name)
.join("zygisk");
// Create the unloaded marker file
if let Ok(dir) = Directory::open(&path) {
dir.open_fd(cstr!("unloaded"), O_CREAT | O_RDONLY, 0o644)
.log()
.ok();
}
}
}
Ok(())
}
fn get_mod_dir(&self, mut client: UnixStream) -> LoggedResult<()> {
let id: i32 = client.read_decodable()?;
let module = &self.module_list.get().unwrap()[id as usize];
let dir = FsPathBuf::default().join(MODULEROOT).join(&module.name);
let fd = open_fd!(&dir, O_RDONLY | O_CLOEXEC)?;
client.send_fds(&[fd.as_raw_fd()])?;
Ok(())
}
}

View File

@@ -1,229 +1,120 @@
#include <libgen.h>
#include <dlfcn.h>
#include <sys/prctl.h>
#include <sys/mount.h>
#include <android/log.h>
#include <android/dlext.h>
#include <dlfcn.h>
#include <base.hpp>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include "zygisk.hpp"
#include "module.hpp"
using namespace std;
string native_bridge = "0";
static bool is_compatible_with(uint32_t) {
zygisk_logging();
hook_entry();
ZLOGD("load success\n");
return false;
static void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
init_thread_pool();
#if defined(__LP64__)
set_nice_name("zygiskd64");
LOGI("* Launching zygiskd64\n");
#else
set_nice_name("zygiskd32");
LOGI("* Launching zygiskd32\n");
#endif
// Load modules
using comp_entry = void(*)(int);
vector<comp_entry> modules;
{
auto module_fds = recv_fds(socket);
for (int fd : module_fds) {
comp_entry entry = nullptr;
struct stat s{};
if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) {
android_dlextinfo info {
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fd,
};
if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) {
*(void **) &entry = dlsym(h, "zygisk_companion_entry");
} else {
LOGW("Failed to dlopen zygisk module: %s\n", dlerror());
}
}
modules.push_back(entry);
close(fd);
}
}
// ack
write_int(socket, 0);
// Start accepting requests
pollfd pfd = { socket, POLLIN, 0 };
for (;;) {
poll(&pfd, 1, -1);
if (pfd.revents && !(pfd.revents & POLLIN)) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int client = recv_fd(socket);
if (client < 0) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int module_id = read_int(client);
if (module_id >= 0 && module_id < modules.size() && modules[module_id]) {
exec_task([=, entry = modules[module_id]] {
struct stat s1;
fstat(client, &s1);
entry(client);
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if (struct stat s2; fstat(client, &s2) == 0) {
if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) {
close(client);
}
}
});
} else {
close(client);
}
}
}
// Entrypoint where we need to re-exec ourselves
// This should only ever be called internally
int zygisk_main(int argc, char *argv[]) {
android_logging();
if (argc == 3 && argv[1] == "companion"sv) {
zygiskd(parse_int(argv[2]));
}
return 0;
}
// Entrypoint of code injection
extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf {
.version = 2,
.padding = {},
.isCompatibleWith = &is_compatible_with,
.isCompatibleWith = [](auto) {
zygisk_logging();
hook_entry();
ZLOGD("load success\n");
return false;
},
};
// The following code runs in zygote/app process
static inline bool should_load_modules(uint32_t flags) {
return (flags & UNMOUNT_MASK) != UNMOUNT_MASK &&
(flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP;
}
int remote_get_info(int uid, const char *process, uint32_t *flags, vector<int> &fds) {
if (int fd = zygisk_request(ZygiskRequest::GET_INFO); fd >= 0) {
write_int(fd, uid);
write_string(fd, process);
#ifdef __LP64__
write_int(fd, 1);
#else
write_int(fd, 0);
#endif
xxread(fd, flags, sizeof(*flags));
if (should_load_modules(*flags)) {
fds = recv_fds(fd);
}
return fd;
}
return -1;
}
// The following code runs in magiskd
static vector<int> get_module_fds(bool is_64_bit) {
vector<int> fds;
// All fds passed to send_fds have to be valid file descriptors.
// To workaround this issue, send over STDOUT_FILENO as an indicator of an
// invalid fd as it will always be /dev/null in magiskd
#if defined(__LP64__)
if (is_64_bit) {
std::transform(module_list->begin(), module_list->end(), std::back_inserter(fds),
[](const module_info &info) { return info.z64 < 0 ? STDOUT_FILENO : info.z64; });
} else
#endif
{
std::transform(module_list->begin(), module_list->end(), std::back_inserter(fds),
[](const module_info &info) { return info.z32 < 0 ? STDOUT_FILENO : info.z32; });
}
return fds;
}
static pthread_mutex_t zygiskd_lock = PTHREAD_MUTEX_INITIALIZER;
static int zygiskd_sockets[] = { -1, -1 };
#define zygiskd_socket zygiskd_sockets[is_64_bit]
static void connect_companion(int client, bool is_64_bit) {
mutex_guard g(zygiskd_lock);
if (zygiskd_socket >= 0) {
// Make sure the socket is still valid
pollfd pfd = { zygiskd_socket, 0, 0 };
poll(&pfd, 1, 0);
if (pfd.revents) {
// Any revent means error
close(zygiskd_socket);
zygiskd_socket = -1;
}
}
if (zygiskd_socket < 0) {
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
zygiskd_socket = fds[0];
if (fork_dont_care() == 0) {
char exe[64];
#if defined(__LP64__)
ssprintf(exe, sizeof(exe), "%s/magisk%s", get_magisk_tmp(), (is_64_bit ? "" : "32"));
#else
ssprintf(exe, sizeof(exe), "%s/magisk", get_magisk_tmp());
#endif
// This fd has to survive exec
fcntl(fds[1], F_SETFD, 0);
char buf[16];
ssprintf(buf, sizeof(buf), "%d", fds[1]);
execl(exe, "", "zygisk", "companion", buf, (char *) nullptr);
exit(-1);
}
close(fds[1]);
vector<int> module_fds = get_module_fds(is_64_bit);
send_fds(zygiskd_socket, module_fds.data(), module_fds.size());
// Wait for ack
if (read_int(zygiskd_socket) != 0) {
LOGE("zygiskd startup error\n");
return;
}
}
send_fd(zygiskd_socket, client);
}
static void get_process_info(int client, const sock_cred *cred) {
int uid = read_int(client);
string process = read_string(client);
int arch = read_int(client);
auto &daemon = MagiskD();
uint32_t flags = 0;
if (is_deny_target(uid, process)) {
flags |= PROCESS_ON_DENYLIST;
}
if (daemon.get_manager(to_user_id(uid), nullptr, false) == uid) {
flags |= PROCESS_IS_MAGISK_APP;
}
if (denylist_enforced) {
flags |= DENYLIST_ENFORCING;
}
if (daemon.uid_granted_root(uid)) {
flags |= PROCESS_GRANTED_ROOT;
}
xwrite(client, &flags, sizeof(flags));
if (should_load_modules(flags)) {
vector<int> fds = get_module_fds(arch);
send_fds(client, fds.data(), fds.size());
}
if (uid != 1000 || process != "system_server")
return;
// Collect module status from system_server
int slots = read_int(client);
dynamic_bitset bits;
for (int i = 0; i < slots; ++i) {
dynamic_bitset::slot_type l = 0;
xxread(client, &l, sizeof(l));
bits.emplace_back(l);
}
for (int id = 0; id < module_list->size(); ++id) {
if (!as_const(bits)[id]) {
// Either not a zygisk module, or incompatible
char buf[4096];
ssprintf(buf, sizeof(buf), MODULEROOT "/%s/zygisk",
module_list->operator[](id).name.data());
if (int dirfd = open(buf, O_RDONLY | O_CLOEXEC); dirfd >= 0) {
close(xopenat(dirfd, "unloaded", O_CREAT | O_RDONLY, 0644));
close(dirfd);
}
}
}
}
static void get_moddir(int client) {
int id = read_int(client);
char buf[4096];
ssprintf(buf, sizeof(buf), MODULEROOT "/%s", module_list->operator[](id).name.data());
int dfd = xopen(buf, O_RDONLY | O_CLOEXEC);
send_fd(client, dfd);
close(dfd);
}
void zygisk_handler(int client, const sock_cred *cred) {
int code = read_int(client);
switch (code) {
case ZygiskRequest::GET_INFO:
get_process_info(client, cred);
break;
case ZygiskRequest::CONNECT_COMPANION: {
int arch = read_int(client);
connect_companion(client, arch);
break;
}
case ZygiskRequest::GET_MODDIR:
get_moddir(client);
break;
default:
// Unknown code
break;
}
close(client);
}
void reset_zygisk(bool restore) {
if (!zygisk_enabled) return;
static atomic_uint zygote_start_count{1};
if (!restore) {
close(zygiskd_sockets[0]);
close(zygiskd_sockets[1]);
zygiskd_sockets[0] = zygiskd_sockets[1] = -1;
}
if (restore) {
zygote_start_count = 1;
} else if (zygote_start_count.fetch_add(1) > 3) {
LOGW("zygote crashes too many times, rolling-back\n");
restore = true;
}
if (restore) {
string native_bridge_orig = "0";
if (native_bridge.length() > strlen(ZYGISKLDR)) {
native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR));
}
set_prop(NBPROP, native_bridge_orig.data());
} else {
set_prop(NBPROP, native_bridge.data());
}
void restore_zygisk_prop() {
string native_bridge_orig = "0";
if (native_bridge.length() > strlen(ZYGISKLDR)) {
native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR));
}
set_prop(NBPROP, native_bridge_orig.data());
}

View File

@@ -1,98 +0,0 @@
#include <sys/mount.h>
#include <android/dlext.h>
#include <dlfcn.h>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include "zygisk.hpp"
using namespace std;
static void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
init_thread_pool();
#if defined(__LP64__)
set_nice_name("zygiskd64");
LOGI("* Launching zygiskd64\n");
#else
set_nice_name("zygiskd32");
LOGI("* Launching zygiskd32\n");
#endif
// Load modules
using comp_entry = void(*)(int);
vector<comp_entry> modules;
{
vector<int> module_fds = recv_fds(socket);
for (int fd : module_fds) {
comp_entry entry = nullptr;
struct stat s{};
if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) {
android_dlextinfo info {
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fd,
};
if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) {
*(void **) &entry = dlsym(h, "zygisk_companion_entry");
} else {
LOGW("Failed to dlopen zygisk module: %s\n", dlerror());
}
}
modules.push_back(entry);
close(fd);
}
}
// ack
write_int(socket, 0);
// Start accepting requests
pollfd pfd = { socket, POLLIN, 0 };
for (;;) {
poll(&pfd, 1, -1);
if (pfd.revents && !(pfd.revents & POLLIN)) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int client = recv_fd(socket);
if (client < 0) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int module_id = read_int(client);
if (module_id >= 0 && module_id < modules.size() && modules[module_id]) {
exec_task([=, entry = modules[module_id]] {
struct stat s1;
fstat(client, &s1);
entry(client);
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if (struct stat s2; fstat(client, &s2) == 0) {
if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) {
close(client);
}
}
});
} else {
close(client);
}
}
}
// Entrypoint where we need to re-exec ourselves
// This should only ever be called internally
int zygisk_main(int argc, char *argv[]) {
android_logging();
if (argc == 3 && argv[1] == "companion"sv) {
zygiskd(parse_int(argv[2]));
}
return 0;
}

View File

@@ -0,0 +1,3 @@
mod daemon;
pub use daemon::zygisk_should_load_module;

View File

@@ -77,11 +77,11 @@ bool ZygiskModule::valid() const {
}
int ZygiskModule::connectCompanion() const {
if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) {
if (int fd = zygisk_request(+ZygiskRequest::ConnectCompanion); fd >= 0) {
#ifdef __LP64__
write_int(fd, 1);
write_any<bool>(fd, true);
#else
write_int(fd, 0);
write_any<bool>(fd, false);
#endif
write_int(fd, id);
return fd;
@@ -90,11 +90,9 @@ int ZygiskModule::connectCompanion() const {
}
int ZygiskModule::getModuleDir() const {
if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) {
if (owned_fd fd = zygisk_request(+ZygiskRequest::GetModDir); fd >= 0) {
write_int(fd, id);
int dfd = recv_fd(fd);
close(fd);
return dfd;
return recv_fd(fd);
}
return -1;
}
@@ -210,6 +208,24 @@ bool ZygiskContext::plt_hook_commit() {
// -----------------------------------------------------------------
int ZygiskContext::get_module_info(int uid, rust::Vec<int> &fds) {
if (int fd = zygisk_request(+ZygiskRequest::GetInfo); fd >= 0) {
write_int(fd, uid);
write_string(fd, process);
#ifdef __LP64__
write_any<bool>(fd, true);
#else
write_any<bool>(fd, false);
#endif
xxread(fd, &info_flags, sizeof(info_flags));
if (zygisk_should_load_module(info_flags)) {
fds = recv_fds(fd);
}
return fd;
}
return -1;
}
void ZygiskContext::sanitize_fds() {
zygisk_close_logd();
@@ -315,16 +331,17 @@ void ZygiskContext::fork_post() {
sigmask(SIG_UNBLOCK, SIGCHLD);
}
void ZygiskContext::run_modules_pre(const vector<int> &fds) {
void ZygiskContext::run_modules_pre(rust::Vec<int> &fds) {
for (int i = 0; i < fds.size(); ++i) {
owned_fd fd = fds[i];
struct stat s{};
if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) {
close(fds[i]);
if (fstat(fd, &s) != 0 || !S_ISREG(s.st_mode)) {
fds[i] = -1;
continue;
}
android_dlextinfo info {
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fds[i],
.library_fd = fd,
};
if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) {
if (void *e = dlsym(h, "zygisk_module_entry")) {
@@ -332,8 +349,8 @@ void ZygiskContext::run_modules_pre(const vector<int> &fds) {
}
} else if (flags & SERVER_FORK_AND_SPECIALIZE) {
ZLOGW("Failed to dlopen zygisk module: %s\n", dlerror());
fds[i] = -1;
}
close(fds[i]);
}
for (auto it = modules.begin(); it != modules.end();) {
@@ -369,20 +386,19 @@ void ZygiskContext::run_modules_post() {
void ZygiskContext::app_specialize_pre() {
flags |= APP_SPECIALIZE;
vector<int> module_fds;
int fd = remote_get_info(args.app->uid, process, &info_flags, module_fds);
rust::Vec<int> module_fds;
owned_fd fd = get_module_info(args.app->uid, module_fds);
if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) {
ZLOGI("[%s] is on the denylist\n", process);
flags |= DO_REVERT_UNMOUNT;
} else if (fd >= 0) {
run_modules_pre(module_fds);
}
close(fd);
}
void ZygiskContext::app_specialize_post() {
run_modules_post();
if (info_flags & PROCESS_IS_MAGISK_APP) {
if (info_flags & +ZygiskStateFlags::ProcessIsMagiskApp) {
setenv("ZYGISK_ENABLED", "1", 1);
}
@@ -391,25 +407,22 @@ void ZygiskContext::app_specialize_post() {
}
void ZygiskContext::server_specialize_pre() {
vector<int> module_fds;
int fd = remote_get_info(1000, "system_server", &info_flags, module_fds);
if (fd >= 0) {
rust::Vec<int> module_fds;
if (owned_fd fd = get_module_info(1000, module_fds); fd >= 0) {
if (module_fds.empty()) {
write_int(fd, 0);
} else {
run_modules_pre(module_fds);
// Send the bitset of module status back to magiskd from system_server
dynamic_bitset bits;
for (const auto &m : modules)
bits[m.getId()] = true;
write_int(fd, static_cast<int>(bits.slots()));
for (int i = 0; i < bits.slots(); ++i) {
auto l = bits.get_slot(i);
xwrite(fd, &l, sizeof(l));
// Find all failed module ids and send it back to magiskd
vector<int> failed_ids;
for (int i = 0; i < module_fds.size(); ++i) {
if (module_fds[i] < 0) {
failed_ids.push_back(i);
}
}
write_vector(fd, failed_ids);
}
close(fd);
}
}
@@ -435,6 +448,7 @@ void ZygiskContext::nativeSpecializeAppProcess_post() {
void ZygiskContext::nativeForkSystemServer_pre() {
ZLOGV("pre forkSystemServer\n");
flags |= SERVER_FORK_AND_SPECIALIZE;
process = "system_server";
fork_pre();
if (is_child()) {

View File

@@ -122,15 +122,13 @@ struct module_abi_v1 {
void (*postServerSpecialize)(void *, const void *);
};
// Assert the flag values to be the same as the public API
static_assert(+ZygiskStateFlags::ProcessGrantedRoot == zygisk::StateFlag::PROCESS_GRANTED_ROOT);
static_assert(+ZygiskStateFlags::ProcessOnDenyList == zygisk::StateFlag::PROCESS_ON_DENYLIST);
enum : uint32_t {
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
DENYLIST_ENFORCING = (1u << 30),
PROCESS_IS_MAGISK_APP = (1u << 31),
UNMOUNT_MASK = (PROCESS_ON_DENYLIST | DENYLIST_ENFORCING),
PRIVATE_MASK = (DENYLIST_ENFORCING | PROCESS_IS_MAGISK_APP)
UNMOUNT_MASK = (+ZygiskStateFlags::ProcessOnDenyList | +ZygiskStateFlags::DenyListEnforced),
PRIVATE_MASK = (+ZygiskStateFlags::DenyListEnforced | +ZygiskStateFlags::ProcessIsMagiskApp)
};
struct api_abi_base {
@@ -188,7 +186,6 @@ struct ZygiskModule {
static uint32_t getFlags();
void tryUnload() const;
void clearApi() { memset(&api, 0, sizeof(api)); }
int getId() const { return id; }
ZygiskModule(int id, void *handle, void *entry);
@@ -264,7 +261,7 @@ struct ZygiskContext {
ZygiskContext(JNIEnv *env, void *args);
~ZygiskContext();
void run_modules_pre(const std::vector<int> &fds);
void run_modules_pre(rust::Vec<int> &fds);
void run_modules_post();
DCL_PRE_POST(fork)
DCL_PRE_POST(app_specialize)
@@ -273,6 +270,7 @@ struct ZygiskContext {
DCL_PRE_POST(nativeSpecializeAppProcess)
DCL_PRE_POST(nativeForkSystemServer)
int get_module_info(int uid, rust::Vec<int> &fds);
void sanitize_fds();
bool exempt_fd(int fd);
bool can_exempt_fd() const;

View File

@@ -6,15 +6,6 @@
#include <vector>
#include <core.hpp>
namespace ZygiskRequest {
enum : int {
GET_INFO,
CONNECT_COMPANION,
GET_MODDIR,
END
};
}
#if defined(__LP64__)
#define ZLOGD(...) LOGD("zygisk64: " __VA_ARGS__)
#define ZLOGE(...) LOGE("zygisk64: " __VA_ARGS__)
@@ -34,8 +25,6 @@ enum : int {
void hook_entry();
void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods);
int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector<int> &fds);
inline int zygisk_request(int req) {
int fd = connect_daemon(+RequestCode::ZYGISK);
if (fd < 0) return fd;

View File

@@ -23,21 +23,6 @@ LOCAL_SRC_FILES := \
lz4/lib/xxhash.c
include $(BUILD_STATIC_LIBRARY)
# libbz2.a
include $(CLEAR_VARS)
LOCAL_MODULE := libbz2
LOCAL_C_INCLUDES := $(LOCAL_PATH)/bzip2
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_SRC_FILES := \
bzip2/blocksort.c \
bzip2/huffman.c \
bzip2/crctable.c \
bzip2/randtable.c \
bzip2/compress.c \
bzip2/decompress.c \
bzip2/bzlib.c
include $(BUILD_STATIC_LIBRARY)
# liblzma.a
include $(CLEAR_VARS)
LOCAL_MODULE := liblzma
@@ -233,33 +218,6 @@ LOCAL_SRC_FILES := \
lsplt/lsplt/src/main/jni/lsplt.cc
include $(BUILD_STATIC_LIBRARY)
# libz.a
include $(CLEAR_VARS)
LOCAL_MODULE:= libz
LOCAL_C_INCLUDES := $(LOCAL_PATH)/zlib
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_CFLAGS := \
-DHAVE_HIDDEN -DZLIB_CONST -Wall -Werror \
-Wno-unused -Wno-unused-parameter -Wno-deprecated-non-prototype
LOCAL_SRC_FILES := \
zlib/adler32.c \
zlib/compress.c \
zlib/cpu_features.c \
zlib/crc32.c \
zlib/deflate.c \
zlib/gzclose.c \
zlib/gzlib.c \
zlib/gzread.c \
zlib/gzwrite.c \
zlib/infback.c \
zlib/inffast.c \
zlib/inflate.c \
zlib/inftrees.c \
zlib/trees.c \
zlib/uncompr.c \
zlib/zutil.c
include $(BUILD_STATIC_LIBRARY)
# libzopfli.a
include $(CLEAR_VARS)
LOCAL_MODULE:= libzopfli

View File

@@ -39,7 +39,7 @@ fn write_if_diff<P: AsRef<Path>>(path: P, bytes: &[u8]) -> io::Result<()> {
pub fn gen_cxx_binding(name: &str) {
println!("cargo:rerun-if-changed=lib.rs");
let opt = Opt::default();
let gen = cxx_gen::generate_header_and_cc_with_path("lib.rs", &opt);
write_if_diff(format!("{}.cpp", name), gen.implementation.as_slice()).ok_or_exit();
write_if_diff(format!("{}.hpp", name), gen.header.as_slice()).ok_or_exit();
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();
}

View File

@@ -13,7 +13,7 @@ pub const APP_PACKAGE_NAME: &str = "com.topjohnwu.magisk";
pub const LOGFILE: &str = "/cache/magisk.log";
// data paths
const SECURE_DIR: &str = "/data/adb";
pub const SECURE_DIR: &str = "/data/adb";
pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules");
// tmpfs paths
@@ -25,3 +25,5 @@ pub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, "/modules");
pub const WORKERDIR: &str = concatcp!(INTERNAL_DIR, "/worker");
pub const DEVICEDIR: &str = concatcp!(INTERNAL_DIR, "/device");
pub const PREINITDEV: &str = concatcp!(DEVICEDIR, "/preinit");
pub const ROOTOVL: &str = concatcp!(INTERNAL_DIR, "/rootdir");
pub const ROOTMNT: &str = concatcp!(ROOTOVL, "/.mount_list");

View File

@@ -1,7 +1,7 @@
[package]
name = "magiskinit"
version = "0.0.0"
edition = "2021"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["staticlib"]
@@ -12,5 +12,5 @@ cxx-gen = { workspace = true }
[dependencies]
base = { path = "../base" }
magiskpolicy = { path = "../sepolicy" }
magiskpolicy = { path = "../sepolicy", default-features = false }
cxx = { workspace = true }

View File

@@ -1,7 +1,7 @@
use crate::gen::gen_cxx_binding;
use crate::codegen::gen_cxx_binding;
#[path = "../include/gen.rs"]
mod gen;
#[path = "../include/codegen.rs"]
mod codegen;
fn main() {
gen_cxx_binding("init-rs");

View File

@@ -118,7 +118,7 @@ static bool check_key_combo() {
return false;
}
void BootConfig::set(const kv_pairs &kv) {
void BootConfig::set(const kv_pairs &kv) noexcept {
for (const auto &[key, value] : kv) {
if (key == "androidboot.slot_suffix") {
// Many Amlogic devices are A-only but have slot_suffix...
@@ -126,10 +126,10 @@ void BootConfig::set(const kv_pairs &kv) {
LOGW("Skip invalid androidboot.slot_suffix=[normal]\n");
continue;
}
strscpy(slot, value.data(), sizeof(slot));
strscpy(slot.data(), value.data(), slot.size());
} else if (key == "androidboot.slot") {
slot[0] = '_';
strscpy(slot + 1, value.data(), sizeof(slot) - 1);
strscpy(slot.data() + 1, value.data(), slot.size() - 1);
} else if (key == "skip_initramfs") {
skip_initramfs = true;
} else if (key == "androidboot.force_normal_boot") {
@@ -137,13 +137,13 @@ void BootConfig::set(const kv_pairs &kv) {
} else if (key == "rootwait") {
rootwait = true;
} else if (key == "androidboot.android_dt_dir") {
strscpy(dt_dir, value.data(), sizeof(dt_dir));
strscpy(dt_dir.data(), value.data(), dt_dir.size());
} else if (key == "androidboot.hardware") {
strscpy(hardware, value.data(), sizeof(hardware));
strscpy(hardware.data(), value.data(), hardware.size());
} else if (key == "androidboot.hardware.platform") {
strscpy(hardware_plat, value.data(), sizeof(hardware_plat));
strscpy(hardware_plat.data(), value.data(), hardware_plat.size());
} else if (key == "androidboot.fstab_suffix") {
strscpy(fstab_suffix, value.data(), sizeof(fstab_suffix));
strscpy(fstab_suffix.data(), value.data(), fstab_suffix.size());
} else if (key == "qemu") {
emulator = true;
} else if (key == "androidboot.partition_map") {
@@ -151,34 +151,24 @@ void BootConfig::set(const kv_pairs &kv) {
// For example, "androidboot.partition_map=vdb,metadata;vdc,userdata" maps
// "vdb" to "metadata", and "vdc" to "userdata".
// https://android.googlesource.com/platform/system/core/+/refs/heads/android13-release/init/devices.cpp#191
partition_map = parse_partition_map(value);
for (const auto &[k, v]: parse_partition_map(value)) {
partition_map.emplace_back(k, v);
}
}
}
}
void BootConfig::print() {
LOGD("skip_initramfs=[%d]\n", skip_initramfs);
LOGD("force_normal_boot=[%d]\n", force_normal_boot);
LOGD("rootwait=[%d]\n", rootwait);
LOGD("slot=[%s]\n", slot);
LOGD("dt_dir=[%s]\n", dt_dir);
LOGD("fstab_suffix=[%s]\n", fstab_suffix);
LOGD("hardware=[%s]\n", hardware);
LOGD("hardware.platform=[%s]\n", hardware_plat);
LOGD("emulator=[%d]\n", emulator);
}
#define read_dt(name, key) \
ssprintf(file_name, sizeof(file_name), "%s/" name, dt_dir); \
ssprintf(file_name, sizeof(file_name), "%s/" name, dt_dir.data()); \
if (access(file_name, R_OK) == 0) { \
string data = full_read(file_name); \
if (!data.empty()) { \
data.pop_back(); \
strscpy(key, data.data(), sizeof(key)); \
strscpy(key.data(), data.data(), key.size()); \
} \
}
void BootConfig::init() {
void BootConfig::init() noexcept {
set(parse_cmdline(full_read("/proc/cmdline")));
set(parse_bootconfig(full_read("/proc/bootconfig")));
@@ -191,7 +181,7 @@ void BootConfig::init() {
});
if (dt_dir[0] == '\0')
strscpy(dt_dir, DEFAULT_DT_DIR, sizeof(dt_dir));
strscpy(dt_dir.data(), DEFAULT_DT_DIR, dt_dir.size());
char file_name[128];
read_dt("fstab_suffix", fstab_suffix)
@@ -201,34 +191,3 @@ void BootConfig::init() {
LOGD("Device config:\n");
print();
}
bool check_two_stage() {
if (access("/first_stage_ramdisk", F_OK) == 0)
return true;
if (access("/second_stage_resources", F_OK) == 0)
return true;
if (access("/system/bin/init", F_OK) == 0)
return true;
// Use the apex folder to determine whether 2SI (Android 10+)
if (access("/apex", F_OK) == 0)
return true;
// If we still have no indication, parse the original init and see what's up
mmap_data init(backup_init());
return init.contains("selinux_setup");
}
static void unxz_init(const char *init_xz, const char *init) {
LOGD("unxz %s -> %s\n", init_xz, init);
int fd = xopen(init, O_WRONLY | O_CREAT, 0777);
fd_stream ch(fd);
unxz(ch, mmap_data{init_xz});
close(fd);
clone_attr(init_xz, init);
unlink(init_xz);
}
const char *backup_init() {
if (access("/.backup/init.xz", F_OK) == 0)
unxz_init("/.backup/init.xz", "/.backup/init");
return "/.backup/init";
}

Some files were not shown because too many files have changed in this diff Show More