/* * 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 // Enable dead_code until feature flag is removed. #![cfg_attr(not(enable_libbpf), allow(dead_code))] use android_ids::{AID_ROOT, AID_SYSTEM}; use android_logger::AndroidLogger; use anyhow::{anyhow, ensure}; use libbpf_rs::{MapCore, ObjectBuilder}; use libc::{mode_t, S_IRGRP, S_IRUSR, S_IWGRP, S_IWUSR}; use log::{debug, error, info, 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}, }; 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>>, 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; 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), })) } } struct MapDesc { name: &'static str, perms: mode_t, } struct ProgDesc { name: &'static str, } struct BpfFileDesc { filename: &'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", critical: false, owner: AID_ROOT, group: AID_SYSTEM, maps: &[ MapDesc { name: "cpu_last_pid_map", perms: PERM_GWO }, MapDesc { name: "cpu_last_update_map", perms: PERM_GWO }, MapDesc { name: "cpu_policy_map", perms: PERM_GWO }, MapDesc { name: "freq_to_idx_map", perms: PERM_GWO }, MapDesc { name: "nr_active_map", perms: PERM_GWO }, MapDesc { name: "pid_task_aggregation_map", perms: PERM_GWO }, MapDesc { name: "pid_time_in_state_map", perms: PERM_GRO }, MapDesc { name: "pid_tracked_hash_map", perms: PERM_GWO }, MapDesc { name: "pid_tracked_map", perms: PERM_GWO }, MapDesc { name: "policy_freq_idx_map", perms: PERM_GWO }, MapDesc { name: "policy_nr_active_map", perms: PERM_GWO }, MapDesc { name: "total_time_in_state_map", perms: PERM_GRW }, MapDesc { name: "uid_concurrent_times_map", perms: PERM_GRW }, MapDesc { name: "uid_last_update_map", perms: PERM_GRW }, MapDesc { name: "uid_time_in_state_map", perms: PERM_GRW }, ], progs: &[ ProgDesc { name: "tracepoint_power_cpu_frequency" }, ProgDesc { name: "tracepoint_sched_sched_process_free" }, ProgDesc { name: "tracepoint_sched_sched_switch" }, ], }]; fn libbpf_worker(file_desc: &BpfFileDesc) -> Result<(), anyhow::Error> { info!("Loading {}", file_desc.filename); let filepath = Path::new("/etc/bpf/").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(); 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(()) } #[cfg(enable_libbpf)] 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); } }; } } #[cfg(not(enable_libbpf))] fn load_libbpf_progs() { // Empty stub for feature flag disabled case info!("Loading libbpf programs DISABLED"); } fn main() { let kmsg_fd = env::var("ANDROID_FILE__dev_kmsg").unwrap().parse::().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); })); 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(); } }