1 //! Handles allow list of code that can be instrumented on user devices.
2 use anyhow::{anyhow, bail, Result};
3 use uprobestats_proto::config::{uprobestats_config::task::ProbeConfig, UprobestatsConfig};
4
5 const ALLOWED_METHOD_PREFIXES: [&str; 4] = [
6 "com.android.server.am.ActivityManagerService$LocalService.updateDeviceIdleTempAllowlist",
7 "com.android.server.am.CachedAppOptimizer",
8 "com.android.server.am.OomAdjuster",
9 "com.android.server.am.OomAdjusterModernImpl",
10 ];
11
12 /// Checks if the given config is allowed to be instrumented on user devices.
13 ///
14 /// If the device is a user build, all configs are allowed. Otherwise, only configs that are
15 /// explicitly allowed are allowed.
is_allowed( config: &UprobestatsConfig, is_user_build: bool, offsets_api_enabled: bool, ) -> Result<bool>16 pub fn is_allowed(
17 config: &UprobestatsConfig,
18 is_user_build: bool,
19 offsets_api_enabled: bool,
20 ) -> Result<bool> {
21 if !is_user_build {
22 return Ok(true);
23 }
24 for task in &config.tasks {
25 for probe in &task.probe_configs {
26 let full_method_name = get_full_method_name(probe, offsets_api_enabled)?;
27 let mut allowed = false;
28 for prefix in ALLOWED_METHOD_PREFIXES {
29 if full_method_name == prefix
30 || full_method_name.starts_with(&(prefix.to_string() + "("))
31 || full_method_name.starts_with(&(prefix.to_string() + "."))
32 || full_method_name.starts_with(&(prefix.to_string() + "$"))
33 {
34 allowed = true;
35 break;
36 }
37 }
38 if !allowed {
39 return Ok(false);
40 }
41 }
42 }
43 Ok(true)
44 }
45
get_full_method_name(probe_config: &ProbeConfig, offsets_api_enabled: bool) -> Result<String>46 fn get_full_method_name(probe_config: &ProbeConfig, offsets_api_enabled: bool) -> Result<String> {
47 if offsets_api_enabled {
48 let Some(ref fqcn) = probe_config.fully_qualified_class_name else {
49 bail!("Fully qualified class name is empty")
50 };
51 let Some(ref method_name) = probe_config.method_name else { bail!("Method name is empty") };
52 Ok(format!("{}.{}", fqcn, method_name))
53 } else {
54 let Some(ref method_signature) = probe_config.method_signature else {
55 bail!("Method signature is empty")
56 };
57 let mut parts = method_signature.split(" ");
58 parts.nth(1).map(String::from).ok_or(anyhow!("Method signature is invalid"))
59 }
60 }
61
62 #[cfg(test)]
63 mod tests {
64 use super::*;
65 use std::clone::Clone;
66 use uprobestats_proto::config::uprobestats_config::Task;
67
68 #[test]
everything_allowed_on_userdebug()69 fn everything_allowed_on_userdebug() {
70 let config = setup_config(vec![setup_probe_config(
71 "com.android.server.am.SomeClass",
72 "doWork",
73 vec![],
74 )]);
75
76 assert!(is_allowed(&config, false, true).unwrap());
77 assert!(is_allowed(&config, false, false).unwrap());
78 }
79
80 #[test]
oom_adjuster_allowed()81 fn oom_adjuster_allowed() {
82 let config = setup_config(vec![
83 setup_probe_config(
84 "com.android.server.am.OomAdjuster",
85 "setUidTempAllowlistStateLSP",
86 vec!["int".to_string(), "boolean".to_string()],
87 ),
88 setup_probe_config(
89 "com.android.server.am.OomAdjuster$$ExternalSyntheticLambda0",
90 "accept",
91 vec!["java.lang.String".to_string()],
92 ),
93 ]);
94
95 assert!(is_allowed(&config, false, false).unwrap());
96 assert!(is_allowed(&config, true, false).unwrap());
97 assert!(is_allowed(&config, false, true).unwrap());
98 assert!(is_allowed(&config, true, true).unwrap());
99 }
100
101 #[test]
update_device_idle_temp_allowlist_allowed()102 fn update_device_idle_temp_allowlist_allowed() {
103 let config = setup_config(vec![setup_probe_config(
104 "com.android.server.am.ActivityManagerService$LocalService",
105 "updateDeviceIdleTempAllowlist",
106 vec![],
107 )]);
108
109 assert_eq!(
110 "com.android.server.am.ActivityManagerService$LocalService.updateDeviceIdleTempAllowlist()",
111 &get_full_method_name(&config.tasks[0].probe_configs[0], false).unwrap()
112 );
113
114 assert!(is_allowed(&config, false, false).unwrap());
115 // TODO: does this actually work in the c++ impl? @mattgilbride ask @yutingtseng
116 // assert!(is_allowed(&config, true, false).unwrap());
117 assert!(is_allowed(&config, false, true).unwrap());
118 assert!(is_allowed(&config, true, true).unwrap());
119 }
120
121 #[test]
oom_adjuster_with_suffix_disallowed()122 fn oom_adjuster_with_suffix_disallowed() {
123 let config = setup_config(vec![setup_probe_config(
124 "com.android.server.am.OomAdjusterWithSomeSuffix",
125 "doWork",
126 vec![],
127 )]);
128
129 assert!(!is_allowed(&config, true, false).unwrap());
130 assert!(!is_allowed(&config, true, true).unwrap());
131 }
132
133 #[test]
disallowed_method_in_second_task_disallowed()134 fn disallowed_method_in_second_task_disallowed() {
135 let config = setup_config(vec![
136 setup_probe_config("com.android.server.am.OomAdjusterWithSomeSuffix", "doWork", vec![]),
137 setup_probe_config("com.android.server.am.DisallowedClass", "doWork", vec![]),
138 ]);
139
140 assert!(!is_allowed(&config, true, false).unwrap());
141 assert!(!is_allowed(&config, true, true).unwrap());
142 }
143
setup_config(probe_configs: Vec<ProbeConfig>) -> UprobestatsConfig144 fn setup_config(probe_configs: Vec<ProbeConfig>) -> UprobestatsConfig {
145 UprobestatsConfig {
146 tasks: vec![Task { probe_configs, ..Task::default() }],
147 ..UprobestatsConfig::default()
148 }
149 }
150
setup_probe_config( class_name: &str, method_name: &str, fully_qualified_parameters: impl IntoIterator<Item = String> + Clone, ) -> ProbeConfig151 fn setup_probe_config(
152 class_name: &str,
153 method_name: &str,
154 fully_qualified_parameters: impl IntoIterator<Item = String> + Clone,
155 ) -> ProbeConfig {
156 ProbeConfig {
157 fully_qualified_class_name: Some(class_name.to_string()),
158 method_name: Some(method_name.to_string()),
159 fully_qualified_parameters: fully_qualified_parameters.clone().into_iter().collect(),
160 method_signature: Some(format!(
161 "void {}.{}({})",
162 class_name,
163 method_name,
164 fully_qualified_parameters.into_iter().collect::<Vec<String>>().join(", ")
165 )),
166 ..ProbeConfig::default()
167 }
168 }
169 }
170