Compare commits

...

6 Commits

Author SHA1 Message Date
topjohnwu
64752f38e8 Do not unwrap when getting decoder and encoder
Or else things will crash mysteriously when unexpected input occurs
2025-12-05 03:40:18 -08:00
topjohnwu
9ac4b5ce7d Add proper lzma format detection 2025-12-05 03:40:18 -08:00
topjohnwu
505053f9b4 Properly support AVD with minor SDK version 2025-12-04 20:55:46 -08:00
topjohnwu
ccb264f33a Release Magisk v30.6
[skip ci]
2025-12-01 15:46:33 -08:00
topjohnwu
84f7d75d30 Update release.sh
Strip out all canary build logic
2025-12-01 15:27:01 -08:00
南宫雪珊
9a776c22d9 Revert "Use rootfs for magisktmp if possible" 2025-12-01 11:45:34 -08:00
13 changed files with 126 additions and 254 deletions

View File

@@ -30,4 +30,4 @@ android.nonFinalResIds=false
# Magisk
magisk.stubVersion=40
magisk.versionCode=30500
magisk.versionCode=30600

View File

@@ -1,5 +1,9 @@
# Magisk Changelog
### v30.6 (2025.12.1)
- [MagiskInit] Revert a change that could result in bootloops
### v30.5 (2025.12.1)
- [General] Improve commandline argument parsing logic

View File

