You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Compare commits
14 Commits
v30.2
...
db8d832707
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db8d832707 | ||
|
|
8dc23d0ead | ||
|
|
b4287700d5 | ||
|
|
8d10ab89f2 | ||
|
|
49fdc1addb | ||
|
|
1333d3b986 | ||
|
|
335146a6a2 | ||
|
|
eaf9527971 | ||
|
|
da937a88c8 | ||
|
|
9476e7282d | ||
|
|
251c3c3e0e | ||
|
|
cd0eca20b0 | ||
|
|
6839cb9ab2 | ||
|
|
d11a3397d8 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -105,7 +105,7 @@ jobs:
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Run AVD test
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
AVD_TEST_LOG: 1
|
||||
run: scripts/avd.sh test ${{ matrix.version }} ${{ matrix.type }}
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Run AVD test
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
FORCE_32_BIT: 1
|
||||
AVD_TEST_LOG: 1
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
|
||||
|
||||
- name: Run Cuttlefish test
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
run: sudo -E -u $USER scripts/cuttlefish.sh test
|
||||
|
||||
- name: Upload logs on error
|
||||
|
||||
@@ -24,7 +24,6 @@ LOCAL_SRC_FILES := \
|
||||
core/core-rs.cpp \
|
||||
core/resetprop/resetprop.cpp \
|
||||
core/su/su.cpp \
|
||||
core/su/connect.cpp \
|
||||
core/zygisk/entry.cpp \
|
||||
core/zygisk/module.cpp \
|
||||
core/zygisk/hook.cpp \
|
||||
@@ -87,7 +86,6 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libboot-rs
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
boot/main.cpp \
|
||||
boot/bootimg.cpp \
|
||||
boot/format.cpp \
|
||||
boot/boot-rs.cpp
|
||||
|
||||
@@ -140,8 +140,14 @@ pub struct FileAttr {
|
||||
pub con: crate::Utf8CStrBufArr<128>,
|
||||
}
|
||||
|
||||
impl Default for FileAttr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl FileAttr {
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
FileAttr {
|
||||
st: unsafe { mem::zeroed() },
|
||||
#[cfg(feature = "selinux")]
|
||||
|
||||
@@ -551,8 +551,8 @@ bool boot_img::verify(const char *cert) const {
|
||||
return rust::verify_boot_image(*this, cert);
|
||||
}
|
||||
|
||||
int split_image_dtb(const char *filename, bool skip_decomp) {
|
||||
mmap_data img(filename);
|
||||
int split_image_dtb(rust::Utf8CStr filename, bool skip_decomp) {
|
||||
mmap_data img(filename.data());
|
||||
|
||||
if (size_t off = find_dtb_offset(img.buf(), img.sz()); off > 0) {
|
||||
FileFormat fmt = check_fmt_lg(img.buf(), img.sz());
|
||||
@@ -566,13 +566,13 @@ int split_image_dtb(const char *filename, bool skip_decomp) {
|
||||
dump(img.buf() + off, img.sz() - off, KER_DTB_FILE);
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "Cannot find DTB in %s\n", filename);
|
||||
fprintf(stderr, "Cannot find DTB in %s\n", filename.data());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int unpack(const char *image, bool skip_decomp, bool hdr) {
|
||||
const boot_img boot(image);
|
||||
int unpack(rust::Utf8CStr image, bool skip_decomp, bool hdr) {
|
||||
const boot_img boot(image.data());
|
||||
|
||||
if (hdr)
|
||||
boot.hdr->dump_hdr_file();
|
||||
@@ -656,9 +656,9 @@ write_zero(fd, align_padding(lseek(fd, 0, SEEK_CUR) - off.header, page_size))
|
||||
|
||||
#define file_align() file_align_with(boot.hdr->page_size())
|
||||
|
||||
void repack(const char *src_img, const char *out_img, bool skip_comp) {
|
||||
const boot_img boot(src_img);
|
||||
fprintf(stderr, "Repack to boot image: [%s]\n", out_img);
|
||||
void repack(rust::Utf8CStr src_img, rust::Utf8CStr out_img, bool skip_comp) {
|
||||
const boot_img boot(src_img.data());
|
||||
fprintf(stderr, "Repack to boot image: [%s]\n", out_img.data());
|
||||
|
||||
struct {
|
||||
uint32_t header;
|
||||
@@ -687,7 +687,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
|
||||
***************/
|
||||
|
||||
// Create new image
|
||||
int fd = creat(out_img, 0644);
|
||||
int fd = creat(out_img.data(), 0644);
|
||||
|
||||
if (boot.flags[DHTB_FLAG]) {
|
||||
// Skip DHTB header
|
||||
@@ -899,7 +899,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
|
||||
******************/
|
||||
|
||||
// Map output image as rw
|
||||
mmap_data out(out_img, true);
|
||||
mmap_data out(out_img.data(), true);
|
||||
|
||||
// MTK headers
|
||||
if (boot.flags[MTK_KERNEL]) {
|
||||
@@ -997,8 +997,8 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int verify(const char *image, const char *cert) {
|
||||
const boot_img boot(image);
|
||||
int verify(rust::Utf8CStr image, const char *cert) {
|
||||
const boot_img boot(image.data());
|
||||
if (cert == nullptr) {
|
||||
// Boot image parsing already checks if the image is signed
|
||||
return boot.flags[AVB1_SIGNED_FLAG] ? 0 : 1;
|
||||
@@ -1008,14 +1008,14 @@ int verify(const char *image, const char *cert) {
|
||||
}
|
||||
}
|
||||
|
||||
int sign(const char *image, const char *name, const char *cert, const char *key) {
|
||||
const boot_img boot(image);
|
||||
auto sig = rust::sign_boot_image(boot.payload, name, cert, key);
|
||||
int sign(rust::Utf8CStr image, rust::Utf8CStr name, const char *cert, const char *key) {
|
||||
const boot_img boot(image.data());
|
||||
auto sig = rust::sign_boot_image(boot.payload, name.data(), cert, key);
|
||||
if (sig.empty())
|
||||
return 1;
|
||||
|
||||
auto eof = boot.tail.buf() - boot.map.buf();
|
||||
int fd = xopen(image, O_WRONLY | O_CLOEXEC);
|
||||
int fd = xopen(image.data(), O_WRONLY | O_CLOEXEC);
|
||||
if (lseek(fd, eof, SEEK_SET) != eof || xwrite(fd, sig.data(), sig.size()) != sig.size()) {
|
||||
close(fd);
|
||||
return 1;
|
||||
|
||||
430
native/src/boot/cli.rs
Normal file
430
native/src/boot/cli.rs
Normal file
@@ -0,0 +1,430 @@
|
||||
use crate::compress::{compress, decompress};
|
||||
use crate::cpio::{cpio_commands, print_cpio_usage};
|
||||
use crate::dtb::{DtbAction, dtb_commands, print_dtb_usage};
|
||||
use crate::ffi::{FileFormat, cleanup, repack, sign, split_image_dtb, unpack, verify};
|
||||
use crate::patch::hexpatch;
|
||||
use crate::payload::extract_boot_from_payload;
|
||||
use crate::sign::sha1_hash;
|
||||
use argh::FromArgs;
|
||||
use base::{
|
||||
EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, cmdline_logging, cstr,
|
||||
libc::umask, log_err, map_args,
|
||||
};
|
||||
use std::ffi::c_char;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct Cli {
|
||||
#[argh(subcommand)]
|
||||
action: Action,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand)]
|
||||
enum Action {
|
||||
Unpack(Unpack),
|
||||
Repack(Repack),
|
||||
Verify(Verify),
|
||||
Sign(Sign),
|
||||
Extract(Extract),
|
||||
HexPatch(HexPatch),
|
||||
Cpio(Cpio),
|
||||
Dtb(Dtb),
|
||||
Split(Split),
|
||||
Sha1(Sha1),
|
||||
Cleanup(Cleanup),
|
||||
Compress(Compress),
|
||||
Decompress(Decompress),
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "unpack")]
|
||||
struct Unpack {
|
||||
#[argh(switch, short = 'n')]
|
||||
no_decompress: bool,
|
||||
#[argh(switch, short = 'h')]
|
||||
dump_header: bool,
|
||||
#[argh(positional)]
|
||||
img: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "repack")]
|
||||
struct Repack {
|
||||
#[argh(switch, short = 'n')]
|
||||
no_compress: bool,
|
||||
#[argh(positional)]
|
||||
img: String,
|
||||
#[argh(positional, default = r#""new-boot.img".to_string()"#)]
|
||||
out: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "verify")]
|
||||
struct Verify {
|
||||
#[argh(positional)]
|
||||
img: String,
|
||||
#[argh(positional)]
|
||||
cert: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "sign")]
|
||||
struct Sign {
|
||||
#[argh(positional)]
|
||||
img: String,
|
||||
#[argh(positional)]
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "extract")]
|
||||
struct Extract {
|
||||
#[argh(positional)]
|
||||
payload: String,
|
||||
#[argh(positional)]
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "hexpatch")]
|
||||
struct HexPatch {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(positional)]
|
||||
src: String,
|
||||
#[argh(positional)]
|
||||
dest: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "cpio")]
|
||||
struct Cpio {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(positional)]
|
||||
cmds: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "dtb")]
|
||||
struct Dtb {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(subcommand)]
|
||||
action: DtbAction,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "split")]
|
||||
struct Split {
|
||||
#[argh(switch, short = 'n')]
|
||||
no_decompress: bool,
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "sha1")]
|
||||
struct Sha1 {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "cleanup")]
|
||||
struct Cleanup {}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "compress")]
|
||||
struct Compress {
|
||||
#[argh(option, short = 'f', default = r#""gzip".to_string()"#)]
|
||||
format: String,
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(positional)]
|
||||
out: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "decompress")]
|
||||
struct Decompress {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(positional)]
|
||||
out: Option<String>,
|
||||
}
|
||||
|
||||
fn print_usage(cmd: &str) {
|
||||
eprintln!(
|
||||
r#"MagiskBoot - Boot Image Modification Tool
|
||||
|
||||
Usage: {} <action> [args...]
|
||||
|
||||
Supported actions:
|
||||
unpack [-n] [-h] <bootimg>
|
||||
Unpack <bootimg> to its individual components, each component to
|
||||
a file with its corresponding file name in the current directory.
|
||||
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
|
||||
dtb, extra, and recovery_dtbo.
|
||||
By default, each component will be decompressed on-the-fly.
|
||||
If '-n' is provided, all decompression operations will be skipped;
|
||||
each component will remain untouched, dumped in its original format.
|
||||
If '-h' is provided, the boot image header information will be
|
||||
dumped to the file 'header', which can be used to modify header
|
||||
configurations during repacking.
|
||||
Return values:
|
||||
0:valid 1:error 2:chromeos
|
||||
|
||||
repack [-n] <origbootimg> [outbootimg]
|
||||
Repack boot image components using files from the current directory
|
||||
to [outbootimg], or 'new-boot.img' if not specified. Current directory
|
||||
should only contain required files for [outbootimg], or incorrect
|
||||
[outbootimg] may be produced.
|
||||
<origbootimg> is the original boot image used to unpack the components.
|
||||
By default, each component will be automatically compressed using its
|
||||
corresponding format detected in <origbootimg>. If a component file
|
||||
in the current directory is already compressed, then no addition
|
||||
compression will be performed for that specific component.
|
||||
If '-n' is provided, all compression operations will be skipped.
|
||||
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
|
||||
the boot image's vbmeta header will be set.
|
||||
|
||||
verify <bootimg> [x509.pem]
|
||||
Check whether the boot image is signed with AVB 1.0 signature.
|
||||
Optionally provide a certificate to verify whether the image is
|
||||
signed by the public key certificate.
|
||||
Return value:
|
||||
0:valid 1:error
|
||||
|
||||
sign <bootimg> [name] [x509.pem pk8]
|
||||
Sign <bootimg> with AVB 1.0 signature.
|
||||
Optionally provide the name of the image (default: '/boot').
|
||||
Optionally provide the certificate/private key pair for signing.
|
||||
If the certificate/private key pair is not provided, the AOSP
|
||||
verity key bundled in the executable will be used.
|
||||
|
||||
extract <payload.bin> [partition] [outfile]
|
||||
Extract [partition] from <payload.bin> to [outfile].
|
||||
If [outfile] is not specified, then output to '[partition].img'.
|
||||
If [partition] is not specified, then attempt to extract either
|
||||
'init_boot' or 'boot'. Which partition was chosen can be determined
|
||||
by whichever 'init_boot.img' or 'boot.img' exists.
|
||||
<payload.bin> can be '-' to be STDIN.
|
||||
|
||||
hexpatch <file> <hexpattern1> <hexpattern2>
|
||||
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
|
||||
|
||||
cpio <incpio> [commands...]
|
||||
Do cpio commands to <incpio> (modifications are done in-place).
|
||||
Each command is a single argument; add quotes for each command.
|
||||
See "cpio --help" for supported commands.
|
||||
|
||||
dtb <file> <action> [args...]
|
||||
Do dtb related actions to <file>.
|
||||
See "dtb --help" for supported actions.
|
||||
|
||||
split [-n] <file>
|
||||
Split image.*-dtb into kernel + kernel_dtb.
|
||||
If '-n' is provided, decompression operations will be skipped;
|
||||
the kernel will remain untouched, split in its original format.
|
||||
|
||||
sha1 <file>
|
||||
Print the SHA1 checksum for <file>
|
||||
|
||||
cleanup
|
||||
Cleanup the current working directory
|
||||
|
||||
compress[=format] <infile> [outfile]
|
||||
Compress <infile> with [format] to [outfile].
|
||||
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
|
||||
If [format] is not specified, then gzip will be used.
|
||||
If [outfile] is not specified, then <infile> will be replaced
|
||||
with another file suffixed with a matching file extension.
|
||||
Supported formats:
|
||||
|
||||
{1}
|
||||
|
||||
decompress <infile> [outfile]
|
||||
Detect format and decompress <infile> to [outfile].
|
||||
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
|
||||
If [outfile] is not specified, then <infile> will be replaced
|
||||
with another file removing its archive format file extension.
|
||||
Supported formats:
|
||||
|
||||
{1}
|
||||
"#,
|
||||
cmd,
|
||||
FileFormat::formats()
|
||||
);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main(argc: i32, argv: *const *const c_char, _envp: *const *const c_char) -> i32 {
|
||||
cmdline_logging();
|
||||
unsafe { umask(0) };
|
||||
let res: LoggedResult<()> = try {
|
||||
let mut cmds = map_args(argc, argv)?;
|
||||
if argc < 2 {
|
||||
print_usage(cmds.first().unwrap_or(&"magiskboot"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if cmds[1].starts_with("--") {
|
||||
cmds[1] = &cmds[1][2..];
|
||||
}
|
||||
|
||||
if let Some(fmt) = str::strip_prefix(cmds[1], "compress=") {
|
||||
cmds.insert(1, "compress");
|
||||
cmds.insert(2, "-f");
|
||||
cmds[3] = fmt;
|
||||
}
|
||||
|
||||
let mut cli = Cli::from_args(&[cmds[0]], &cmds[1..]).on_early_exit(|| match cmds.get(1) {
|
||||
Some(&"dtb") => print_dtb_usage(),
|
||||
Some(&"cpio") => print_cpio_usage(),
|
||||
_ => print_usage(cmds[0]),
|
||||
});
|
||||
match cli.action {
|
||||
Action::Unpack(Unpack {
|
||||
no_decompress,
|
||||
dump_header,
|
||||
ref mut img,
|
||||
}) => return unpack(Utf8CStr::from_string(img), no_decompress, dump_header),
|
||||
Action::Repack(Repack {
|
||||
no_compress,
|
||||
ref mut img,
|
||||
ref mut out,
|
||||
}) => {
|
||||
repack(
|
||||
Utf8CStr::from_string(img),
|
||||
Utf8CStr::from_string(out),
|
||||
no_compress,
|
||||
);
|
||||
}
|
||||
Action::Verify(Verify {
|
||||
ref mut img,
|
||||
ref mut cert,
|
||||
}) => {
|
||||
return unsafe {
|
||||
verify(
|
||||
Utf8CStr::from_string(img),
|
||||
cert.as_mut()
|
||||
.map(|x| Utf8CStr::from_string(x).as_ptr())
|
||||
.unwrap_or(std::ptr::null()),
|
||||
)
|
||||
};
|
||||
}
|
||||
Action::Sign(Sign {
|
||||
ref mut img,
|
||||
ref mut args,
|
||||
}) => {
|
||||
let (pem, pk8) = match args.get_mut(1..=2) {
|
||||
Some([pem, pk8]) => (
|
||||
Utf8CStr::from_string(pem).as_ptr(),
|
||||
Utf8CStr::from_string(pk8).as_ptr(),
|
||||
),
|
||||
_ => (std::ptr::null(), std::ptr::null()),
|
||||
};
|
||||
return unsafe {
|
||||
sign(
|
||||
Utf8CStr::from_string(img),
|
||||
args.first_mut()
|
||||
.map(Utf8CStr::from_string)
|
||||
.unwrap_or(cstr!("/boot")),
|
||||
pem,
|
||||
pk8,
|
||||
)
|
||||
};
|
||||
}
|
||||
Action::Extract(Extract {
|
||||
ref payload,
|
||||
ref args,
|
||||
}) => {
|
||||
if args.len() > 2 {
|
||||
Err(log_err!("Too many arguments"))?;
|
||||
}
|
||||
extract_boot_from_payload(
|
||||
payload,
|
||||
args.first().map(|x| x.as_str()),
|
||||
args.get(1).map(|x| x.as_str()),
|
||||
)
|
||||
.log_with_msg(|w| w.write_str("Failed to extract from payload"))?;
|
||||
}
|
||||
Action::HexPatch(HexPatch {
|
||||
ref mut file,
|
||||
ref mut src,
|
||||
ref mut dest,
|
||||
}) => {
|
||||
if !hexpatch(
|
||||
file,
|
||||
Utf8CStr::from_string(src),
|
||||
Utf8CStr::from_string(dest),
|
||||
) {
|
||||
Err(log_err!("Failed to patch"))?;
|
||||
}
|
||||
}
|
||||
Action::Cpio(Cpio {
|
||||
ref mut file,
|
||||
ref mut cmds,
|
||||
}) => {
|
||||
return if cpio_commands(file, cmds)
|
||||
.log_with_msg(|w| w.write_str("Failed to process cpio"))?
|
||||
{
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
}
|
||||
Action::Dtb(Dtb {
|
||||
ref mut file,
|
||||
ref action,
|
||||
}) => {
|
||||
return if dtb_commands(file, action)
|
||||
.log_with_msg(|w| w.write_str("Failed to process dtb"))?
|
||||
{
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
}
|
||||
Action::Split(Split {
|
||||
no_decompress,
|
||||
ref mut file,
|
||||
}) => {
|
||||
return split_image_dtb(Utf8CStr::from_string(file), no_decompress);
|
||||
}
|
||||
Action::Sha1(Sha1 { ref mut file }) => {
|
||||
let file = MappedFile::open(Utf8CStr::from_string(file))?;
|
||||
let mut sha1 = [0u8; 20];
|
||||
sha1_hash(file.as_ref(), &mut sha1);
|
||||
for byte in &sha1 {
|
||||
print!("{byte:02x}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Action::Cleanup(_) => {
|
||||
eprintln!("Cleaning up...");
|
||||
cleanup();
|
||||
}
|
||||
Action::Decompress(Decompress {
|
||||
ref mut file,
|
||||
ref mut out,
|
||||
}) => {
|
||||
decompress(file, out.as_mut())?;
|
||||
}
|
||||
Action::Compress(Compress {
|
||||
ref mut file,
|
||||
ref format,
|
||||
ref mut out,
|
||||
}) => {
|
||||
compress(
|
||||
FileFormat::from_str(format).unwrap_or(FileFormat::UNKNOWN),
|
||||
file,
|
||||
out.as_mut(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
if res.is_ok() { 0 } else { 1 }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::ffi::FileFormat;
|
||||
use base::{Chunker, LoggedResult, WriteExt};
|
||||
use crate::ffi::{FileFormat, check_fmt};
|
||||
use base::libc::{O_RDONLY, O_TRUNC, O_WRONLY};
|
||||
use base::{Chunker, LoggedResult, Utf8CStr, WriteExt, error, log_err};
|
||||
use bytemuck::bytes_of_mut;
|
||||
use bzip2::{Compression as BzCompression, write::BzDecoder, write::BzEncoder};
|
||||
use flate2::{Compression as GzCompression, write::GzEncoder, write::MultiGzDecoder};
|
||||
@@ -9,11 +10,11 @@ use lz4::{
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Read, Write};
|
||||
use std::io::{BufWriter, Read, Stdin, Stdout, Write, stdin, stdout};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::DerefMut;
|
||||
use std::os::fd::{FromRawFd, RawFd};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||
use xz2::{
|
||||
stream::{Check as LzmaCheck, Filters as LzmaFilters, LzmaOptions, Stream as LzmaStream},
|
||||
write::{XzDecoder, XzEncoder},
|
||||
@@ -424,3 +425,116 @@ pub fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {
|
||||
decoder.finish()?;
|
||||
};
|
||||
}
|
||||
|
||||
enum AsRawFdFile {
|
||||
Stdin(Stdin),
|
||||
Stdout(Stdout),
|
||||
File(File),
|
||||
}
|
||||
|
||||
impl AsRawFd for AsRawFdFile {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
match self {
|
||||
AsRawFdFile::Stdin(stdin) => stdin.as_raw_fd(),
|
||||
AsRawFdFile::Stdout(stdout) => stdout.as_raw_fd(),
|
||||
AsRawFdFile::File(file) => file.as_raw_fd(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decompress(infile: &mut String, outfile: Option<&mut String>) -> LoggedResult<()> {
|
||||
let in_std = infile == "-";
|
||||
let mut rm_in = false;
|
||||
|
||||
let raw_in = if in_std {
|
||||
AsRawFdFile::Stdin(stdin())
|
||||
} else {
|
||||
AsRawFdFile::File(Utf8CStr::from_string(infile).open(O_RDONLY)?)
|
||||
};
|
||||
|
||||
let mut buf = [0u8; 4096];
|
||||
|
||||
let mut in_file = unsafe { File::from_raw_fd(raw_in.as_raw_fd()) };
|
||||
let _ = in_file.read(&mut buf)?;
|
||||
|
||||
let format = check_fmt(&buf);
|
||||
|
||||
eprintln!("Detected format: {format}");
|
||||
|
||||
if !format.is_compressed() {
|
||||
return Err(log_err!("Input file is not a supported type!"));
|
||||
}
|
||||
|
||||
let raw_out = if let Some(outfile) = outfile {
|
||||
if outfile == "-" {
|
||||
AsRawFdFile::Stdout(stdout())
|
||||
} else {
|
||||
AsRawFdFile::File(Utf8CStr::from_string(outfile).create(O_WRONLY | O_TRUNC, 0o644)?)
|
||||
}
|
||||
} else if in_std {
|
||||
AsRawFdFile::Stdout(stdout())
|
||||
} else {
|
||||
// strip the extension
|
||||
rm_in = true;
|
||||
let mut outfile = if let Some((outfile, ext)) = infile.rsplit_once('.') {
|
||||
if ext != format.ext() {
|
||||
Err(log_err!("Input file is not a supported type!"))?;
|
||||
}
|
||||
outfile.to_owned()
|
||||
} else {
|
||||
infile.clone()
|
||||
};
|
||||
eprintln!("Decompressing to [{outfile}]");
|
||||
|
||||
AsRawFdFile::File(Utf8CStr::from_string(&mut outfile).create(O_WRONLY | O_TRUNC, 0o644)?)
|
||||
};
|
||||
|
||||
decompress_bytes_fd(format, &buf, raw_in.as_raw_fd(), raw_out.as_raw_fd());
|
||||
|
||||
if rm_in {
|
||||
Utf8CStr::from_string(infile).remove()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compress(
|
||||
method: FileFormat,
|
||||
infile: &mut String,
|
||||
outfile: Option<&mut String>,
|
||||
) -> LoggedResult<()> {
|
||||
if method == FileFormat::UNKNOWN {
|
||||
error!("Unsupported compression format");
|
||||
}
|
||||
|
||||
let in_std = infile == "-";
|
||||
let mut rm_in = false;
|
||||
|
||||
let raw_in = if in_std {
|
||||
AsRawFdFile::Stdin(stdin())
|
||||
} else {
|
||||
AsRawFdFile::File(Utf8CStr::from_string(infile).open(O_RDONLY)?)
|
||||
};
|
||||
|
||||
let raw_out = if let Some(outfile) = outfile {
|
||||
if outfile == "-" {
|
||||
AsRawFdFile::Stdout(stdout())
|
||||
} else {
|
||||
AsRawFdFile::File(Utf8CStr::from_string(outfile).create(O_WRONLY | O_TRUNC, 0o644)?)
|
||||
}
|
||||
} else if in_std {
|
||||
AsRawFdFile::Stdout(stdout())
|
||||
} else {
|
||||
let mut outfile = format!("{infile}.{}", method.ext());
|
||||
eprintln!("Compressing to [{outfile}]");
|
||||
rm_in = true;
|
||||
AsRawFdFile::File(Utf8CStr::from_string(&mut outfile).create(O_WRONLY | O_TRUNC, 0o644)?)
|
||||
};
|
||||
|
||||
compress_fd(method, raw_in.as_raw_fd(), raw_out.as_raw_fd());
|
||||
|
||||
if rm_in {
|
||||
Utf8CStr::from_string(infile).remove()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ use size::{Base, Size, Style};
|
||||
use base::libc::{
|
||||
O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT,
|
||||
S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
|
||||
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t,
|
||||
dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t,
|
||||
};
|
||||
use base::{
|
||||
BytesExt, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
|
||||
cstr, log_err, map_args,
|
||||
cstr, log_err,
|
||||
};
|
||||
|
||||
use crate::check_env;
|
||||
@@ -29,14 +29,6 @@ use crate::compress::{get_decoder, get_encoder};
|
||||
use crate::ffi::FileFormat;
|
||||
use crate::patch::{patch_encryption, patch_verity};
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct CpioCli {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(positional)]
|
||||
commands: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct CpioCommand {
|
||||
#[argh(subcommand)]
|
||||
@@ -151,7 +143,7 @@ struct List {
|
||||
recursive: bool,
|
||||
}
|
||||
|
||||
fn print_cpio_usage() {
|
||||
pub(crate) fn print_cpio_usage() {
|
||||
eprintln!(
|
||||
r#"Usage: magiskboot cpio <incpio> [commands...]
|
||||
|
||||
@@ -761,74 +753,58 @@ impl Display for CpioEntry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
|
||||
let res: LoggedResult<()> = try {
|
||||
if argc < 1 {
|
||||
Err(log_err!("No arguments"))?;
|
||||
}
|
||||
|
||||
let cmds = map_args(argc, argv)?;
|
||||
|
||||
let mut cli =
|
||||
CpioCli::from_args(&["magiskboot", "cpio"], &cmds).on_early_exit(print_cpio_usage);
|
||||
|
||||
let file = Utf8CStr::from_string(&mut cli.file);
|
||||
let mut cpio = if file.exists() {
|
||||
Cpio::load_from_file(file)?
|
||||
} else {
|
||||
Cpio::new()
|
||||
};
|
||||
|
||||
for cmd in cli.commands {
|
||||
if cmd.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
let mut cli = CpioCommand::from_args(
|
||||
&["magiskboot", "cpio", file],
|
||||
cmd.split(' ')
|
||||
.filter(|x| !x.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.on_early_exit(print_cpio_usage);
|
||||
|
||||
match &mut cli.action {
|
||||
CpioAction::Test(_) => exit(cpio.test()),
|
||||
CpioAction::Restore(_) => cpio.restore()?,
|
||||
CpioAction::Patch(_) => cpio.patch(),
|
||||
CpioAction::Exists(Exists { path }) => {
|
||||
if cpio.exists(path) {
|
||||
exit(0);
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
CpioAction::Backup(Backup {
|
||||
origin,
|
||||
skip_compress,
|
||||
}) => cpio.backup(origin, *skip_compress)?,
|
||||
CpioAction::Remove(Remove { path, recursive }) => cpio.rm(path, *recursive),
|
||||
CpioAction::Move(Move { from, to }) => cpio.mv(from, to)?,
|
||||
CpioAction::MakeDir(MakeDir { mode, dir }) => cpio.mkdir(*mode, dir),
|
||||
CpioAction::Link(Link { src, dst }) => cpio.ln(src, dst),
|
||||
CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?,
|
||||
CpioAction::Extract(Extract { paths }) => {
|
||||
if !paths.is_empty() && paths.len() != 2 {
|
||||
Err(log_err!("invalid arguments"))?;
|
||||
}
|
||||
let mut it = paths.iter_mut();
|
||||
cpio.extract(it.next(), it.next())?;
|
||||
}
|
||||
CpioAction::List(List { path, recursive }) => {
|
||||
cpio.ls(path.as_str(), *recursive);
|
||||
exit(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
cpio.dump(file)?;
|
||||
pub(crate) fn cpio_commands(file: &mut String, cmds: &mut Vec<String>) -> LoggedResult<bool> {
|
||||
let file = Utf8CStr::from_string(file);
|
||||
let mut cpio = if file.exists() {
|
||||
Cpio::load_from_file(file)?
|
||||
} else {
|
||||
Cpio::new()
|
||||
};
|
||||
res.log_with_msg(|w| w.write_str("Failed to process cpio"))
|
||||
.is_ok()
|
||||
|
||||
for cmd in cmds {
|
||||
if cmd.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
let mut cmd = CpioCommand::from_args(
|
||||
&["magiskboot", "cpio", file],
|
||||
cmd.split(' ')
|
||||
.filter(|x| !x.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.on_early_exit(print_cpio_usage);
|
||||
|
||||
match &mut cmd.action {
|
||||
CpioAction::Test(_) => exit(cpio.test()),
|
||||
CpioAction::Restore(_) => cpio.restore()?,
|
||||
CpioAction::Patch(_) => cpio.patch(),
|
||||
CpioAction::Exists(Exists { path }) => {
|
||||
return Ok(cpio.exists(path));
|
||||
}
|
||||
CpioAction::Backup(Backup {
|
||||
origin,
|
||||
skip_compress,
|
||||
}) => cpio.backup(origin, *skip_compress)?,
|
||||
CpioAction::Remove(Remove { path, recursive }) => cpio.rm(path, *recursive),
|
||||
CpioAction::Move(Move { from, to }) => cpio.mv(from, to)?,
|
||||
CpioAction::MakeDir(MakeDir { mode, dir }) => cpio.mkdir(*mode, dir),
|
||||
CpioAction::Link(Link { src, dst }) => cpio.ln(src, dst),
|
||||
CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?,
|
||||
CpioAction::Extract(Extract { paths }) => {
|
||||
if !paths.is_empty() && paths.len() != 2 {
|
||||
Err(log_err!("invalid arguments"))?;
|
||||
}
|
||||
let mut it = paths.iter_mut();
|
||||
cpio.extract(it.next(), it.next())?;
|
||||
}
|
||||
CpioAction::List(List { path, recursive }) => {
|
||||
cpio.ls(path.as_str(), *recursive);
|
||||
return Ok(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
cpio.dump(file)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn x8u(x: &[u8; 8]) -> LoggedResult<u32> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::UnsafeCell, process::exit};
|
||||
use std::cell::UnsafeCell;
|
||||
|
||||
use argh::FromArgs;
|
||||
use fdt::{
|
||||
@@ -6,23 +6,13 @@ use fdt::{
|
||||
node::{FdtNode, NodeProperty},
|
||||
};
|
||||
|
||||
use base::{
|
||||
EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, libc::c_char, log_err, map_args,
|
||||
};
|
||||
use base::{LoggedResult, MappedFile, Utf8CStr};
|
||||
|
||||
use crate::{check_env, patch::patch_verity};
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct DtbCli {
|
||||
#[argh(positional)]
|
||||
file: String,
|
||||
#[argh(subcommand)]
|
||||
action: DtbAction,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand)]
|
||||
enum DtbAction {
|
||||
pub(crate) enum DtbAction {
|
||||
Print(Print),
|
||||
Patch(Patch),
|
||||
Test(Test),
|
||||
@@ -30,20 +20,20 @@ enum DtbAction {
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "print")]
|
||||
struct Print {
|
||||
pub(crate) struct Print {
|
||||
#[argh(switch, short = 'f')]
|
||||
fstab: bool,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "patch")]
|
||||
struct Patch {}
|
||||
pub(crate) struct Patch {}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[argh(subcommand, name = "test")]
|
||||
struct Test {}
|
||||
pub(crate) struct Test {}
|
||||
|
||||
fn print_dtb_usage() {
|
||||
pub(crate) fn print_dtb_usage() {
|
||||
eprintln!(
|
||||
r#"Usage: magiskboot dtb <file> <action> [args...]
|
||||
Do dtb related actions to <file>.
|
||||
@@ -274,34 +264,14 @@ fn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {
|
||||
Ok(patched)
|
||||
}
|
||||
|
||||
pub fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool {
|
||||
let res: LoggedResult<()> = try {
|
||||
if argc < 1 {
|
||||
Err(log_err!("No arguments"))?;
|
||||
pub(crate) fn dtb_commands(file: &mut String, action: &DtbAction) -> LoggedResult<bool> {
|
||||
let file = Utf8CStr::from_string(file);
|
||||
match action {
|
||||
DtbAction::Print(Print { fstab }) => {
|
||||
dtb_print(file, *fstab)?;
|
||||
Ok(true)
|
||||
}
|
||||
let cmds = map_args(argc, argv)?;
|
||||
|
||||
let mut cli =
|
||||
DtbCli::from_args(&["magiskboot", "dtb"], &cmds).on_early_exit(print_dtb_usage);
|
||||
|
||||
let file = Utf8CStr::from_string(&mut cli.file);
|
||||
|
||||
match cli.action {
|
||||
DtbAction::Print(Print { fstab }) => {
|
||||
dtb_print(file, fstab)?;
|
||||
}
|
||||
DtbAction::Test(_) => {
|
||||
if !dtb_test(file)? {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
DtbAction::Patch(_) => {
|
||||
if !dtb_patch(file)? {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
res.log_with_msg(|w| w.write_str("Failed to process dtb"))
|
||||
.is_ok()
|
||||
DtbAction::Test(_) => Ok(dtb_test(file)?),
|
||||
DtbAction::Patch(_) => Ok(dtb_patch(file)?),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#include "boot-rs.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
Name2Fmt name2fmt;
|
||||
Fmt2Name fmt2name;
|
||||
Fmt2Ext fmt2ext;
|
||||
|
||||
#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s))
|
||||
|
||||
@@ -72,40 +70,3 @@ const char *Fmt2Name::operator[](FileFormat fmt) {
|
||||
return "raw";
|
||||
}
|
||||
}
|
||||
|
||||
const char *Fmt2Ext::operator[](FileFormat fmt) {
|
||||
switch (fmt) {
|
||||
case FileFormat::GZIP:
|
||||
case FileFormat::ZOPFLI:
|
||||
return ".gz";
|
||||
case FileFormat::LZOP:
|
||||
return ".lzo";
|
||||
case FileFormat::XZ:
|
||||
return ".xz";
|
||||
case FileFormat::LZMA:
|
||||
return ".lzma";
|
||||
case FileFormat::BZIP2:
|
||||
return ".bz2";
|
||||
case FileFormat::LZ4:
|
||||
case FileFormat::LZ4_LEGACY:
|
||||
case FileFormat::LZ4_LG:
|
||||
return ".lz4";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK(s, f) else if (name == s) return f;
|
||||
|
||||
FileFormat Name2Fmt::operator[](std::string_view name) {
|
||||
if (0) {}
|
||||
CHECK("gzip", FileFormat::GZIP)
|
||||
CHECK("zopfli", FileFormat::ZOPFLI)
|
||||
CHECK("xz", FileFormat::XZ)
|
||||
CHECK("lzma", FileFormat::LZMA)
|
||||
CHECK("bzip2", FileFormat::BZIP2)
|
||||
CHECK("lz4", FileFormat::LZ4)
|
||||
CHECK("lz4_legacy", FileFormat::LZ4_LEGACY)
|
||||
CHECK("lz4_lg", FileFormat::LZ4_LG)
|
||||
else return FileFormat::UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -46,11 +46,6 @@ public:
|
||||
const char *operator[](FileFormat fmt);
|
||||
};
|
||||
|
||||
class Fmt2Ext {
|
||||
public:
|
||||
const char *operator[](FileFormat fmt);
|
||||
};
|
||||
|
||||
class Name2Fmt {
|
||||
public:
|
||||
FileFormat operator[](std::string_view name);
|
||||
@@ -62,6 +57,4 @@ static inline FileFormat check_fmt(rust::Slice<const uint8_t> bytes) {
|
||||
return check_fmt(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
extern Name2Fmt name2fmt;
|
||||
extern Fmt2Name fmt2name;
|
||||
extern Fmt2Ext fmt2ext;
|
||||
|
||||
85
native/src/boot/format.rs
Normal file
85
native/src/boot/format.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crate::ffi::FileFormat;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
impl FromStr for FileFormat {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"gzip" => Ok(Self::GZIP),
|
||||
"zopfli" => Ok(Self::ZOPFLI),
|
||||
"xz" => Ok(Self::XZ),
|
||||
"lzma" => Ok(Self::LZMA),
|
||||
"bzip2" => Ok(Self::BZIP2),
|
||||
"lz4" => Ok(Self::LZ4),
|
||||
"lz4_legacy" => Ok(Self::LZ4_LEGACY),
|
||||
"lz4_lg" => Ok(Self::LZ4_LG),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FileFormat {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Self::GZIP => write!(f, "gzip"),
|
||||
Self::ZOPFLI => write!(f, "zopfli"),
|
||||
Self::LZOP => write!(f, "lzop"),
|
||||
Self::XZ => write!(f, "xz"),
|
||||
Self::LZMA => write!(f, "lzma"),
|
||||
Self::BZIP2 => write!(f, "bzip2"),
|
||||
Self::LZ4 => write!(f, "lz4"),
|
||||
Self::LZ4_LEGACY => write!(f, "lz4_legacy"),
|
||||
Self::LZ4_LG => write!(f, "lz4_lg"),
|
||||
Self::DTB => write!(f, "dtb"),
|
||||
Self::ZIMAGE => write!(f, "zimage"),
|
||||
_ => write!(f, "raw"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileFormat {
|
||||
pub fn ext(&self) -> &'static str {
|
||||
match *self {
|
||||
Self::GZIP | Self::ZOPFLI => ".gz",
|
||||
Self::LZOP => ".lzo",
|
||||
Self::XZ => ".xz",
|
||||
Self::LZMA => ".lzma",
|
||||
Self::BZIP2 => ".bz2",
|
||||
Self::LZ4 | Self::LZ4_LEGACY | Self::LZ4_LG => ".lz4",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_compressed(&self) -> bool {
|
||||
matches!(
|
||||
*self,
|
||||
Self::GZIP
|
||||
| Self::ZOPFLI
|
||||
| Self::LZOP
|
||||
| Self::XZ
|
||||
| Self::LZMA
|
||||
| Self::BZIP2
|
||||
| Self::LZ4
|
||||
| Self::LZ4_LEGACY
|
||||
| Self::LZ4_LG
|
||||
)
|
||||
}
|
||||
|
||||
pub fn formats() -> String {
|
||||
[
|
||||
Self::GZIP,
|
||||
Self::ZOPFLI,
|
||||
Self::XZ,
|
||||
Self::LZMA,
|
||||
Self::BZIP2,
|
||||
Self::LZ4,
|
||||
Self::LZ4_LEGACY,
|
||||
Self::LZ4_LG,
|
||||
Self::LZOP,
|
||||
]
|
||||
.map(|f| f.to_string())
|
||||
.join(" ")
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,8 @@
|
||||
#![feature(try_blocks)]
|
||||
|
||||
pub use base;
|
||||
use compress::{compress_bytes, compress_fd, decompress_bytes, decompress_bytes_fd};
|
||||
use cpio::cpio_commands;
|
||||
use dtb::dtb_commands;
|
||||
use patch::hexpatch;
|
||||
use payload::extract_boot_from_payload;
|
||||
use sign::{SHA, get_sha, sha1_hash, sha256_hash, sign_boot_image, verify_boot_image};
|
||||
use compress::{compress_bytes, decompress_bytes};
|
||||
use sign::{SHA, get_sha, sha256_hash, sign_boot_image, verify_boot_image};
|
||||
use std::env;
|
||||
|
||||
mod compress;
|
||||
@@ -18,6 +14,8 @@ mod dtb;
|
||||
mod patch;
|
||||
mod payload;
|
||||
// Suppress warnings in generated code
|
||||
mod cli;
|
||||
mod format;
|
||||
#[allow(warnings)]
|
||||
mod proto;
|
||||
mod sign;
|
||||
@@ -68,6 +66,19 @@ pub mod ffi {
|
||||
fn payload(self: &BootImage) -> &[u8];
|
||||
#[cxx_name = "get_tail"]
|
||||
fn tail(self: &BootImage) -> &[u8];
|
||||
|
||||
include!("magiskboot.hpp");
|
||||
fn cleanup();
|
||||
fn unpack(image: Utf8CStrRef, skip_decomp: bool, hdr: bool) -> i32;
|
||||
fn repack(src_img: Utf8CStrRef, out_img: Utf8CStrRef, skip_comp: bool);
|
||||
unsafe fn verify(image: Utf8CStrRef, cert: *const c_char) -> i32;
|
||||
unsafe fn sign(
|
||||
image: Utf8CStrRef,
|
||||
name: Utf8CStrRef,
|
||||
cert: *const c_char,
|
||||
key: *const c_char,
|
||||
) -> i32;
|
||||
fn split_image_dtb(filename: Utf8CStrRef, skip_decomp: bool) -> i32;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
@@ -76,33 +87,22 @@ pub mod ffi {
|
||||
fn update(self: &mut SHA, data: &[u8]);
|
||||
fn finalize_into(self: &mut SHA, out: &mut [u8]);
|
||||
fn output_size(self: &SHA) -> usize;
|
||||
fn sha1_hash(data: &[u8], out: &mut [u8]);
|
||||
fn sha256_hash(data: &[u8], out: &mut [u8]);
|
||||
|
||||
fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool;
|
||||
fn compress_fd(format: FileFormat, in_fd: i32, out_fd: i32);
|
||||
fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32);
|
||||
fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32);
|
||||
fn decompress_bytes_fd(format: FileFormat, in_bytes: &[u8], in_fd: i32, out_fd: i32);
|
||||
}
|
||||
|
||||
#[namespace = "rust"]
|
||||
#[allow(unused_unsafe)]
|
||||
extern "Rust" {
|
||||
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;
|
||||
unsafe fn sign_boot_image(
|
||||
payload: &[u8],
|
||||
name: *const c_char,
|
||||
cert: *const c_char,
|
||||
key: *const c_char,
|
||||
) -> Vec<u8>;
|
||||
unsafe fn dtb_commands(argc: i32, argv: *const *const c_char) -> bool;
|
||||
unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,21 @@
|
||||
#define BOOTCONFIG_FILE "bootconfig"
|
||||
#define NEW_BOOT "new-boot.img"
|
||||
|
||||
int unpack(const char *image, bool skip_decomp = false, bool hdr = false);
|
||||
void repack(const char *src_img, const char *out_img, bool skip_comp = false);
|
||||
int verify(const char *image, const char *cert);
|
||||
int sign(const char *image, const char *name, const char *cert, const char *key);
|
||||
int split_image_dtb(const char *filename, bool skip_decomp = false);
|
||||
int unpack(rust::Utf8CStr image, bool skip_decomp = false, bool hdr = false);
|
||||
void repack(rust::Utf8CStr src_img, rust::Utf8CStr out_img, bool skip_comp = false);
|
||||
int verify(rust::Utf8CStr image, const char *cert);
|
||||
int sign(rust::Utf8CStr image, rust::Utf8CStr name, const char *cert, const char *key);
|
||||
int split_image_dtb(rust::Utf8CStr filename, bool skip_decomp = false);
|
||||
|
||||
inline void cleanup() {
|
||||
unlink(HEADER_FILE);
|
||||
unlink(KERNEL_FILE);
|
||||
unlink(RAMDISK_FILE);
|
||||
unlink(SECOND_FILE);
|
||||
unlink(KER_DTB_FILE);
|
||||
unlink(EXTRA_FILE);
|
||||
unlink(RECV_DTBO_FILE);
|
||||
unlink(DTB_FILE);
|
||||
unlink(BOOTCONFIG_FILE);
|
||||
rm_rf(VND_RAMDISK_DIR);
|
||||
}
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
#include <base.hpp>
|
||||
|
||||
#include "boot-rs.hpp"
|
||||
#include "magiskboot.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void print_formats() {
|
||||
for (int fmt = +FileFormat::GZIP; fmt < +FileFormat::LZOP; ++fmt) {
|
||||
fprintf(stderr, "%s ", fmt2name[(FileFormat) fmt]);
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(char *arg0) {
|
||||
fprintf(stderr,
|
||||
R"EOF(MagiskBoot - Boot Image Modification Tool
|
||||
|
||||
Usage: %s <action> [args...]
|
||||
|
||||
Supported actions:
|
||||
unpack [-n] [-h] <bootimg>
|
||||
Unpack <bootimg> to its individual components, each component to
|
||||
a file with its corresponding file name in the current directory.
|
||||
Supported components: kernel, kernel_dtb, ramdisk.cpio, second,
|
||||
dtb, extra, and recovery_dtbo.
|
||||
By default, each component will be decompressed on-the-fly.
|
||||
If '-n' is provided, all decompression operations will be skipped;
|
||||
each component will remain untouched, dumped in its original format.
|
||||
If '-h' is provided, the boot image header information will be
|
||||
dumped to the file 'header', which can be used to modify header
|
||||
configurations during repacking.
|
||||
Return values:
|
||||
0:valid 1:error 2:chromeos
|
||||
|
||||
repack [-n] <origbootimg> [outbootimg]
|
||||
Repack boot image components using files from the current directory
|
||||
to [outbootimg], or 'new-boot.img' if not specified. Current directory
|
||||
should only contain required files for [outbootimg], or incorrect
|
||||
[outbootimg] may be produced.
|
||||
<origbootimg> is the original boot image used to unpack the components.
|
||||
By default, each component will be automatically compressed using its
|
||||
corresponding format detected in <origbootimg>. If a component file
|
||||
in the current directory is already compressed, then no addition
|
||||
compression will be performed for that specific component.
|
||||
If '-n' is provided, all compression operations will be skipped.
|
||||
If env variable PATCHVBMETAFLAG is set to true, all disable flags in
|
||||
the boot image's vbmeta header will be set.
|
||||
|
||||
verify <bootimg> [x509.pem]
|
||||
Check whether the boot image is signed with AVB 1.0 signature.
|
||||
Optionally provide a certificate to verify whether the image is
|
||||
signed by the public key certificate.
|
||||
Return value:
|
||||
0:valid 1:error
|
||||
|
||||
sign <bootimg> [name] [x509.pem pk8]
|
||||
Sign <bootimg> with AVB 1.0 signature.
|
||||
Optionally provide the name of the image (default: '/boot').
|
||||
Optionally provide the certificate/private key pair for signing.
|
||||
If the certificate/private key pair is not provided, the AOSP
|
||||
verity key bundled in the executable will be used.
|
||||
|
||||
extract <payload.bin> [partition] [outfile]
|
||||
Extract [partition] from <payload.bin> to [outfile].
|
||||
If [outfile] is not specified, then output to '[partition].img'.
|
||||
If [partition] is not specified, then attempt to extract either
|
||||
'init_boot' or 'boot'. Which partition was chosen can be determined
|
||||
by whichever 'init_boot.img' or 'boot.img' exists.
|
||||
<payload.bin> can be '-' to be STDIN.
|
||||
|
||||
hexpatch <file> <hexpattern1> <hexpattern2>
|
||||
Search <hexpattern1> in <file>, and replace it with <hexpattern2>
|
||||
|
||||
cpio <incpio> [commands...]
|
||||
Do cpio commands to <incpio> (modifications are done in-place).
|
||||
Each command is a single argument; add quotes for each command.
|
||||
See "cpio --help" for supported commands.
|
||||
|
||||
dtb <file> <action> [args...]
|
||||
Do dtb related actions to <file>.
|
||||
See "dtb --help" for supported actions.
|
||||
|
||||
split [-n] <file>
|
||||
Split image.*-dtb into kernel + kernel_dtb.
|
||||
If '-n' is provided, decompression operations will be skipped;
|
||||
the kernel will remain untouched, split in its original format.
|
||||
|
||||
sha1 <file>
|
||||
Print the SHA1 checksum for <file>
|
||||
|
||||
cleanup
|
||||
Cleanup the current working directory
|
||||
|
||||
compress[=format] <infile> [outfile]
|
||||
Compress <infile> with [format] to [outfile].
|
||||
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
|
||||
If [format] is not specified, then gzip will be used.
|
||||
If [outfile] is not specified, then <infile> will be replaced
|
||||
with another file suffixed with a matching file extension.
|
||||
Supported formats: )EOF", arg0);
|
||||
|
||||
print_formats();
|
||||
|
||||
fprintf(stderr, R"EOF(
|
||||
|
||||
decompress <infile> [outfile]
|
||||
Detect format and decompress <infile> to [outfile].
|
||||
<infile>/[outfile] can be '-' to be STDIN/STDOUT.
|
||||
If [outfile] is not specified, then <infile> will be replaced
|
||||
with another file removing its archive format file extension.
|
||||
Supported formats: )EOF");
|
||||
|
||||
print_formats();
|
||||
|
||||
fprintf(stderr, "\n\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void decompress(char *infile, const char *outfile) {
|
||||
bool in_std = infile == "-"sv;
|
||||
bool rm_in = false;
|
||||
|
||||
int in_fd = in_std ? STDIN_FILENO : xopen(infile, O_RDONLY);
|
||||
int out_fd = -1;
|
||||
|
||||
uint8_t buf[4096];
|
||||
size_t len = read(in_fd, buf, sizeof(buf));
|
||||
FileFormat type = check_fmt(buf, len);
|
||||
|
||||
fprintf(stderr, "Detected format: [%s]\n", fmt2name[type]);
|
||||
|
||||
if (!COMPRESSED(type))
|
||||
LOGE("Input file is not a supported compressed type!\n");
|
||||
|
||||
// If user does not provide outfile, infile has to be either
|
||||
// <path>.[ext], or '-'. Outfile will be either <path> or '-'.
|
||||
// If the input does not have proper format, abort.
|
||||
|
||||
char *ext = nullptr;
|
||||
if (outfile == nullptr) {
|
||||
outfile = infile;
|
||||
if (!in_std) {
|
||||
ext = strrchr(infile, '.');
|
||||
if (ext == nullptr || strcmp(ext, fmt2ext[type]) != 0)
|
||||
LOGE("Input file is not a supported type!\n");
|
||||
|
||||
// Strip out extension and remove input
|
||||
*ext = '\0';
|
||||
rm_in = true;
|
||||
fprintf(stderr, "Decompressing to [%s]\n", outfile);
|
||||
}
|
||||
}
|
||||
|
||||
out_fd = outfile == "-"sv ?
|
||||
STDOUT_FILENO :
|
||||
xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (ext) *ext = '.';
|
||||
|
||||
decompress_bytes_fd(type, byte_view{ buf, len }, in_fd, out_fd);
|
||||
|
||||
if (in_fd != STDIN_FILENO) close(in_fd);
|
||||
if (out_fd != STDOUT_FILENO) close(out_fd);
|
||||
|
||||
if (rm_in)
|
||||
unlink(infile);
|
||||
}
|
||||
|
||||
static void compress(const char *method, const char *infile, const char *outfile) {
|
||||
FileFormat fmt = name2fmt[method];
|
||||
if (fmt == FileFormat::UNKNOWN)
|
||||
LOGE("Unknown compression method: [%s]\n", method);
|
||||
|
||||
bool in_std = infile == "-"sv;
|
||||
bool rm_in = false;
|
||||
|
||||
int in_fd = in_std ? STDIN_FILENO : xopen(infile, O_RDONLY);
|
||||
int out_fd = -1;
|
||||
|
||||
if (outfile == nullptr) {
|
||||
if (in_std) {
|
||||
out_fd = STDOUT_FILENO;
|
||||
} else {
|
||||
// If user does not provide outfile and infile is not
|
||||
// STDIN, output to <infile>.[ext]
|
||||
string tmp(infile);
|
||||
tmp += fmt2ext[fmt];
|
||||
out_fd = xopen(tmp.data(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
fprintf(stderr, "Compressing to [%s]\n", tmp.data());
|
||||
rm_in = true;
|
||||
}
|
||||
} else {
|
||||
out_fd = outfile == "-"sv ?
|
||||
STDOUT_FILENO :
|
||||
xopen(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
}
|
||||
|
||||
compress_fd(fmt, in_fd, out_fd);
|
||||
|
||||
if (in_fd != STDIN_FILENO) close(in_fd);
|
||||
if (out_fd != STDOUT_FILENO) close(out_fd);
|
||||
|
||||
if (rm_in)
|
||||
unlink(infile);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
cmdline_logging();
|
||||
umask(0);
|
||||
|
||||
if (argc < 2)
|
||||
usage(argv[0]);
|
||||
|
||||
// Skip '--' for backwards compatibility
|
||||
string_view action(argv[1]);
|
||||
if (str_starts(action, "--"))
|
||||
action = argv[1] + 2;
|
||||
|
||||
if (action == "cleanup") {
|
||||
fprintf(stderr, "Cleaning up...\n");
|
||||
unlink(HEADER_FILE);
|
||||
unlink(KERNEL_FILE);
|
||||
unlink(RAMDISK_FILE);
|
||||
unlink(SECOND_FILE);
|
||||
unlink(KER_DTB_FILE);
|
||||
unlink(EXTRA_FILE);
|
||||
unlink(RECV_DTBO_FILE);
|
||||
unlink(DTB_FILE);
|
||||
unlink(BOOTCONFIG_FILE);
|
||||
rm_rf(VND_RAMDISK_DIR);
|
||||
} else if (argc > 2 && action == "sha1") {
|
||||
uint8_t sha1[20];
|
||||
{
|
||||
mmap_data m(argv[2]);
|
||||
sha1_hash(m, byte_data(sha1, sizeof(sha1)));
|
||||
}
|
||||
for (uint8_t i : sha1)
|
||||
printf("%02x", i);
|
||||
printf("\n");
|
||||
} else if (argc > 2 && action == "split") {
|
||||
if (argv[2] == "-n"sv) {
|
||||
if (argc == 3)
|
||||
usage(argv[0]);
|
||||
return split_image_dtb(argv[3], true);
|
||||
} else {
|
||||
return split_image_dtb(argv[2]);
|
||||
}
|
||||
} else if (argc > 2 && action == "unpack") {
|
||||
int idx = 2;
|
||||
bool nodecomp = false;
|
||||
bool hdr = false;
|
||||
for (;;) {
|
||||
if (idx >= argc)
|
||||
usage(argv[0]);
|
||||
if (argv[idx][0] != '-')
|
||||
break;
|
||||
for (char *flag = &argv[idx][1]; *flag; ++flag) {
|
||||
if (*flag == 'n')
|
||||
nodecomp = true;
|
||||
else if (*flag == 'h')
|
||||
hdr = true;
|
||||
else
|
||||
usage(argv[0]);
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
return unpack(argv[idx], nodecomp, hdr);
|
||||
} else if (argc > 2 && action == "repack") {
|
||||
if (argv[2] == "-n"sv) {
|
||||
if (argc == 3)
|
||||
usage(argv[0]);
|
||||
repack(argv[3], argv[4] ? argv[4] : NEW_BOOT, true);
|
||||
} else {
|
||||
repack(argv[2], argv[3] ? argv[3] : NEW_BOOT);
|
||||
}
|
||||
} else if (argc > 2 && action == "verify") {
|
||||
return verify(argv[2], argv[3]);
|
||||
} else if (argc > 2 && action == "sign") {
|
||||
if (argc == 5) usage(argv[0]);
|
||||
return sign(
|
||||
argv[2],
|
||||
argc > 3 ? argv[3] : "/boot",
|
||||
argc > 5 ? argv[4] : nullptr,
|
||||
argc > 5 ? argv[5] : nullptr);
|
||||
} else if (argc > 2 && action == "decompress") {
|
||||
decompress(argv[2], argv[3]);
|
||||
} else if (argc > 2 && str_starts(action, "compress")) {
|
||||
compress(action[8] == '=' ? &action[9] : "gzip", argv[2], argv[3]);
|
||||
} else if (argc > 4 && action == "hexpatch") {
|
||||
return hexpatch(byte_view(argv[2]), byte_view(argv[3]), byte_view(argv[4])) ? 0 : 1;
|
||||
} else if (argc > 2 && action == "cpio") {
|
||||
return rust::cpio_commands(argc - 2, argv + 2) ? 0 : 1;
|
||||
} else if (argc > 2 && action == "dtb") {
|
||||
return rust::dtb_commands(argc - 2, argv + 2) ? 0 : 1;
|
||||
} else if (argc > 2 && action == "extract") {
|
||||
return rust::extract_boot_from_payload(
|
||||
argv[2],
|
||||
argc > 3 ? argv[3] : "",
|
||||
argc > 4 ? argv[4] : ""
|
||||
) ? 0 : 1;
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -102,13 +102,9 @@ fn hex2byte(hex: &[u8]) -> Vec<u8> {
|
||||
v
|
||||
}
|
||||
|
||||
pub fn hexpatch(file: &[u8], from: &[u8], to: &[u8]) -> bool {
|
||||
pub fn hexpatch(file: &mut String, from: &Utf8CStr, to: &Utf8CStr) -> bool {
|
||||
let res: LoggedResult<bool> = try {
|
||||
let file = Utf8CStr::from_bytes(file)?;
|
||||
let from = Utf8CStr::from_bytes(from)?;
|
||||
let to = Utf8CStr::from_bytes(to)?;
|
||||
|
||||
let mut map = MappedFile::open_rw(file)?;
|
||||
let mut map = MappedFile::open_rw(Utf8CStr::from_string(file))?;
|
||||
let pattern = hex2byte(from.as_bytes());
|
||||
let patch = hex2byte(to.as_bytes());
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ use std::{
|
||||
use crate::compress::get_decoder;
|
||||
use crate::ffi::check_fmt;
|
||||
use crate::proto::update_metadata::{DeltaArchiveManifest, mod_InstallOperation::Type};
|
||||
use base::{
|
||||
LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt, error, ffi::Utf8CStrRef,
|
||||
};
|
||||
use base::{LoggedError, LoggedResult, ReadSeekExt, ResultExt, WriteExt, error};
|
||||
|
||||
macro_rules! bad_payload {
|
||||
($msg:literal) => {{
|
||||
@@ -26,10 +24,10 @@ macro_rules! bad_payload {
|
||||
|
||||
const PAYLOAD_MAGIC: &str = "CrAU";
|
||||
|
||||
fn do_extract_boot_from_payload(
|
||||
in_path: &Utf8CStr,
|
||||
partition_name: Option<&Utf8CStr>,
|
||||
out_path: Option<&Utf8CStr>,
|
||||
pub fn extract_boot_from_payload(
|
||||
in_path: &str,
|
||||
partition_name: Option<&str>,
|
||||
out_path: Option<&str>,
|
||||
) -> LoggedResult<()> {
|
||||
let mut reader = BufReader::new(if in_path == "-" {
|
||||
unsafe { File::from_raw_fd(0) }
|
||||
@@ -179,25 +177,3 @@ fn do_extract_boot_from_payload(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn extract_boot_from_payload(
|
||||
in_path: Utf8CStrRef,
|
||||
partition: Utf8CStrRef,
|
||||
out_path: Utf8CStrRef,
|
||||
) -> bool {
|
||||
let res: LoggedResult<()> = try {
|
||||
let partition = if partition.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(partition)
|
||||
};
|
||||
let out_path = if out_path.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(out_path)
|
||||
};
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ static void handle_request_sync(int client, int code) {
|
||||
denylist_handler(-1, nullptr);
|
||||
|
||||
// Restore native bridge property
|
||||
restore_zygisk_prop();
|
||||
MagiskD::Get().restore_zygisk_prop();
|
||||
|
||||
write_int(client, 0);
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ use crate::mount::{clean_mounts, setup_preinit_dir};
|
||||
use crate::package::ManagerInfo;
|
||||
use crate::selinux::restore_tmpcon;
|
||||
use crate::su::SuInfo;
|
||||
use crate::zygisk::ZygiskState;
|
||||
use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY};
|
||||
use base::{
|
||||
AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc,
|
||||
};
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::{BufReader, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process::Command;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
// Global magiskd singleton
|
||||
@@ -66,9 +66,8 @@ pub struct MagiskD {
|
||||
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 zygisk: Mutex<ZygiskState>,
|
||||
pub cached_su_info: AtomicArc<SuInfo>,
|
||||
sdk_int: i32,
|
||||
pub is_emulator: bool,
|
||||
@@ -80,10 +79,6 @@ impl MagiskD {
|
||||
unsafe { MAGISKD.get().unwrap_unchecked() }
|
||||
}
|
||||
|
||||
pub fn zygisk_enabled(&self) -> bool {
|
||||
self.zygisk_enabled.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn sdk_int(&self) -> i32 {
|
||||
self.sdk_int
|
||||
}
|
||||
@@ -175,7 +170,7 @@ impl MagiskD {
|
||||
|
||||
setup_preinit_dir();
|
||||
self.ensure_manager();
|
||||
self.zygisk_reset(true)
|
||||
self.zygisk.lock().unwrap().reset(true);
|
||||
}
|
||||
|
||||
pub fn boot_stage_handler(&self, client: i32, code: i32) {
|
||||
@@ -319,7 +314,6 @@ pub fn daemon_entry() {
|
||||
sdk_int,
|
||||
is_emulator,
|
||||
is_recovery,
|
||||
zygote_start_count: AtomicU32::new(1),
|
||||
..Default::default()
|
||||
};
|
||||
MAGISKD.set(magiskd).ok();
|
||||
|
||||
@@ -35,11 +35,6 @@ void unlock_blocks();
|
||||
bool setup_magisk_env();
|
||||
bool check_key_combo();
|
||||
|
||||
// Zygisk daemon
|
||||
rust::Str get_zygisk_lib_name();
|
||||
void set_zygisk_prop();
|
||||
void restore_zygisk_prop();
|
||||
|
||||
// Sockets
|
||||
struct sock_cred : public ucred {
|
||||
std::string context;
|
||||
@@ -112,9 +107,6 @@ 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(); }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#![feature(fn_traits)]
|
||||
#![feature(unix_socket_ancillary_data)]
|
||||
#![feature(unix_socket_peek)]
|
||||
#![feature(default_field_values)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use crate::ffi::SuRequest;
|
||||
@@ -125,15 +126,6 @@ pub mod ffi {
|
||||
gids: Vec<u32>,
|
||||
}
|
||||
|
||||
struct SuAppRequest<'a> {
|
||||
uid: i32,
|
||||
pid: i32,
|
||||
eval_uid: i32,
|
||||
mgr_pkg: &'a str,
|
||||
mgr_uid: i32,
|
||||
request: &'a SuRequest,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
#[namespace = "rust"]
|
||||
#[cxx_name = "Utf8CStr"]
|
||||
@@ -157,13 +149,7 @@ pub mod ffi {
|
||||
fn uninstall_pkg(apk: Utf8CStrRef);
|
||||
fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);
|
||||
fn initialize_denylist();
|
||||
fn get_zygisk_lib_name() -> &'static str;
|
||||
fn set_zygisk_prop();
|
||||
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);
|
||||
|
||||
include!("include/sqlite.hpp");
|
||||
@@ -211,7 +197,7 @@ pub mod ffi {
|
||||
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);
|
||||
fn write_to_fd(self: &SuRequest, fd: i32);
|
||||
fn pump_tty(infd: i32, outfd: i32);
|
||||
fn get_pty_num(fd: i32) -> i32;
|
||||
fn restore_stdin() -> bool;
|
||||
@@ -240,6 +226,7 @@ pub mod ffi {
|
||||
fn boot_stage_handler(&self, client: i32, code: i32);
|
||||
fn zygisk_handler(&self, client: i32);
|
||||
fn zygisk_reset(&self, restore: bool);
|
||||
fn restore_zygisk_prop(&self);
|
||||
fn prune_su_access(&self);
|
||||
fn su_daemon_handler(&self, client: i32, cred: &UCred);
|
||||
#[cxx_name = "get_manager"]
|
||||
@@ -266,7 +253,7 @@ unsafe impl ExternType for UCred {
|
||||
}
|
||||
|
||||
impl SuRequest {
|
||||
unsafe fn write_to_fd(&self, fd: i32) {
|
||||
fn write_to_fd(&self, fd: i32) {
|
||||
unsafe {
|
||||
let mut w = ManuallyDrop::new(File::from_raw_fd(fd));
|
||||
self.encode(w.deref_mut()).ok();
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR};
|
||||
use crate::daemon::MagiskD;
|
||||
use crate::ffi::{
|
||||
ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, get_zygisk_lib_name,
|
||||
load_prop_file, set_zygisk_prop,
|
||||
};
|
||||
use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, load_prop_file};
|
||||
use crate::mount::setup_module_mount;
|
||||
use base::{
|
||||
DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt,
|
||||
@@ -376,7 +373,7 @@ fn get_path_env() -> String {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn inject_magisk_bins(system: &mut FsNode) {
|
||||
fn inject_magisk_bins(system: &mut FsNode, is_emulator: bool) {
|
||||
fn inject(children: &mut FsNodeMap) {
|
||||
let mut path = cstr::buf::default().join_path(get_magisk_tmp());
|
||||
|
||||
@@ -418,7 +415,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
let mut candidates = vec![];
|
||||
|
||||
for orig_item in path_env.split(':') {
|
||||
// Filter not suitbale paths
|
||||
// Filter non-suitable paths
|
||||
if !MAGISK_BIN_INJECT_PARTITIONS
|
||||
.iter()
|
||||
.any(|p| orig_item.starts_with(p.as_str()))
|
||||
@@ -430,6 +427,11 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We want to keep /system/xbin/su on emulators (for debugging)
|
||||
if is_emulator && orig_item.starts_with("/system/xbin") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Override existing su first
|
||||
let su_path = Utf8CString::from(format!("{orig_item}/su"));
|
||||
if su_path.exists() {
|
||||
@@ -497,9 +499,7 @@ fn inject_magisk_bins(system: &mut FsNode) {
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_zygisk_bins(system: &mut FsNode) {
|
||||
let name = get_zygisk_lib_name();
|
||||
|
||||
fn inject_zygisk_bins(name: &str, system: &mut FsNode) {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let has_32_bit = cstr!("/system/bin/linker").exists();
|
||||
|
||||
@@ -555,114 +555,6 @@ fn inject_zygisk_bins(system: &mut FsNode) {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_modules(zygisk: bool, module_list: &[ModuleInfo]) {
|
||||
let mut system = FsNode::new_dir();
|
||||
|
||||
// Build all the base "prefix" paths
|
||||
let mut root = cstr::buf::default().join_path("/");
|
||||
|
||||
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
|
||||
|
||||
let mut module_mnt = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(MODULEMNT);
|
||||
|
||||
let mut worker = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(WORKERDIR);
|
||||
|
||||
// Create a collection of all relevant paths
|
||||
let mut root_paths = FilePaths {
|
||||
real: PathTracker::from(&mut root),
|
||||
worker: PathTracker::from(&mut worker),
|
||||
module_mnt: PathTracker::from(&mut module_mnt),
|
||||
module_root: PathTracker::from(&mut module_dir),
|
||||
};
|
||||
|
||||
// Step 1: Create virtual filesystem tree
|
||||
//
|
||||
// In this step, there is zero logic applied during tree construction; we simply collect and
|
||||
// record the union of all module filesystem trees under each of their /system directory.
|
||||
|
||||
for info in module_list {
|
||||
let mut module_paths = root_paths.append(&info.name);
|
||||
{
|
||||
// Read props
|
||||
let prop = module_paths.append("system.prop");
|
||||
if prop.module().exists() {
|
||||
// Do NOT go through property service as it could cause boot lock
|
||||
load_prop_file(prop.module(), true);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Check whether skip mounting
|
||||
let skip = module_paths.append("skip_mount");
|
||||
if skip.module().exists() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Double check whether the system folder exists
|
||||
let sys = module_paths.append("system");
|
||||
if sys.module().exists() {
|
||||
info!("{}: loading module files", &info.name);
|
||||
system.collect(sys).log_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Inject custom files
|
||||
//
|
||||
// Magisk provides some built-in functionality that requires augmenting the filesystem.
|
||||
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
|
||||
// has to also be added into the default LD_LIBRARY_PATH for code injection.
|
||||
// We directly inject file nodes into the virtual filesystem tree we built in the previous
|
||||
// step, treating Magisk just like a special "module".
|
||||
|
||||
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
|
||||
inject_magisk_bins(&mut system);
|
||||
}
|
||||
if zygisk {
|
||||
inject_zygisk_bins(&mut system);
|
||||
}
|
||||
|
||||
// Step 3: Extract all supported read-only partition roots
|
||||
//
|
||||
// For simplicity and backwards compatibility on older Android versions, when constructing
|
||||
// Magisk modules, we always assume that there is only a single read-only partition mounted
|
||||
// at /system. However, on modern Android there are actually multiple read-only partitions
|
||||
// mounted at their respective paths. We need to extract these subtrees out of the main
|
||||
// tree and treat them as individual trees.
|
||||
|
||||
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
|
||||
if let FsNode::Directory { children } = &mut system {
|
||||
for dir in SECONDARY_READ_ONLY_PARTITIONS {
|
||||
// Only treat these nodes as root iff it is actually a directory in rootdir
|
||||
if let Ok(attr) = dir.get_attr()
|
||||
&& attr.is_dir()
|
||||
{
|
||||
let name = dir.trim_start_matches('/');
|
||||
if let Some(root) = children.remove(name) {
|
||||
roots.insert(name, root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
roots.insert("system", system);
|
||||
|
||||
for (dir, mut root) in roots {
|
||||
// Step 4: Convert virtual filesystem tree into concrete operations
|
||||
//
|
||||
// Compare the virtual filesystem tree we constructed against the real filesystem
|
||||
// structure on-device to generate a series of "operations".
|
||||
// The "core" of the logic is to decide which directories need to be rebuilt in the
|
||||
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
|
||||
|
||||
let path = root_paths.append(dir);
|
||||
root.commit(path, true).log_ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade_modules() -> LoggedResult<()> {
|
||||
let mut upgrade = Directory::open(cstr!(MODULEUPGRADE))?;
|
||||
let ufd = upgrade.as_raw_fd();
|
||||
@@ -872,11 +764,120 @@ impl MagiskD {
|
||||
|
||||
// Recollect modules (module scripts could remove itself)
|
||||
let modules = collect_modules(zygisk, true);
|
||||
if zygisk {
|
||||
set_zygisk_prop();
|
||||
}
|
||||
apply_modules(zygisk, &modules);
|
||||
self.apply_modules(&modules);
|
||||
|
||||
self.module_list.set(modules).ok();
|
||||
}
|
||||
|
||||
fn apply_modules(&self, module_list: &[ModuleInfo]) {
|
||||
let mut system = FsNode::new_dir();
|
||||
|
||||
// Build all the base "prefix" paths
|
||||
let mut root = cstr::buf::default().join_path("/");
|
||||
|
||||
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
|
||||
|
||||
let mut module_mnt = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(MODULEMNT);
|
||||
|
||||
let mut worker = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(WORKERDIR);
|
||||
|
||||
// Create a collection of all relevant paths
|
||||
let mut root_paths = FilePaths {
|
||||
real: PathTracker::from(&mut root),
|
||||
worker: PathTracker::from(&mut worker),
|
||||
module_mnt: PathTracker::from(&mut module_mnt),
|
||||
module_root: PathTracker::from(&mut module_dir),
|
||||
};
|
||||
|
||||
// Step 1: Create virtual filesystem tree
|
||||
//
|
||||
// In this step, there is zero logic applied during tree construction; we simply collect and
|
||||
// record the union of all module filesystem trees under each of their /system directory.
|
||||
|
||||
for info in module_list {
|
||||
let mut module_paths = root_paths.append(&info.name);
|
||||
{
|
||||
// Read props
|
||||
let prop = module_paths.append("system.prop");
|
||||
if prop.module().exists() {
|
||||
// Do NOT go through property service as it could cause boot lock
|
||||
load_prop_file(prop.module(), true);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Check whether skip mounting
|
||||
let skip = module_paths.append("skip_mount");
|
||||
if skip.module().exists() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Double check whether the system folder exists
|
||||
let sys = module_paths.append("system");
|
||||
if sys.module().exists() {
|
||||
info!("{}: loading module files", &info.name);
|
||||
system.collect(sys).log_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Inject custom files
|
||||
//
|
||||
// Magisk provides some built-in functionality that requires augmenting the filesystem.
|
||||
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
|
||||
// has to also be added into the default LD_LIBRARY_PATH for code injection.
|
||||
// We directly inject file nodes into the virtual filesystem tree we built in the previous
|
||||
// step, treating Magisk just like a special "module".
|
||||
|
||||
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
|
||||
inject_magisk_bins(&mut system, self.is_emulator);
|
||||
}
|
||||
|
||||
// Handle zygisk
|
||||
if self.zygisk_enabled.load(Ordering::Acquire) {
|
||||
let mut zygisk = self.zygisk.lock().unwrap();
|
||||
zygisk.set_prop();
|
||||
inject_zygisk_bins(&zygisk.lib_name, &mut system);
|
||||
}
|
||||
|
||||
// Step 3: Extract all supported read-only partition roots
|
||||
//
|
||||
// For simplicity and backwards compatibility on older Android versions, when constructing
|
||||
// Magisk modules, we always assume that there is only a single read-only partition mounted
|
||||
// at /system. However, on modern Android there are actually multiple read-only partitions
|
||||
// mounted at their respective paths. We need to extract these subtrees out of the main
|
||||
// tree and treat them as individual trees.
|
||||
|
||||
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
|
||||
if let FsNode::Directory { children } = &mut system {
|
||||
for dir in SECONDARY_READ_ONLY_PARTITIONS {
|
||||
// Only treat these nodes as root iff it is actually a directory in rootdir
|
||||
if let Ok(attr) = dir.get_attr()
|
||||
&& attr.is_dir()
|
||||
{
|
||||
let name = dir.trim_start_matches('/');
|
||||
if let Some(root) = children.remove(name) {
|
||||
roots.insert(name, root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
roots.insert("system", system);
|
||||
|
||||
for (dir, mut root) in roots {
|
||||
// Step 4: Convert virtual filesystem tree into concrete operations
|
||||
//
|
||||
// Compare the virtual filesystem tree we constructed against the real filesystem
|
||||
// structure on-device to generate a series of "operations".
|
||||
// The "core" of the logic is to decide which directories need to be rebuilt in the
|
||||
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
|
||||
|
||||
let path = root_paths.append(dir);
|
||||
root.commit(path, true).log_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <consts.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define CALL_PROVIDER \
|
||||
"/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \
|
||||
"call", "--uri", target, "--user", user, "--method", action
|
||||
|
||||
#define START_ACTIVITY \
|
||||
"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \
|
||||
"start", "-p", target, "--user", user, "-a", "android.intent.action.VIEW", \
|
||||
"-f", "0x18800020", "--es", "action", action
|
||||
|
||||
// 0x18800020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
|
||||
// FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES
|
||||
|
||||
class Extra {
|
||||
const char *key;
|
||||
enum {
|
||||
INT,
|
||||
BOOL,
|
||||
STRING,
|
||||
INTLIST,
|
||||
} type;
|
||||
union {
|
||||
int int_val;
|
||||
bool bool_val;
|
||||
const char *str_val;
|
||||
const vector<uint32_t> *intlist_val;
|
||||
};
|
||||
string str;
|
||||
public:
|
||||
Extra(const char *k, int v): key(k), type(INT), int_val(v) {}
|
||||
Extra(const char *k, bool v): key(k), type(BOOL), bool_val(v) {}
|
||||
Extra(const char *k, const char *v): key(k), type(STRING), str_val(v) {}
|
||||
Extra(const char *k, const vector<uint32_t> *v): key(k), type(INTLIST), intlist_val(v) {}
|
||||
|
||||
void add_intent(vector<const char *> &vec) {
|
||||
const char *val;
|
||||
switch (type) {
|
||||
case INT:
|
||||
vec.push_back("--ei");
|
||||
str = to_string(int_val);
|
||||
val = str.data();
|
||||
break;
|
||||
case BOOL:
|
||||
vec.push_back("--ez");
|
||||
val = bool_val ? "true" : "false";
|
||||
break;
|
||||
case STRING:
|
||||
vec.push_back("--es");
|
||||
val = str_val;
|
||||
break;
|
||||
case INTLIST:
|
||||
vec.push_back("--es");
|
||||
for (auto i : *intlist_val) {
|
||||
str += to_string(i);
|
||||
str += ",";
|
||||
}
|
||||
if (!str.empty()) str.pop_back();
|
||||
val = str.data();
|
||||
break;
|
||||
}
|
||||
vec.push_back(key);
|
||||
vec.push_back(val);
|
||||
}
|
||||
|
||||
void add_bind(vector<const char *> &vec) {
|
||||
char buf[32];
|
||||
str = key;
|
||||
switch (type) {
|
||||
case INT:
|
||||
str += ":i:";
|
||||
ssprintf(buf, sizeof(buf), "%d", int_val);
|
||||
str += buf;
|
||||
break;
|
||||
case BOOL:
|
||||
str += ":b:";
|
||||
str += bool_val ? "true" : "false";
|
||||
break;
|
||||
case STRING:
|
||||
str += ":s:";
|
||||
if (SDK_INT >= 30) {
|
||||
string tmp = str_val;
|
||||
replace_all(tmp, "\\", "\\\\");
|
||||
replace_all(tmp, ":", "\\:");
|
||||
str += tmp;
|
||||
} else {
|
||||
str += str_val;
|
||||
}
|
||||
break;
|
||||
case INTLIST:
|
||||
str += ":s:";
|
||||
for (auto i : *intlist_val) {
|
||||
str += to_string(i);
|
||||
str += ",";
|
||||
}
|
||||
if (str.back() == ',') str.pop_back();
|
||||
break;
|
||||
}
|
||||
vec.push_back("--extra");
|
||||
vec.push_back(str.data());
|
||||
}
|
||||
};
|
||||
|
||||
static bool check_no_error(int fd) {
|
||||
char buf[1024];
|
||||
auto out = xopen_file(fd, "r");
|
||||
while (fgets(buf, sizeof(buf), out.get())) {
|
||||
if (strncasecmp(buf, "Error", 5) == 0) {
|
||||
LOGD("exec_cmd: %s\n", buf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void exec_cmd(const char *action, vector<Extra> &data,
|
||||
const SuAppRequest &info, bool provider = true) {
|
||||
char target[128];
|
||||
char user[4];
|
||||
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",
|
||||
(int) info.mgr_pkg.size(), info.mgr_pkg.data());
|
||||
vector<const char *> args{ CALL_PROVIDER };
|
||||
for (auto &e : data) {
|
||||
e.add_bind(args);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
exec_t exec {
|
||||
.err = true,
|
||||
.fd = -1,
|
||||
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/content.jar", 1); },
|
||||
.argv = args.data()
|
||||
};
|
||||
exec_command_sync(exec);
|
||||
if (check_no_error(exec.fd))
|
||||
return;
|
||||
}
|
||||
|
||||
// Then try start activity with package name
|
||||
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);
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
exec_t exec {
|
||||
.fd = -2,
|
||||
.pre_exec = [] { setenv("CLASSPATH", "/system/framework/am.jar", 1); },
|
||||
.fork = fork_dont_care,
|
||||
.argv = args.data()
|
||||
};
|
||||
exec_command(exec);
|
||||
}
|
||||
|
||||
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", 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, info);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
void app_notify(const SuAppRequest &info, SuPolicy policy) {
|
||||
if (fork_dont_care() == 0) {
|
||||
vector<Extra> extras;
|
||||
extras.reserve(3);
|
||||
extras.emplace_back("from.uid", info.uid);
|
||||
extras.emplace_back("pid", info.pid);
|
||||
extras.emplace_back("policy", +policy);
|
||||
|
||||
exec_cmd("notify", extras, info);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
int app_request(const SuAppRequest &info) {
|
||||
// Create FIFO
|
||||
char fifo[64];
|
||||
ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), info.pid);
|
||||
mkfifo(fifo, 0600);
|
||||
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", 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
|
||||
int fd = xopen(fifo, O_RDWR | O_CLOEXEC);
|
||||
struct pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLLIN
|
||||
};
|
||||
if (xpoll(&pfd, 1, 70 * 1000) <= 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
unlink(fifo);
|
||||
return fd;
|
||||
}
|
||||
315
native/src/core/su/connect.rs
Normal file
315
native/src/core/su/connect.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use super::SuInfo;
|
||||
use super::db::RootSettings;
|
||||
use crate::consts::{INTERNAL_DIR, MAGISK_FILE_CON};
|
||||
use crate::daemon::to_user_id;
|
||||
use crate::ffi::{SuPolicy, SuRequest, get_magisk_tmp};
|
||||
use crate::socket::IpcRead;
|
||||
use ExtraVal::{Bool, Int, IntList, Str};
|
||||
use base::{
|
||||
BytesExt, FileAttr, LibcReturn, LoggedResult, OsError, ResultExt, cstr, fork_dont_care, libc,
|
||||
};
|
||||
use libc::pollfd as PollFd;
|
||||
use num_traits::AsPrimitive;
|
||||
use std::{fmt::Write, fs::File, os::fd::AsRawFd, process::Command, process::exit};
|
||||
|
||||
struct Extra<'a> {
|
||||
key: &'static str,
|
||||
value: ExtraVal<'a>,
|
||||
}
|
||||
|
||||
enum ExtraVal<'a> {
|
||||
Int(i32),
|
||||
Bool(bool),
|
||||
Str(&'a str),
|
||||
IntList(&'a [u32]),
|
||||
}
|
||||
|
||||
impl Extra<'_> {
|
||||
fn add_intent(&self, cmd: &mut Command) {
|
||||
match self.value {
|
||||
Int(i) => {
|
||||
cmd.args(["--ei", self.key, &i.to_string()]);
|
||||
}
|
||||
Bool(b) => {
|
||||
cmd.args(["--ez", self.key, &b.to_string()]);
|
||||
}
|
||||
Str(s) => {
|
||||
cmd.args(["--es", self.key, s]);
|
||||
}
|
||||
IntList(list) => {
|
||||
cmd.args(["--es", self.key]);
|
||||
let mut tmp = String::new();
|
||||
list.iter().for_each(|i| write!(&mut tmp, "{i},").unwrap());
|
||||
tmp.pop();
|
||||
cmd.arg(&tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_bind(&self, cmd: &mut Command) {
|
||||
let mut tmp: String;
|
||||
match self.value {
|
||||
Int(i) => {
|
||||
tmp = format!("{}:i:{}", self.key, i);
|
||||
}
|
||||
Bool(b) => {
|
||||
tmp = format!("{}:b:{}", self.key, b);
|
||||
}
|
||||
Str(s) => {
|
||||
let s = s.replace("\\", "\\\\").replace(":", "\\:");
|
||||
tmp = format!("{}:s:{}", self.key, s);
|
||||
}
|
||||
IntList(list) => {
|
||||
tmp = format!("{}:s:", self.key);
|
||||
if !list.is_empty() {
|
||||
list.iter().for_each(|i| write!(&mut tmp, "{i},").unwrap());
|
||||
tmp.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd.args(["--extra", &tmp]);
|
||||
}
|
||||
|
||||
fn add_bind_legacy(&self, cmd: &mut Command) {
|
||||
match self.value {
|
||||
Str(s) => {
|
||||
let tmp = format!("{}:s:{}", self.key, s);
|
||||
cmd.args(["--extra", &tmp]);
|
||||
}
|
||||
_ => self.add_bind(cmd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct SuAppContext<'a> {
|
||||
pub(super) cred: libc::ucred,
|
||||
pub(super) request: &'a SuRequest,
|
||||
pub(super) info: &'a SuInfo,
|
||||
pub(super) settings: &'a mut RootSettings,
|
||||
pub(super) sdk_int: i32,
|
||||
}
|
||||
|
||||
impl SuAppContext<'_> {
|
||||
fn exec_cmd(&self, action: &'static str, extras: &[Extra], use_provider: bool) {
|
||||
let user = to_user_id(self.info.eval_uid);
|
||||
let user = user.to_string();
|
||||
|
||||
if use_provider {
|
||||
let provider = format!("content://{}.provider", self.info.mgr_pkg);
|
||||
let mut cmd = Command::new("/system/bin/app_process");
|
||||
cmd.args([
|
||||
"/system/bin",
|
||||
"com.android.commands.content.Content",
|
||||
"call",
|
||||
"--uri",
|
||||
&provider,
|
||||
"--user",
|
||||
&user,
|
||||
"--method",
|
||||
action,
|
||||
]);
|
||||
if self.sdk_int >= 30 {
|
||||
extras.iter().for_each(|e| e.add_bind(&mut cmd))
|
||||
} else {
|
||||
extras.iter().for_each(|e| e.add_bind_legacy(&mut cmd))
|
||||
}
|
||||
cmd.env("CLASSPATH", "/system/framework/content.jar");
|
||||
|
||||
if let Ok(output) = cmd.output()
|
||||
&& !output.stderr.contains(b"Error")
|
||||
&& !output.stdout.contains(b"Error")
|
||||
{
|
||||
// The provider call succeed
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("/system/bin/app_process");
|
||||
cmd.args([
|
||||
"/system/bin",
|
||||
"com.android.commands.am.Am",
|
||||
"start",
|
||||
"-p",
|
||||
&self.info.mgr_pkg,
|
||||
"--user",
|
||||
&user,
|
||||
"-a",
|
||||
"android.intent.action.VIEW",
|
||||
"-f",
|
||||
// FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|
|
||||
// FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES
|
||||
"0x18800020",
|
||||
"--es",
|
||||
"action",
|
||||
action,
|
||||
]);
|
||||
extras.iter().for_each(|e| e.add_intent(&mut cmd));
|
||||
cmd.env("CLASSPATH", "/system/framework/am.jar");
|
||||
|
||||
// Sometimes `am start` will fail, keep trying until it works
|
||||
loop {
|
||||
if let Ok(output) = cmd.output()
|
||||
&& !output.stdout.is_empty()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn app_request(&mut self) {
|
||||
let mut fifo = cstr::buf::new::<64>();
|
||||
fifo.write_fmt(format_args!(
|
||||
"{}/{}/su_request_{}",
|
||||
get_magisk_tmp(),
|
||||
INTERNAL_DIR,
|
||||
self.cred.pid
|
||||
))
|
||||
.ok();
|
||||
|
||||
let fd: LoggedResult<File> = try {
|
||||
let mut attr = FileAttr::new();
|
||||
attr.st.st_mode = 0o600;
|
||||
attr.st.st_uid = self.info.mgr_uid.as_();
|
||||
attr.st.st_gid = self.info.mgr_uid.as_();
|
||||
attr.con.write_str(MAGISK_FILE_CON).ok();
|
||||
|
||||
fifo.mkfifo(0o600)?;
|
||||
fifo.set_attr(&attr)?;
|
||||
|
||||
let extras = [
|
||||
Extra {
|
||||
key: "fifo",
|
||||
value: Str(&fifo),
|
||||
},
|
||||
Extra {
|
||||
key: "uid",
|
||||
value: Int(self.info.eval_uid),
|
||||
},
|
||||
Extra {
|
||||
key: "pid",
|
||||
value: Int(self.cred.pid),
|
||||
},
|
||||
];
|
||||
self.exec_cmd("request", &extras, false);
|
||||
|
||||
// Open with O_RDWR to prevent FIFO open block
|
||||
let fd = fifo.open(libc::O_RDWR | libc::O_CLOEXEC)?;
|
||||
|
||||
// Wait for data input for at most 70 seconds
|
||||
let mut pfd = PollFd {
|
||||
fd: fd.as_raw_fd(),
|
||||
events: libc::POLLIN,
|
||||
revents: 0,
|
||||
};
|
||||
if unsafe { libc::poll(&mut pfd, 1, 70 * 1000).as_os_result("poll", None, None)? } == 0
|
||||
{
|
||||
Err(OsError::with_os_error(libc::ETIMEDOUT, "poll", None, None))?;
|
||||
}
|
||||
|
||||
fd
|
||||
};
|
||||
|
||||
fifo.remove().log_ok();
|
||||
|
||||
if let Ok(mut fd) = fd {
|
||||
self.settings.policy = SuPolicy {
|
||||
repr: fd
|
||||
.read_decodable::<i32>()
|
||||
.log()
|
||||
.map(i32::from_be)
|
||||
.unwrap_or(SuPolicy::Deny.repr),
|
||||
};
|
||||
} else {
|
||||
self.settings.policy = SuPolicy::Deny;
|
||||
};
|
||||
}
|
||||
|
||||
fn app_notify(&self) {
|
||||
let extras = [
|
||||
Extra {
|
||||
key: "from.uid",
|
||||
value: Int(self.cred.uid.as_()),
|
||||
},
|
||||
Extra {
|
||||
key: "pid",
|
||||
value: Int(self.cred.pid.as_()),
|
||||
},
|
||||
Extra {
|
||||
key: "policy",
|
||||
value: Int(self.settings.policy.repr),
|
||||
},
|
||||
];
|
||||
self.exec_cmd("notify", &extras, true);
|
||||
}
|
||||
|
||||
fn app_log(&self) {
|
||||
let command = if self.request.command.is_empty() {
|
||||
&self.request.shell
|
||||
} else {
|
||||
&self.request.command
|
||||
};
|
||||
let extras = [
|
||||
Extra {
|
||||
key: "from.uid",
|
||||
value: Int(self.cred.uid.as_()),
|
||||
},
|
||||
Extra {
|
||||
key: "to.uid",
|
||||
value: Int(self.request.target_uid),
|
||||
},
|
||||
Extra {
|
||||
key: "pid",
|
||||
value: Int(self.cred.pid.as_()),
|
||||
},
|
||||
Extra {
|
||||
key: "policy",
|
||||
value: Int(self.settings.policy.repr),
|
||||
},
|
||||
Extra {
|
||||
key: "target",
|
||||
value: Int(self.request.target_pid),
|
||||
},
|
||||
Extra {
|
||||
key: "context",
|
||||
value: Str(&self.request.context),
|
||||
},
|
||||
Extra {
|
||||
key: "gids",
|
||||
value: IntList(&self.request.gids),
|
||||
},
|
||||
Extra {
|
||||
key: "command",
|
||||
value: Str(command),
|
||||
},
|
||||
Extra {
|
||||
key: "notify",
|
||||
value: Bool(self.settings.notify),
|
||||
},
|
||||
];
|
||||
self.exec_cmd("log", &extras, true);
|
||||
}
|
||||
|
||||
pub(super) fn connect_app(&mut self) {
|
||||
// If policy is undetermined, show dialog for user consent
|
||||
if self.settings.policy == SuPolicy::Query {
|
||||
self.app_request();
|
||||
}
|
||||
|
||||
if !self.settings.log && !self.settings.notify {
|
||||
return;
|
||||
}
|
||||
|
||||
if fork_dont_care() != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify su usage to application
|
||||
if self.settings.log {
|
||||
self.app_log();
|
||||
} else if self.settings.notify {
|
||||
self.app_notify();
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
use super::connect::SuAppContext;
|
||||
use super::db::RootSettings;
|
||||
use crate::UCred;
|
||||
use crate::daemon::{AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id};
|
||||
use crate::db::{DbSettings, MultiuserMode, RootAccess};
|
||||
use crate::ffi::{
|
||||
SuAppRequest, SuPolicy, SuRequest, app_log, app_notify, app_request, exec_root_shell,
|
||||
};
|
||||
use crate::ffi::{SuPolicy, SuRequest, exec_root_shell};
|
||||
use crate::socket::IpcRead;
|
||||
use crate::su::db::RootSettings;
|
||||
use base::{LoggedResult, ResultExt, WriteExt, debug, error, exit_on_error, libc, warn};
|
||||
use std::fs::File;
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -32,11 +30,11 @@ impl Default for SuRequest {
|
||||
}
|
||||
|
||||
pub struct SuInfo {
|
||||
uid: i32,
|
||||
eval_uid: i32,
|
||||
pub(super) uid: i32,
|
||||
pub(super) eval_uid: i32,
|
||||
pub(super) mgr_pkg: String,
|
||||
pub(super) mgr_uid: i32,
|
||||
cfg: DbSettings,
|
||||
mgr_pkg: String,
|
||||
mgr_uid: i32,
|
||||
access: Mutex<AccessInfo>,
|
||||
}
|
||||
|
||||
@@ -132,39 +130,18 @@ impl MagiskD {
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
// Talk to su manager
|
||||
let mut app = SuAppContext {
|
||||
cred,
|
||||
request: &req,
|
||||
info: &info,
|
||||
settings: &mut access.settings,
|
||||
sdk_int: self.sdk_int(),
|
||||
};
|
||||
app.connect_app();
|
||||
|
||||
// Before unlocking, refresh the timestamp
|
||||
access.refresh();
|
||||
@@ -291,9 +268,9 @@ impl MagiskD {
|
||||
Arc::new(SuInfo {
|
||||
uid,
|
||||
eval_uid,
|
||||
cfg,
|
||||
mgr_pkg,
|
||||
mgr_uid,
|
||||
cfg,
|
||||
access: Mutex::new(AccessInfo::new(access)),
|
||||
})
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod connect;
|
||||
mod daemon;
|
||||
mod db;
|
||||
mod pts;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::consts::MODULEROOT;
|
||||
use crate::daemon::{MagiskD, to_user_id};
|
||||
use crate::ffi::{
|
||||
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, restore_zygisk_prop, update_deny_flags,
|
||||
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, get_prop, set_prop, update_deny_flags,
|
||||
};
|
||||
use crate::socket::{IpcRead, UnixSocketExt};
|
||||
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO};
|
||||
use base::{
|
||||
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, WriteExt, cstr, error,
|
||||
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, Utf8CStr, WriteExt, cstr,
|
||||
fork_dont_care, libc, raw_cstr, warn,
|
||||
};
|
||||
use std::fmt::Write;
|
||||
@@ -15,6 +15,8 @@ use std::os::unix::net::UnixStream;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
const NBPROP: &Utf8CStr = cstr!("ro.dalvik.vm.native.bridge");
|
||||
const ZYGISKLDR: &str = "libzygisk.so";
|
||||
const UNMOUNT_MASK: u32 =
|
||||
ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr;
|
||||
|
||||
@@ -56,6 +58,105 @@ fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ZygiskState {
|
||||
pub lib_name: String,
|
||||
sockets: (Option<UnixStream>, Option<UnixStream>),
|
||||
start_count: u32 = 1,
|
||||
}
|
||||
|
||||
impl ZygiskState {
|
||||
fn connect_zygiskd(&mut self, mut client: UnixStream, daemon: &MagiskD) -> LoggedResult<()> {
|
||||
let is_64_bit: bool = client.read_decodable()?;
|
||||
let socket = if is_64_bit {
|
||||
&mut self.sockets.1
|
||||
} else {
|
||||
&mut self.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) = daemon.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()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, mut restore: bool) {
|
||||
if restore {
|
||||
self.start_count = 1;
|
||||
} else {
|
||||
self.sockets = (None, None);
|
||||
self.start_count += 1;
|
||||
if self.start_count > 3 {
|
||||
warn!("zygote crashed too many times, rolling-back");
|
||||
restore = true;
|
||||
}
|
||||
}
|
||||
|
||||
if restore {
|
||||
self.restore_prop();
|
||||
} else {
|
||||
self.set_prop();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prop(&mut self) {
|
||||
if !self.lib_name.is_empty() {
|
||||
return;
|
||||
}
|
||||
let orig = get_prop(NBPROP, false);
|
||||
self.lib_name = if orig.is_empty() || orig == "0" {
|
||||
ZYGISKLDR.to_string()
|
||||
} else {
|
||||
orig + ZYGISKLDR
|
||||
};
|
||||
set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name), false);
|
||||
// Whether Huawei's Maple compiler is enabled.
|
||||
// If so, system server will be created by a special Zygote which ignores the native bridge
|
||||
// and make system server out of our control. Avoid it by disabling.
|
||||
if get_prop(cstr!("ro.maple.enable"), false) == "1" {
|
||||
set_prop(cstr!("ro.maple.enable"), cstr!("0"), false);
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_prop(&mut self) {
|
||||
let mut orig = "0".to_string();
|
||||
if self.lib_name.len() > ZYGISKLDR.len() {
|
||||
orig = self.lib_name[ZYGISKLDR.len()..].to_string();
|
||||
}
|
||||
set_prop(NBPROP, Utf8CStr::from_string(&mut orig), false);
|
||||
self.lib_name.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl MagiskD {
|
||||
pub fn zygisk_handler(&self, client: i32) {
|
||||
let mut client = unsafe { UnixStream::from_raw_fd(client) };
|
||||
@@ -65,33 +166,18 @@ impl MagiskD {
|
||||
};
|
||||
match code {
|
||||
ZygiskRequest::GetInfo => self.get_process_info(client)?,
|
||||
ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client),
|
||||
ZygiskRequest::ConnectCompanion => self
|
||||
.zygisk
|
||||
.lock()
|
||||
.unwrap()
|
||||
.connect_zygiskd(client, self)
|
||||
.log_with_msg(|w| w.write_str("zygiskd startup error"))?,
|
||||
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
|
||||
@@ -105,54 +191,6 @@ impl MagiskD {
|
||||
})
|
||||
}
|
||||
|
||||
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()?;
|
||||
@@ -212,3 +250,18 @@ impl MagiskD {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// FFI to C++
|
||||
impl MagiskD {
|
||||
pub fn zygisk_enabled(&self) -> bool {
|
||||
self.zygisk_enabled.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn zygisk_reset(&self, restore: bool) {
|
||||
self.zygisk.lock().unwrap().reset(restore);
|
||||
}
|
||||
|
||||
pub fn restore_zygisk_prop(&self) {
|
||||
self.zygisk.lock().unwrap().restore_prop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string zygisk_lib_name = "0";
|
||||
|
||||
static void zygiskd(int socket) {
|
||||
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
|
||||
exit(-1);
|
||||
@@ -109,30 +107,3 @@ extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
rust::Str get_zygisk_lib_name() {
|
||||
return zygisk_lib_name;
|
||||
}
|
||||
|
||||
void set_zygisk_prop() {
|
||||
string native_bridge_orig = get_prop(NBPROP);
|
||||
if (native_bridge_orig.empty()) {
|
||||
native_bridge_orig = "0";
|
||||
}
|
||||
zygisk_lib_name = native_bridge_orig == "0" ? ZYGISKLDR : ZYGISKLDR + native_bridge_orig;
|
||||
set_prop(NBPROP, zygisk_lib_name.data());
|
||||
// Whether Huawei's Maple compiler is enabled.
|
||||
// If so, system server will be created by a special Zygote which ignores the native bridge
|
||||
// and make system server out of our control. Avoid it by disabling.
|
||||
if (get_prop("ro.maple.enable") == "1") {
|
||||
set_prop("ro.maple.enable", "0");
|
||||
}
|
||||
}
|
||||
|
||||
void restore_zygisk_prop() {
|
||||
string native_bridge_orig = "0";
|
||||
if (zygisk_lib_name.length() > strlen(ZYGISKLDR)) {
|
||||
native_bridge_orig = zygisk_lib_name.substr(strlen(ZYGISKLDR));
|
||||
}
|
||||
set_prop(NBPROP, native_bridge_orig.data());
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ void HookContext::hook_jni_methods(JNIEnv *env, const char *clz, JNIMethods meth
|
||||
auto &new_method = new_methods[i];
|
||||
if (new_method.fnPtr == method.fnPtr) {
|
||||
auto &old_method = old_methods[i];
|
||||
ZLOGD("replace %s#%s%s %p -> %p\n", clz, method.name, method.signature, old_method.fnPtr, method.fnPtr);
|
||||
ZLOGV("replace %s#%s%s %p -> %p\n", clz, method.name, method.signature, old_method.fnPtr, method.fnPtr);
|
||||
method.fnPtr = old_method.fnPtr;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
mod daemon;
|
||||
|
||||
pub use daemon::zygisk_should_load_module;
|
||||
pub use daemon::{ZygiskState, zygisk_should_load_module};
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
#endif
|
||||
|
||||
// Extreme verbose logging
|
||||
#define ZLOGV(...) ZLOGD(__VA_ARGS__)
|
||||
//#define ZLOGV(...) (void*)0
|
||||
//#define ZLOGV(...) ZLOGD(__VA_ARGS__)
|
||||
#define ZLOGV(...) (void*)0
|
||||
|
||||
void hook_entry();
|
||||
void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods);
|
||||
|
||||
@@ -20,7 +20,7 @@ pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
|
||||
pub const MAGISKDB: &str = concatcp!(SECURE_DIR, "/magisk.db");
|
||||
|
||||
// tmpfs paths
|
||||
const INTERNAL_DIR: &str = ".magisk";
|
||||
pub const INTERNAL_DIR: &str = ".magisk";
|
||||
pub const MAIN_CONFIG: &str = concatcp!(INTERNAL_DIR, "/config");
|
||||
pub const PREINITMIRR: &str = concatcp!(INTERNAL_DIR, "/preinit");
|
||||
pub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, "/modules");
|
||||
@@ -37,6 +37,7 @@ pub const SEPOL_PROC_DOMAIN: &str = "magisk";
|
||||
pub const MAGISK_PROC_CON: &str = concatcp!("u:r:", SEPOL_PROC_DOMAIN, ":s0");
|
||||
// Unconstrained file type that anyone can access
|
||||
pub const SEPOL_FILE_TYPE: &str = "magisk_file";
|
||||
pub const MAGISK_FILE_CON: &str = concatcp!("u:object_r:", SEPOL_FILE_TYPE, ":s0");
|
||||
// Log pipe that only root and zygote can open
|
||||
pub const SEPOL_LOG_TYPE: &str = "magisk_log_file";
|
||||
pub const MAGISK_LOG_CON: &str = concatcp!("u:object_r:", SEPOL_LOG_TYPE, ":s0");
|
||||
|
||||
@@ -32,9 +32,6 @@ on nonencrypted
|
||||
|
||||
on property:sys.boot_completed=1
|
||||
exec {0} 0 0 -- {1}/magisk --boot-complete
|
||||
|
||||
on property:init.svc.zygote=stopped
|
||||
exec {0} 0 0 -- {1}/magisk --zygote-restart
|
||||
"#,
|
||||
"u:r:magisk:s0", tmp_dir
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ emu_args=
|
||||
emu_pid=
|
||||
|
||||
atd_min_api=30
|
||||
atd_max_api=35
|
||||
atd_max_api=36
|
||||
huge_ram_min_api=26
|
||||
|
||||
case $(uname -m) in
|
||||
@@ -94,7 +94,7 @@ resolve_vars() {
|
||||
UpsideDownCakePrivacySandbox) api=34 ;;
|
||||
VanillaIceCream) api=35 ;;
|
||||
Baklava) api=36 ;;
|
||||
36*CANARY) api=36 ;;
|
||||
36*CANARY) api=10000 ;;
|
||||
*)
|
||||
print_error "! Unknown system image version '$ver'"
|
||||
exit 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# AVD MagiskInit Setup
|
||||
#####################################################################
|
||||
#
|
||||
# Support API level: 23 - 35
|
||||
# Support API level: 23 - 36
|
||||
#
|
||||
# With an emulator booted and accessible via ADB, usage:
|
||||
# ./build.py avd_patch path/to/booted/avd-image/ramdisk.img
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# AVD Magisk Setup
|
||||
#####################################################################
|
||||
#
|
||||
# Support API level: 23 - 35
|
||||
# Support API level: 23 - 36
|
||||
#
|
||||
# For developing Magisk, just use:
|
||||
# ./build.py emulator
|
||||
|
||||
Reference in New Issue
Block a user