blob: 481b81cf9bdf3255edb024f7b6664bbb6e1cbcb4 [file] [log] [blame]
/*
* Copyright (C) 2024 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.
*/
//! BPF loader for system and vendor applications
use android_ids::{AID_ROOT, AID_SYSTEM};
use android_logger::AndroidLogger;
use anyhow::{anyhow, ensure};
use libbpf_rs::{set_print, MapCore, ObjectBuilder, PrintLevel};
use libc::{mode_t, S_IRGRP, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_ISVTX, S_IWGRP, S_IWUSR};
use log::{debug, error, info, warn, Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
use std::{
cmp::max,
env, fs,
fs::{File, Permissions},
io::{LineWriter, Write},
os::fd::FromRawFd,
os::unix::fs::{chown, PermissionsExt},
panic,
path::Path,
sync::{Arc, Mutex},
};
const fn kver(a: u32, b: u32, c: u32) -> u32 {
(a << 24) + (b << 16) + c
}
const KVER_NONE: u32 = kver(0, 0, 0);
const KVER_INF: u32 = 0xFFFFFFFF;
enum KernelLevel {
// Commented out unused due to rust complaining...
// EMERG = 0,
// ALERT = 1,
// CRIT = 2,
ERR = 3,
WARNING = 4,
// NOTICE = 5,
INFO = 6,
DEBUG = 7,
}
fn level_to_kern_level(level: &Level) -> u8 {
let result = match level {
Level::Error => KernelLevel::ERR,
Level::Warn => KernelLevel::WARNING,
Level::Info => KernelLevel::INFO,
Level::Debug => KernelLevel::DEBUG,
Level::Trace => KernelLevel::DEBUG,
};
result as u8
}
/// A logger implementation to enable bpfloader to write to kmsg on error as
/// bpfloader runs at early init prior to the availability of standard Android
/// logging. If a crash were to occur, we can disrupt boot, and therefore we
/// need the ability to access the logs on the serial port.
pub struct BpfKmsgLogger {
log_level: LevelFilter,
tag: String,
kmsg_writer: Arc<Mutex<Box<dyn Write + Send>>>,
a_logger: AndroidLogger,
}
impl Log for BpfKmsgLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.log_level || self.a_logger.enabled(metadata)
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
if record.metadata().level() <= self.log_level {
let mut writer = self.kmsg_writer.lock().unwrap();
write!(
writer,
"<{}>{}: {}",
level_to_kern_level(&record.level()),
self.tag,
record.args()
)
.unwrap();
let _ = writer.flush();
}
self.a_logger.log(record);
}
fn flush(&self) {}
}
impl BpfKmsgLogger {
/// Initialize the logger
pub fn init(kmsg_file: File) -> Result<(), SetLoggerError> {
let alog_level = LevelFilter::Info;
let kmsg_level = LevelFilter::Error;
let log_config = android_logger::Config::default()
.with_tag("BpfLoader-rs")
.with_max_level(alog_level)
.with_log_buffer(android_logger::LogId::Main)
.format(|buf, record| writeln!(buf, "{}", record.args()));
let writer = Box::new(LineWriter::new(kmsg_file)) as Box<dyn Write + Send>;
log::set_max_level(max(alog_level, kmsg_level));
log::set_boxed_logger(Box::new(BpfKmsgLogger {
log_level: kmsg_level,
tag: "BpfLoader-rs".to_string(),
kmsg_writer: Arc::new(Mutex::new(writer)),
a_logger: AndroidLogger::new(log_config),
}))
}
}
fn libbpf_print(level: PrintLevel, mut msg: String) {
if msg.ends_with('\n') {
msg.pop();
}
match level {
PrintLevel::Debug => debug!("{}", msg),
PrintLevel::Info => info!("{}", msg),
PrintLevel::Warn => warn!("{}", msg),
}
}
#[allow(dead_code)]
struct MapDesc {
name: &'static str,
perms: mode_t,
// Map is loaded if kernel_version() is >= min_kver and < max_kver
min_kver: u32,
max_kver: u32,
}
impl MapDesc {
pub const fn new(name: &'static str, perms: mode_t) -> Self {
MapDesc { name, perms, min_kver: KVER_NONE, max_kver: KVER_INF }
}
}
#[allow(dead_code)]
struct ProgDesc {
name: &'static str,
// Prog is loaded if kernel_version() is >= min_kver and < max_kver
min_kver: u32,
max_kver: u32,
}
impl ProgDesc {
pub const fn new(name: &'static str) -> Self {
ProgDesc { name, min_kver: KVER_NONE, max_kver: KVER_INF }
}
}
struct BpfFileDesc {
filename: &'static str,
// The directory where the BPF file is located.
dir: &'static str,
// Maps and Progs are pinned under /sys/fs/bpf/<prefix>.
prefix: &'static str,
// Warning: setting this to 'true' will cause the system to boot loop if there are any issues
// loading the bpf program.
critical: bool,
owner: u32,
group: u32,
maps: &'static [MapDesc],
progs: &'static [ProgDesc],
}
const PERM_GRW: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
const PERM_GRO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP;
const PERM_GWO: mode_t = S_IRUSR | S_IWUSR | S_IWGRP;
const PERM_UGR: mode_t = S_IRUSR | S_IRGRP;
const FILE_ARR: &[BpfFileDesc] = &[BpfFileDesc {
filename: "timeInState.bpf",
dir: "/etc/bpf/",
prefix: "",
critical: false,
owner: AID_ROOT,
group: AID_SYSTEM,
maps: &[
MapDesc::new("cpu_last_pid_map", PERM_GWO),
MapDesc::new("cpu_last_update_map", PERM_GWO),
MapDesc::new("cpu_policy_map", PERM_GWO),
MapDesc::new("freq_to_idx_map", PERM_GWO),
MapDesc::new("nr_active_map", PERM_GWO),
MapDesc::new("pid_task_aggregation_map", PERM_GWO),
MapDesc::new("pid_time_in_state_map", PERM_GRO),
MapDesc::new("pid_tracked_hash_map", PERM_GWO),
MapDesc::new("pid_tracked_map", PERM_GWO),
MapDesc::new("policy_freq_idx_map", PERM_GWO),
MapDesc::new("policy_nr_active_map", PERM_GWO),
MapDesc::new("total_time_in_state_map", PERM_GRW),
MapDesc::new("uid_concurrent_times_map", PERM_GRW),
MapDesc::new("uid_last_update_map", PERM_GRW),
MapDesc::new("uid_time_in_state_map", PERM_GRW),
],
progs: &[
ProgDesc::new("tracepoint_power_cpu_frequency"),
ProgDesc::new("tracepoint_sched_sched_process_free"),
ProgDesc::new("tracepoint_sched_sched_switch"),
],
}];
fn create_dir(dir_path: &Path) -> Result<(), anyhow::Error> {
if dir_path.exists() {
return Ok(());
}
fs::create_dir(dir_path)
.map_err(|e| anyhow!("Failed to create {}: {e}", dir_path.display()))?;
// The cast is not unnecessary on all platforms.
#[allow(clippy::unnecessary_cast)]
fs::set_permissions(
dir_path,
Permissions::from_mode((S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) as u32),
)
.map_err(|e| anyhow!("Failed to set permissions for {}: {e}", dir_path.display()))?;
Ok(())
}
fn libbpf_worker(file_desc: &BpfFileDesc) -> Result<(), anyhow::Error> {
info!("Loading {}", file_desc.filename);
let filepath = Path::new(file_desc.dir).join(file_desc.filename);
ensure!(filepath.exists(), "File not found {}", filepath.display());
let filename =
filepath.file_stem().ok_or_else(|| anyhow!("Failed to parse stem from filename"))?;
let filename = filename.to_str().ok_or_else(|| anyhow!("Failed to parse filename"))?;
let mut ob = ObjectBuilder::default();
let open_file = ob.open_file(&filepath)?;
let mut loaded_file = open_file.load()?;
let bpffs_path = "/sys/fs/bpf/".to_owned() + file_desc.prefix;
create_dir(Path::new(&bpffs_path))?;
for mut map in loaded_file.maps_mut() {
let mut desc_found = false;
let name =
map.name().to_str().ok_or_else(|| anyhow!("Failed to parse map name into UTF-8"))?;
let name = String::from(name);
for map_desc in file_desc.maps {
if map_desc.name == name {
desc_found = true;
let pinpath_str = bpffs_path.clone() + "map_" + filename + "_" + &name;
let pinpath = Path::new(&pinpath_str);
debug!("Pinning: {}", pinpath.display());
map.pin(pinpath).map_err(|e| anyhow!("Failed to pin map {name}: {e}"))?;
fs::set_permissions(pinpath, Permissions::from_mode(map_desc.perms as _)).map_err(
|e| {
anyhow!(
"Failed to set permissions: {} on pinned map {}: {e}",
map_desc.perms,
pinpath.display()
)
},
)?;
chown(pinpath, Some(file_desc.owner), Some(file_desc.group)).map_err(|e| {
anyhow!(
"Failed to chown {} with owner: {} group: {} err: {e}",
pinpath.display(),
file_desc.owner,
file_desc.group
)
})?;
break;
}
}
ensure!(desc_found, "Descriptor for {name} not found!");
}
for mut prog in loaded_file.progs_mut() {
let mut desc_found = false;
let name =
prog.name().to_str().ok_or_else(|| anyhow!("Failed to parse prog name into UTF-8"))?;
let name = String::from(name);
for prog_desc in file_desc.progs {
if prog_desc.name == name {
desc_found = true;
let pinpath_str = bpffs_path.clone() + "prog_" + filename + "_" + &name;
let pinpath = Path::new(&pinpath_str);
debug!("Pinning: {}", pinpath.display());
prog.pin(pinpath).map_err(|e| anyhow!("Failed to pin prog {name}: {e}"))?;
fs::set_permissions(pinpath, Permissions::from_mode(PERM_UGR as _)).map_err(
|e| {
anyhow!(
"Failed to set permissions on pinned prog {}: {e}",
pinpath.display()
)
},
)?;
chown(pinpath, Some(file_desc.owner), Some(file_desc.group)).map_err(|e| {
anyhow!(
"Failed to chown {} with owner: {} group: {} err: {e}",
pinpath.display(),
file_desc.owner,
file_desc.group
)
})?;
break;
}
}
ensure!(desc_found, "Descriptor for {name} not found!");
}
Ok(())
}
fn load_libbpf_progs() {
info!("Loading libbpf programs");
for file_desc in FILE_ARR {
if let Err(e) = libbpf_worker(file_desc) {
if file_desc.critical {
panic!("Error when loading {0}: {e}", file_desc.filename);
} else {
error!("Error when loading {0}: {e}", file_desc.filename);
}
};
}
}
fn main() {
let kmsg_fd = env::var("ANDROID_FILE__dev_kmsg").unwrap().parse::<i32>().unwrap();
// SAFETY: The init script opens this file for us
let kmsg_file = unsafe { File::from_raw_fd(kmsg_fd) };
if let Err(logger) = BpfKmsgLogger::init(kmsg_file) {
error!("BpfLoader-rs: log::setlogger failed: {}", logger);
}
// Redirect panic messages to both logcat and serial port
panic::set_hook(Box::new(|panic_info| {
error!("{}", panic_info);
}));
// Enable logging from libbpf
set_print(Some((PrintLevel::Debug, libbpf_print)));
load_libbpf_progs();
info!("Loading legacy BPF progs");
// SAFETY: Linking in the existing legacy bpfloader functionality.
// Any of the four following bindgen functions can abort() or exit()
// on failure and execNetBpfLoadDone() execve()'s.
unsafe {
bpf_android_bindgen::initLogging();
bpf_android_bindgen::createBpfFsSubDirectories();
bpf_android_bindgen::legacyBpfLoader();
bpf_android_bindgen::execNetBpfLoadDone();
}
}