@@ -53,6 +53,19 @@ static bool check_env(const char *name) {
return val != nullptr && val == "true"sv;
}
static bool guess_lzma(const uint8_t *buf, size_t len) {
// 0 : (pb * 5 + lp) * 9 + lc
// 1 - 4 : dict size, must be 2^n
// 5 - 12: all 0xFF
if (len <= 13) return false;
if (memcmp(buf, "\x5d", 1) != 0) return false;
uint32_t dict_sz = 0;
memcpy(&dict_sz, buf + 1, sizeof(dict_sz));
if (dict_sz == 0 || (dict_sz & (dict_sz - 1)) != 0) return false;
if (memcmp(buf + 5, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) != 0) return false;
return true;
}
FileFormat check_fmt(const void *buf, size_t len) {
if (CHECKED_MATCH(CHROMEOS_MAGIC)) {
return FileFormat::CHROMEOS;
@@ -66,8 +79,7 @@ FileFormat check_fmt(const void *buf, size_t len) {
return FileFormat::LZOP;
} else if (CHECKED_MATCH(XZ_MAGIC)) {
return FileFormat::XZ;
} else if (len >= 13 && memcmp(buf, "\x5d\x00\x00", 3) == 0
&& (((char *)buf)[12] == '\xff' || ((char *)buf)[12] == '\x00')) {
} else if (guess_lzma(static_cast<const uint8_t *>(buf), len)) {
return FileFormat::LZMA;
} else if (CHECKED_MATCH(BZIP_MAGIC)) {
return FileFormat::BZIP2;

View File

@@ -1,8 +1,6 @@
use crate::ffi::{FileFormat, check_fmt};
use base::nix::fcntl::OFlag;
use base::{
Chunker, FileOrStd, LoggedResult, ReadExt, ResultExt, Utf8CStr, Utf8CString, WriteExt, log_err,
};
use base::{Chunker, FileOrStd, LoggedResult, ReadExt, Utf8CStr, Utf8CString, WriteExt, log_err};
use bzip2::Compression as BzCompression;
use bzip2::read::BzDecoder;
use bzip2::write::BzEncoder;
@@ -218,16 +216,21 @@ impl<R: Read> Read for LZ4BlockDecoder<R> {
// Top-level APIs
pub fn get_encoder<'a, W: Write + 'a>(format: FileFormat, w: W) -> Box<dyn WriteFinish<W> + 'a> {
match format {
pub fn get_encoder<'a, W: Write + 'a>(
format: FileFormat,
w: W,
) -> std::io::Result<Box<dyn WriteFinish<W> + 'a>> {
Ok(match format {
FileFormat::XZ => {
let mut opt = XzOptions::with_preset(9);
opt.set_check_sum_type(CheckType::Crc32);
Box::new(XzWriter::new(w, opt).unwrap())
}
FileFormat::LZMA => {
Box::new(LzmaWriter::new_use_header(w, &LzmaOptions::with_preset(9), None).unwrap())
Box::new(XzWriter::new(w, opt)?)
}
FileFormat::LZMA => Box::new(LzmaWriter::new_use_header(
w,
&LzmaOptions::with_preset(9),
None,
)?),
FileFormat::BZIP2 => Box::new(BzEncoder::new(w, BzCompression::best())),
FileFormat::LZ4 => {
let encoder = LZ4FrameEncoderBuilder::new()
@@ -237,8 +240,7 @@ pub fn get_encoder<'a, W: Write + 'a>(format: FileFormat, w: W) -> Box<dyn Write
.block_checksum(BlockChecksum::BlockChecksumEnabled)
.level(9)
.auto_flush(true)
.build(w)
.unwrap();
.build(w)?;
Box::new(encoder)
}
FileFormat::LZ4_LEGACY => Box::new(LZ4BlockEncoder::new(w, false)),
@@ -250,23 +252,26 @@ pub fn get_encoder<'a, W: Write + 'a>(format: FileFormat, w: W) -> Box<dyn Write
maximum_block_splits: 1,
..Default::default()
};
Box::new(ZopFliEncoder::new_buffered(opt, BlockType::Dynamic, w).unwrap())
Box::new(ZopFliEncoder::new_buffered(opt, BlockType::Dynamic, w)?)
}
FileFormat::GZIP => Box::new(GzEncoder::new(w, GzCompression::best())),
_ => unreachable!(),
}
})
}
pub fn get_decoder<'a, R: Read + 'a>(format: FileFormat, r: R) -> Box<dyn Read + 'a> {
match format {
pub fn get_decoder<'a, R: Read + 'a>(
format: FileFormat,
r: R,
) -> std::io::Result<Box<dyn Read + 'a>> {
Ok(match format {
FileFormat::XZ => Box::new(XzReader::new(r, true)),
FileFormat::LZMA => Box::new(LzmaReader::new_mem_limit(r, u32::MAX, None).unwrap()),
FileFormat::LZMA => Box::new(LzmaReader::new_mem_limit(r, u32::MAX, None)?),
FileFormat::BZIP2 => Box::new(BzDecoder::new(r)),
FileFormat::LZ4 => Box::new(LZ4FrameDecoder::new(r).unwrap()),
FileFormat::LZ4 => Box::new(LZ4FrameDecoder::new(r)?),
FileFormat::LZ4_LG | FileFormat::LZ4_LEGACY => Box::new(LZ4BlockDecoder::new(r)),
FileFormat::ZOPFLI | FileFormat::GZIP => Box::new(MultiGzDecoder::new(r)),
_ => unreachable!(),
}
})
}
// C++ FFI
@@ -274,9 +279,9 @@ pub fn get_decoder<'a, R: Read + 'a>(format: FileFormat, r: R) -> Box<dyn Read +
pub fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };
let mut encoder = get_encoder(format, out_file.deref_mut());
let _: LoggedResult<()> = try {
encoder.write_all(in_bytes)?;
let mut encoder = get_encoder(format, out_file.deref_mut())?;
std::io::copy(&mut Cursor::new(in_bytes), encoder.deref_mut())?;
encoder.finish()?;
};
}
@@ -284,8 +289,10 @@ pub fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
pub fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };
let mut decoder = get_decoder(format, in_bytes);
std::io::copy(decoder.as_mut(), out_file.deref_mut()).log_ok();
let _: LoggedResult<()> = try {
let mut decoder = get_decoder(format, in_bytes)?;
std::io::copy(decoder.as_mut(), out_file.deref_mut())?;
};
}
// Command-line entry points
@@ -341,7 +348,7 @@ pub(crate) fn decompress_cmd(infile: &Utf8CStr, outfile: Option<&Utf8CStr>) -> L
FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)
};
let mut decoder = get_decoder(format, Cursor::new(buf).chain(input.as_file()));
let mut decoder = get_decoder(format, Cursor::new(buf).chain(input.as_file()))?;
std::io::copy(decoder.as_mut(), &mut output.as_file())?;
if rm_in {
@@ -384,7 +391,7 @@ pub(crate) fn compress_cmd(
FileOrStd::File(outfile)
};
let mut encoder = get_encoder(method, output.as_file());
let mut encoder = get_encoder(method, output.as_file())?;
std::io::copy(&mut input.as_file(), encoder.as_mut())?;
encoder.finish()?;

View File

@@ -691,8 +691,8 @@ impl CpioEntry {
if self.mode & S_IFMT != S_IFREG {
return false;
}
let mut encoder = get_encoder(FileFormat::XZ, Vec::new());
let Ok(data): std::io::Result<Vec<u8>> = (try {
let mut encoder = get_encoder(FileFormat::XZ, Vec::new())?;
encoder.write_all(&self.data)?;
encoder.finish()?
}) else {
@@ -710,7 +710,7 @@ impl CpioEntry {
}
let Ok(data): std::io::Result<Vec<u8>> = (try {
let mut decoder = get_decoder(FileFormat::XZ, Cursor::new(&self.data));
let mut decoder = get_decoder(FileFormat::XZ, Cursor::new(&self.data))?;
let mut data = Vec::new();
std::io::copy(decoder.as_mut(), &mut data)?;
data

View File

@@ -164,8 +164,8 @@ pub fn extract_boot_from_payload(
out_file.seek(SeekFrom::Start(out_offset))?;
let fmt = check_fmt(data);
let mut decoder = get_decoder(fmt, Cursor::new(data));
let Ok(_): std::io::Result<()> = (try {
let mut decoder = get_decoder(fmt, Cursor::new(data))?;
std::io::copy(decoder.as_mut(), &mut out_file)?;
}) else {
return Err(bad_payload!("decompression failed"));

View File

@@ -221,10 +221,7 @@ pub fn revert_unmount(pid: i32) {
// Unmount Magisk tmpfs and mounts from module files
for info in parse_mount_info("self") {
if info.source == "magisk"
|| info.root.starts_with("/adb/modules")
|| (info.fs_type == "rootfs" && info.root.starts_with("/magisk"))
{
if info.source == "magisk" || info.root.starts_with("/adb/modules") {
targets.push(info.target);
}
}

View File

@@ -1,10 +1,9 @@
use crate::ffi::{BootConfig, MagiskInit, backup_init, magisk_proxy_main};
use crate::logging::setup_klog;
use crate::mount::{is_rootfs, occupy, unoccupy};
use crate::mount::is_rootfs;
use crate::twostage::hexpatch_init_for_second_stage;
use base::libc::{basename, getpid, mount, umask};
use base::nix::mount::MsFlags;
use base::{LibcReturn, LoggedResult, ResultExt, Utf8CStr, cstr, info, nix, raw_cstr};
use base::{LibcReturn, LoggedResult, ResultExt, cstr, info, raw_cstr};
use std::ffi::{CStr, c_char};
use std::ptr::null;
@@ -32,7 +31,7 @@ impl MagiskInit {
fn first_stage(&self) {
info!("First Stage Init");
let rootfs_magisktmp = self.prepare_data(true);
self.prepare_data();
if !cstr!("/sdcard").exists() && !cstr!("/first_stage_ramdisk/sdcard").exists() {
self.hijack_init_with_switch_root();
@@ -42,28 +41,11 @@ impl MagiskInit {
// Fallback to hexpatch if /sdcard exists
hexpatch_init_for_second_stage(true);
}
if rootfs_magisktmp {
info!("Occupy /data");
occupy(cstr!("/data"));
}
}
fn second_stage(&mut self) {
info!("Second Stage Init");
if unoccupy(cstr!("/data")) {
nix::mount::mount(
None::<&Utf8CStr>,
cstr!("/data"),
None::<&Utf8CStr>,
MsFlags::MS_REMOUNT,
Some(cstr!("size=100%")),
)
.check_os_err("mount", Some("/data"), Some("tmpfs"))
.log_ok();
}
cstr!("/init").unmount().ok();
cstr!("/system/bin/init").unmount().ok(); // just in case
cstr!("/data/init").remove().ok();
@@ -89,7 +71,7 @@ impl MagiskInit {
fn legacy_system_as_root(&mut self) {
info!("Legacy SAR Init");
self.prepare_data(false);
self.prepare_data();
let is_two_stage = self.mount_system_root();
if is_two_stage {
hexpatch_init_for_second_stage(false);
@@ -100,7 +82,7 @@ impl MagiskInit {
fn rootfs(&mut self) {
info!("RootFS Init");
self.prepare_data(false);
self.prepare_data();
self.restore_ramdisk_init();
self.patch_rw_root();
}

View File

@@ -1,6 +1,4 @@
use crate::ffi::MagiskInit;
use base::WalkResult::{Continue, Skip};
use base::nix::mount::{MntFlags, mount, umount2};
use base::{
Directory, FsPathBuilder, LibcReturn, LoggedResult, ResultExt, Utf8CStr, cstr, debug, libc,
nix, parse_mount_info, raw_cstr,
@@ -64,54 +62,6 @@ pub(crate) fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool {
false
}
pub(crate) fn occupy(path: &Utf8CStr) {
Directory::open(path)
.map(|mut dir| {
dir.pre_order_walk(|entry| {
let mut path = cstr::buf::default();
entry.resolve_path(&mut path)?;
let path = path.as_utf8_cstr();
mount(
Some(path),
path,
None::<&Utf8CStr>,
MsFlags::MS_BIND | MsFlags::MS_RDONLY,
None::<&Utf8CStr>,
)
.check_os_err("occupy", Some(path), None)?;
Ok(Continue)
})
.log_ok();
})
.log_ok();
}
pub(crate) fn unoccupy(path: &Utf8CStr) -> bool {
let mut ok = false;
Directory::open(path)
.map(|mut dir| {
ok = dir
.pre_order_walk(|entry| {
let mut path = cstr::buf::default();
entry.resolve_path(&mut path)?;
let path = path.as_utf8_cstr();
umount2(path, MntFlags::MNT_DETACH).check_os_err(
"unoccupy",
Some(path),
None,
)?;
if entry.is_dir() {
Ok(Skip)
} else {
Ok(Continue)
}
})
.is_ok();
})
.log_ok();
ok
}
const RAMFS_MAGIC: u32 = 0x858458f6;
pub(crate) fn is_rootfs() -> bool {
@@ -123,44 +73,22 @@ pub(crate) fn is_rootfs() -> bool {
}
impl MagiskInit {
pub(crate) fn prepare_data(&self, use_rootfs: bool) -> bool {
pub(crate) fn prepare_data(&self) {
debug!("Setup data tmp");
cstr!("/data").mkdir(0o755).log_ok();
nix::mount::mount(
Some(cstr!("magisk")),
cstr!("/data"),
Some(cstr!("tmpfs")),
MsFlags::empty(),
Some(cstr!("mode=755")),
)
.check_os_err("mount", Some("/data"), Some("tmpfs"))
.log_ok();
let mut rootfs_magisktmp = false;
if use_rootfs {
cstr!("/magisk").mkdir(0o755).log_ok();
rootfs_magisktmp = cstr!("/magisk")
.bind_mount_to(cstr!("/data"), false)
.is_ok();
}
if rootfs_magisktmp {
cstr!("/init")
.rename_to(cstr!("/magisk/magiskinit"))
.log_ok();
cstr!("/.backup").copy_to(cstr!("/magisk/.backup")).ok();
cstr!("/overlay.d")
.rename_to(cstr!("/magisk/overlay.d"))
.ok();
} else {
nix::mount::mount(
Some(cstr!("magisk")),
cstr!("/data"),
Some(cstr!("tmpfs")),
MsFlags::empty(),
Some(cstr!("mode=755")),
)
.check_os_err("mount", Some("/data"), Some("tmpfs"))
.log_ok();
cstr!("/init").copy_to(cstr!("/data/magiskinit")).ok();
cstr!("/.backup").copy_to(cstr!("/data/.backup")).ok();
cstr!("/overlay.d").copy_to(cstr!("/data/overlay.d")).ok();
}
rootfs_magisktmp
cstr!("/init").copy_to(cstr!("/data/magiskinit")).ok();
cstr!("/.backup").copy_to(cstr!("/data/.backup")).ok();
cstr!("/overlay.d").copy_to(cstr!("/data/overlay.d")).ok();
}
pub(crate) fn exec_init(&mut self) {

View File

@@ -84,14 +84,20 @@ impl MagiskInit {
.log_ok();
debug!("Symlink /storage/self/primary -> /system/system/bin/init");
}
cstr!("/init").rename_to(cstr!("/sdcard")).log_ok();
// Binding mounting from rootfs is not supported before Linux 3.12
cstr!("/sdcard")
.create(OFlag::O_RDONLY | OFlag::O_CLOEXEC, 0)
.log_ok();
cstr!("/data/magiskinit")
// First try to mount magiskinit from rootfs to workaround Samsung RKP
if cstr!("/sdcard")
.bind_mount_to(cstr!("/sdcard"), false)
.log_ok();
debug!("Bind mount /data/magiskinit -> /sdcard");
.is_ok()
{
debug!("Bind mount /sdcard -> /sdcard");
} else {
// Binding mounting from rootfs is not supported before Linux 3.12
cstr!("/data/magiskinit")
.bind_mount_to(cstr!("/sdcard"), false)
.log_ok();
debug!("Bind mount /data/magiskinit -> /sdcard");
}
}
}

View File

@@ -110,7 +110,7 @@ impl SePolicy {
allow(["kernel"], ["rootfs", "tmpfs"], ["chr_file"], ["write"]);
// Allow magiskinit daemon to handle mock selinuxfs
allow(["kernel"], ["rootfs", "tmpfs"], ["fifo_file"], ["open", "read", "write"]);
allow(["kernel"], ["tmpfs"], ["fifo_file"], ["open", "read", "write"]);
// For relabelling files
allow(["rootfs"], ["labeledfs", "tmpfs"], ["filesystem"], ["associate"]);

View File

@@ -32,17 +32,17 @@ case $(uname -m) in
esac
cleanup() {
pkill -INT -P $$
wait
trap - EXIT
rm -f magisk_*.img
"$avd" delete avd -n test
exit 1
}
test_error() {
trap - EXIT
print_error "! An error occurred"
pkill -INT -P $$
wait
cleanup
exit 1
}
wait_for_boot() {
@@ -72,13 +72,14 @@ wait_emu() {
dump_vars() {
local val
for name in $@; do
for name in $@ emu_args; do
eval val=\$$name
echo $name=\"$val\"\;
done
}
resolve_vars() {
set +x
local arg_list="$1"
local ver=$2
local type=$3
@@ -138,8 +139,14 @@ dl_emu() {
setup_emu() {
local avd_pkg=$1
local ver=$2
dl_emu $avd_pkg
echo no | "$avd" create avd -f -n test -k $avd_pkg
# avdmanager is outdated, it might not set the proper target
local ini=$ANDROID_AVD_HOME/test.ini
sed "s:^target\s*=.*:target=android-$ver:g" $ini > $ini.new
mv $ini.new $ini
}
test_emu() {
@@ -169,16 +176,15 @@ test_emu() {
}
test_main() {
local avd_pkg ramdisk vars
vars=$(resolve_vars "emu_args avd_pkg ramdisk" $1 $2)
eval $vars
local ver avd_pkg ramdisk
eval $(resolve_vars "ver avd_pkg ramdisk" $1 $2)
# Specify an explicit port so that tests can run with other emulators running at the same time
local emu_port=5682
emu_args="$emu_args -port $emu_port"
export ANDROID_SERIAL="emulator-$emu_port"
setup_emu "$avd_pkg"
setup_emu "$avd_pkg" $ver
# Restart ADB daemon just in case
adb kill-server
@@ -211,24 +217,21 @@ test_main() {
test_emu release
fi
# Cleanup
rm -f magisk_*.img
"$avd" delete avd -n test
cleanup
}
run_main() {
local avd_pkg vars
vars=$(resolve_vars "emu_args avd_pkg" $1 $2)
eval $vars
setup_emu "$avd_pkg"
local ver avd_pkg
eval $(resolve_vars "ver avd_pkg" $1 $2)
setup_emu "$avd_pkg" $ver
print_title "* Launching $avd_pkg"
"$emu" @test $emu_args 2>/dev/null
cleanup
}
dl_main() {
local avd_pkg vars
vars=$(resolve_vars "avd_pkg" $1 $2)
eval $vars
local avd_pkg
eval $(resolve_vars "avd_pkg" $1 $2)
print_title "* Downloading $avd_pkg"
dl_emu "$avd_pkg"
}

View File

@@ -36,25 +36,6 @@ disable_version_config() {
sed -i "s:^version=:# version=:g" $CONFIG
}
bump_canary_version() {
# Update version code
local code=$(grep_prop magisk.versionCode $GCONFIG)
code=$((code + 1))
local tag="canary-$code"
sed -i "s:versionCode=.*:versionCode=${code}:g" $GCONFIG
# Commit version code changes
git add -u .
git status
git commit -m "Release new canary build" -m "[skip ci]"
git tag $tag
# Update version name
local ver=$(git rev-parse --short=8 HEAD)
sed -i "s:version=.*:version=${ver}:g" $CONFIG
sed -i "1s:.*:## Magisk (${ver}) (${code}):" $NOTES
}
# $1 = ver
set_version() {
local ver=$1
@@ -69,89 +50,45 @@ set_version() {
git add -u .
git status
git commit -m "Release Magisk v$ver" -m "[skip ci]"
git tag $tag
}
build_apk() {
# $1 = ver
build() {
[ -z $1 ] && exit 1
local ver=$1
git pull
set_version $ver
$BUILDCMD clean
$BUILDCMD all
$BUILDCMD -r all
}
build_canary() {
bump_canary_version
build_apk
}
# $1 = ver
build_public() {
[ -z $1 ] && exit 1
local ver=$1
set_version $ver
build_apk
}
upload() {
# Verify pattern
[[ "$1" =~ canary|beta|stable ]]
local type=$1
gh auth status
local latest_tag=$(git describe --abbrev=0 --tags)
local ver=$(grep_prop version $CONFIG)
local code=$(grep_prop magisk.versionCode $GCONFIG)
local out=$(grep_prop outdir $CONFIG)
local tag title
local ver=$(echo - | awk "{ print $code / 1000 }")
local tag="v$ver"
local title="Magisk v$ver"
local out=$(grep_prop outdir $CONFIG)
if [ -z $out ]; then
out=out
fi
git tag $tag
git push origin master
git push --tags
# Prepare release notes
tail -n +3 $NOTES > release.md
case $type in
canary )
tag="canary-$code"
title="Magisk ($ver) ($code)"
# Publish release
local release_apk="Magisk-v${ver}.apk"
cp $out/app-release.apk $release_apk
gh release create --verify-tag $tag -p -t "$title" -F release.md $release_apk $out/app-debug.apk $NOTES
# Assert tag format
[ $latest_tag = $tag ]
# Publish release
gh release create --verify-tag $tag -p -t "$title" -F release.md $out/app-release.apk $out/app-debug.apk $NOTES
;;
beta|stable )
tag="v$ver"
title="Magisk v$ver"
# Assert tag format
[ $latest_tag = $tag ]
# Publish release
local release_apk="Magisk-v${ver}.apk"
cp $out/app-release.apk $release_apk
gh release create --verify-tag $tag -p -t "$title" -F release.md $release_apk $out/app-debug.apk $NOTES
rm -f $release_apk
;;
esac
# If publishing stable, make it not prerelease and explicitly latest
if [ $type = "stable" ]; then
gh release edit $tag --prerelease=false --latest
fi
rm -f release.md
}
revert() {
local latest_tag=$(git describe --abbrev=0 --tags)
git tag -d $latest_tag
git reset --hard HEAD~
rm -f $release_apk release.md
}
# Use GNU sed on macOS
@@ -160,14 +97,10 @@ if command -v gsed >/dev/null; then
export -f sed
fi
git pull
trap disable_version_config EXIT
ensure_config
case $1 in
canary ) build_canary ;;
public ) build_public $2 ;;
upload ) upload $2 ;;
revert ) revert ;;
build ) build $2 ;;
upload ) upload ;;
* ) exit 1 ;;
esac