blob: f376ccb6ab3804eb41a419d0998226a551660a29 [file]
//
// Copyright (C) 2025 The Android Open-Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use anyhow::{bail, Context, Result};
use bitflags::bitflags;
use libc::{
dl_iterate_phdr, dl_phdr_info, mallopt, mprotect, size_t, PROT_EXEC, PROT_READ, PT_LOAD,
};
use native_activity_thread_bindgen::{tzset, PF_X};
use nix::{
errno::Errno,
sys::resource::{getrlimit, setrlimit, Resource},
unistd::{getuid, sysconf, SysconfVar},
};
use rustutils::android::process::android_mallopt;
use rustutils::android::process::MalloptOpcode;
use rustutils::android::system_properties;
use std::ffi::{c_int, c_ulong, c_void};
use std::io::Error;
use std::slice;
const AID_APP_START: u32 = 10000;
static PROP_ZYGOTE_CORE_DUMP: &str = "persist.zygote.core_dump";
static PROP_DEBUGGABLE: &str = "ro.debuggable";
/// A safe wrapper around tzset()
pub fn reset_time_zone() {
// Refresh Bionic's timezone information.
// SAFETY: Not passing any function parameter.
unsafe { tzset() };
}
/// Since Native Zygote only forks isolated process, which cannot read the `ro.debuggable` property,
/// we should call this function and cache the value when the Native Zygote server is initialized.
pub fn get_or_init_debuggable() -> bool {
static DEBUGGABLE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*DEBUGGABLE
.get_or_init(|| system_properties::read_bool(PROP_DEBUGGABLE, false).unwrap_or(false))
}
fn get_page_size() -> usize {
match sysconf(SysconfVar::PAGE_SIZE) {
Ok(Some(size)) => size as usize,
Ok(None) => panic!("sysconf(PAGE_SIZE) returned None"),
Err(e) => panic!("sysconf(PAGE_SIZE) returned an error: {e}"),
}
}
#[allow(non_camel_case_types)]
#[cfg(target_pointer_width = "64")]
type ElfW_Phdr = libc::Elf64_Phdr;
#[allow(non_camel_case_types)]
#[cfg(target_pointer_width = "32")]
type ElfW_Phdr = libc::Elf32_Phdr;
/// A callback function for `dl_iterate_phdr` which changes XO regions to RX.
///
/// # Safety
/// Callers must ensure that `info` is a valid pointer to `dl_phdr_info` which outlives this
/// function and contains a shared object information.
unsafe extern "C" fn disable_execute_only(
info: *mut dl_phdr_info,
_size: usize,
_data: *mut c_void,
) -> c_int {
if info.is_null() {
return 0;
}
// SAFETY: `info` is a valid pointer which outlives this function and is aligned properly.
let info_ref = unsafe { &*info };
let phdr_ptr = info_ref.dlpi_phdr;
let phdr_count = info_ref.dlpi_phnum as usize;
// SAFETY: `phdr_ptr` points to a valid ElfW_Phdr array of `phdr_count` elements.
let program_headers: &[ElfW_Phdr] = unsafe { slice::from_raw_parts(phdr_ptr, phdr_count) };
let page_mask = !(get_page_size() - 1);
// Search for any execute-only segments and mark them read+execute.
// This operation only affects RWX flags because of the implementation
// of mprotect, so other architectural flags (like PROT_BTI) will not be cleared.
for phdr in program_headers {
if phdr.p_type == PT_LOAD && phdr.p_flags == PF_X {
let addr = (info_ref.dlpi_addr + phdr.p_vaddr) as usize;
let aligned_addr = (addr & page_mask) as *mut c_void;
let len = phdr.p_memsz as size_t;
let adjusted_len = addr + len - aligned_addr as usize;
// SAFETY: `aligned_addr` is aligned to a page boundary and `adjusted_len` is a valid
// size of the region including the XO segment.
let ret = unsafe { mprotect(aligned_addr, adjusted_len, PROT_READ | PROT_EXEC) };
if ret != 0 {
log::warn!("Failed to mprotect(): {}", Error::last_os_error());
}
}
}
0
}
bitflags! {
/// Runtime flag constants.
/// Must match values in com.android.internal.os.Zygote.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeFlags: u32 {
const DEBUG_ENABLE_JDWP = 1;
const DEBUG_ENABLE_CHECKJNI = 1 << 1;
const DEBUG_ENABLE_ASSERT = 1 << 2;
const DEBUG_ENABLE_SAFEMODE = 1 << 3;
const DEBUG_ENABLE_JNI_LOGGING = 1 << 4;
const DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
const DEBUG_ALWAYS_JIT = 1 << 6;
const DEBUG_NATIVE_DEBUGGABLE = 1 << 7;
const DEBUG_JAVA_DEBUGGABLE = 1 << 8;
const DISABLE_VERIFIER = 1 << 9;
const ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
const DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 11;
const API_ENFORCEMENT_POLICY_MASK = (1 << 12) | (1 << 13);
const PROFILE_SYSTEM_SERVER = 1 << 14;
const PROFILE_FROM_SHELL = 1 << 15;
const USE_APP_IMAGE_STARTUP_CACHE = 1 << 16;
const DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17;
const DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
const MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
const MEMORY_TAG_LEVEL_TBI = 1 << 19;
const MEMORY_TAG_LEVEL_ASYNC = 2 << 19;
const MEMORY_TAG_LEVEL_SYNC = 3 << 19;
const GWP_ASAN_LEVEL_MASK = (1 << 21) | (1 << 22);
const GWP_ASAN_LEVEL_NEVER = 0 << 21;
const GWP_ASAN_LEVEL_LOTTERY = 1 << 21;
const GWP_ASAN_LEVEL_ALWAYS = 2 << 21;
const GWP_ASAN_LEVEL_DEFAULT = 3 << 21;
const NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23;
const PROFILEABLE = 1 << 24;
const DEBUG_ENABLE_PTRACE = 1 << 25;
const ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26;
const ENABLE_EXECUTE_ONLY_MEMORY = 1 << 27;
}
}
impl RuntimeFlags {
pub fn get_heap_tagging_level(&self) -> c_int {
match self.intersection(Self::MEMORY_TAG_LEVEL_MASK) {
Self::MEMORY_TAG_LEVEL_TBI => libc::M_HEAP_TAGGING_LEVEL_TBI,
Self::MEMORY_TAG_LEVEL_ASYNC => libc::M_HEAP_TAGGING_LEVEL_ASYNC,
Self::MEMORY_TAG_LEVEL_SYNC => libc::M_HEAP_TAGGING_LEVEL_SYNC,
_ => libc::M_HEAP_TAGGING_LEVEL_NONE,
}
}
pub fn is_native_heap_zero_init_enabled(&self) -> bool {
self.contains(Self::NATIVE_HEAP_ZERO_INIT_ENABLED)
}
pub fn is_ptrace_enabled(&self) -> bool {
self.contains(Self::DEBUG_ENABLE_PTRACE)
}
pub fn is_profileable_from_shell(&self) -> bool {
self.contains(Self::PROFILE_FROM_SHELL)
}
pub fn is_profileable(&self) -> bool {
self.contains(Self::PROFILEABLE)
}
pub fn is_execute_only_memory_enabled(&self) -> bool {
self.contains(Self::ENABLE_EXECUTE_ONLY_MEMORY)
}
}
pub fn apply_runtime_flags(runtime_flags: u32) -> Result<()> {
let Some(flags) = RuntimeFlags::from_bits(runtime_flags) else {
bail!("runtime_flags doesn't have a valid representation: {:#x}", runtime_flags);
};
// Set process properties to enable debugging if required.
if flags.is_ptrace_enabled() {
if let Err(e) = enable_debugger() {
log::warn!("Failed to enable debbuger: {e}");
}
}
if flags.is_profileable_from_shell() {
// simpleperf needs the process to be dumpable to profile it.
prctl_set_dumpable(Dumpable::ByUser).expect("prctl(PR_SET_DUMPABLE) failed");
}
if get_or_init_debuggable() || flags.is_profileable() {
// SAFETY: This opcode takes no arguments so a nullptr is passed
// instead.
let ret = unsafe {
android_mallopt(MalloptOpcode::InitZygoteChildProfiling, std::ptr::null_mut(), 0)
};
if ret.is_err() {
log::error!(
"Failed to android_mallopt(M_INIT_ZYGOTE_CHILD_HEAP_PROFILING): {}",
Error::last_os_error()
);
}
}
if !flags.is_native_heap_zero_init_enabled() {
// SAFETY: Setting configuration with valid argument (0 = disable).
let ret = unsafe { mallopt(libc::M_BIONIC_ZERO_INIT, 0) };
if ret == 0 {
log::warn!("Failed to mallopt(M_BIONIC_ZERO_INIT): {}", Error::last_os_error());
}
}
// SAFETY: Setting configuration with valid argument (as defined in get_heap_tagging_level).
let ret =
unsafe { mallopt(libc::M_BIONIC_SET_HEAP_TAGGING_LEVEL, flags.get_heap_tagging_level()) };
if ret == 0 {
log::warn!(
"Failed to mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL): {}",
Error::last_os_error()
);
}
// If the app does not support execute-only memory, then iterate through
// the shared objects and mark them readable.
if cfg!(feature = "build_execute_only_memory") && !flags.is_execute_only_memory_enabled() {
// SAFETY: Passes a callback function which has the expected function signature. The
// callback doesn't use `data`. The libc runtime guarantees to meet the safety requirements
// of the callback.
unsafe { dl_iterate_phdr(Some(disable_execute_only), std::ptr::null_mut()) };
}
Ok(())
}
// Enum corresponding to SUID_DUMP_* defined in linux/sched/coredump.h.
#[derive(PartialEq)]
enum Dumpable {
Disable,
ByUser,
ByRoot,
}
impl TryFrom<i32> for Dumpable {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
let dumpable = match value {
0 => Dumpable::Disable,
1 => Dumpable::ByUser,
2 => Dumpable::ByRoot,
_ => return Err(()),
};
Ok(dumpable)
}
}
impl From<Dumpable> for i32 {
fn from(value: Dumpable) -> Self {
match value {
Dumpable::Disable => 0,
Dumpable::ByUser => 1,
Dumpable::ByRoot => 2,
}
}
}
// TODO(b/450462991): Use nix::sys::prctl::get_dumpable once `prctl` module is available to android
// and this issue is fixed: https://github.com/nix-rust/nix/issues/2684.
fn prctl_get_dumpable() -> nix::Result<Dumpable> {
// SAFETY: Trivially safe. See `man PR_GET_DUMPABLE`.
let res = unsafe { libc::prctl(libc::PR_GET_DUMPABLE) };
if res == -1 {
return Err(Errno::last());
}
Ok(Dumpable::try_from(res)
.unwrap_or_else(|_| panic!("prctl(PR_GET_DUMPABLE) returned an unexpected value: {res}")))
}
// TODO(b/450462991): Use nix::sys::prctl::set_dumpable once `prctl` module is available to
// android.
fn prctl_set_dumpable(dumpable: Dumpable) -> nix::Result<()> {
let val: i32 = dumpable.into();
// SAFETY: Trivially safe. See `man PR_SET_DUMPABLE`.
let res = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, val) };
if res == -1 {
return Err(Errno::last());
}
Ok(())
}
fn prctl_set_ptracer(pid: c_ulong) -> nix::Result<()> {
// SAFETY: Trivially safe. See `man PR_SET_PTRACER`.
let res = unsafe { libc::prctl(libc::PR_SET_PTRACER, pid) };
if res == -1 {
return Err(Errno::last());
}
Ok(())
}
pub fn setup_process_dumpability() -> Result<()> {
if prctl_get_dumpable().context("prctl(PR_GET_DUMPABLE) failed")? == Dumpable::ByRoot
&& getuid().as_raw() >= AID_APP_START
{
prctl_set_dumpable(Dumpable::Disable).context("prctl(PR_SET_DUMPABLE) failed")?;
}
Ok(())
}
fn enable_debugger() -> Result<()> {
// To let a non-privileged gdbserver attach to this process,
// we must set our dumpable flag.
prctl_set_dumpable(Dumpable::ByUser).context("prctl(PR_SET_DUMPABLE) failed")?;
// A non-privileged native debugger should be able to attach to the debuggable app,
// even if Yama is enabled (see kernel/Documentation/security/Yama.txt).
if let Err(errno) = prctl_set_ptracer(libc::PR_SET_PTRACER_ANY) {
// if Yama is off prctl(PR_SET_PTRACER) returns EINVAL -
// don't log in this case since it's expected behaviour.
if errno != Errno::EINVAL {
bail!("prctl(PR_SET_TRACER, PR_SET_PTRACER_ANY) failed: {errno}");
}
}
// Set the core dump size to zero unless wanted
// (see also coredump_setup in build/envsetup.sh).
if system_properties::read_bool(PROP_ZYGOTE_CORE_DUMP, false)
.with_context(|| format!("Failed to read {PROP_ZYGOTE_CORE_DUMP}"))?
{
// Set the soft limit on core dump size to 0 without changing the hard limit.
let (_, hard_limit) =
getrlimit(Resource::RLIMIT_CORE).context("getrlimit(RLIMIT_CORE) failed")?;
setrlimit(Resource::RLIMIT_CORE, 0, hard_limit).context("setrlimit(RLIMIT_CORE) failed")?;
}
Ok(())
}