• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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