1use crate::error::ProgramError;
6use ::routing::policy::ScopedPolicyChecker;
7use fidl_fuchsia_data as fdata;
8use runner::StartInfoProgramError;
9
10const CREATE_RAW_PROCESSES_KEY: &str = "job_policy_create_raw_processes";
11const BAD_HANDLE_POLICY_KEY: &str = "job_policy_bad_handles";
12const BAD_HANDLE_POLICY_VARIANTS: [&'static str; 2] = ["deny_exception", "allow_exception"];
13const SHARED_PROCESS_KEY: &str = "is_shared_process";
14const CRITICAL_KEY: &str = "main_process_critical";
15const FORWARD_STDOUT_KEY: &str = "forward_stdout_to";
16const FORWARD_STDERR_KEY: &str = "forward_stderr_to";
17const VMEX_KEY: &str = "job_policy_ambient_mark_vmo_exec";
18const STOP_EVENT_KEY: &str = "lifecycle.stop_event";
19const STOP_EVENT_VARIANTS: [&'static str; 2] = ["notify", "ignore"];
20const USE_NEXT_VDSO_KEY: &str = "use_next_vdso";
21const JOB_WITH_AVAILABLE_EXCEPTION_CHANNEL_KEY: &str = "job_with_available_exception_channel";
22const MEMORY_ATTRIBUTION: &str = "memory_attribution";
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum StreamSink {
27 Log,
29 None,
31}
32
33impl Default for StreamSink {
34 fn default() -> Self {
35 StreamSink::Log
36 }
37}
38
39#[derive(Debug, Default, Eq, PartialEq, Clone)]
40pub enum ElfProgramBadHandlesPolicy {
41 #[default]
42 DenyException,
43 AllowException,
44}
45
46#[derive(Debug, Default, Eq, PartialEq, Clone)]
48pub struct ElfProgramConfig {
49 pub binary: String,
50 pub args: Vec<String>,
51 pub notify_lifecycle_stop: bool,
52 pub ambient_mark_vmo_exec: bool,
53 pub main_process_critical: bool,
54 pub create_raw_processes: bool,
55 pub job_policy_bad_handles: Option<ElfProgramBadHandlesPolicy>,
56 pub is_shared_process: bool,
57 pub use_next_vdso: bool,
58 pub job_with_available_exception_channel: bool,
59 pub memory_attribution: bool,
60 pub stdout_sink: StreamSink,
61 pub stderr_sink: StreamSink,
62 pub environ: Option<Vec<String>>,
63}
64
65impl ElfProgramConfig {
66 pub fn parse_and_check(
72 program: &fdata::Dictionary,
73 checker: Option<&ScopedPolicyChecker>,
74 ) -> Result<Self, ProgramError> {
75 let config = Self::parse(program).map_err(ProgramError::Parse)?;
76
77 if config.ambient_mark_vmo_exec {
78 checker
79 .ok_or(ProgramError::DenyPolicy)?
80 .ambient_mark_vmo_exec_allowed()
81 .map_err(ProgramError::Policy)?;
82 }
83
84 if config.main_process_critical {
85 checker
86 .ok_or(ProgramError::DenyPolicy)?
87 .main_process_critical_allowed()
88 .map_err(ProgramError::Policy)?;
89 }
90
91 if config.create_raw_processes {
92 checker
93 .ok_or(ProgramError::DenyPolicy)?
94 .create_raw_processes_allowed()
95 .map_err(ProgramError::Policy)?;
96 }
97
98 if config.is_shared_process && !config.create_raw_processes {
99 return Err(ProgramError::SharedProcessRequiresJobPolicy);
100 }
101
102 Ok(config)
103 }
104
105 fn parse(program: &fdata::Dictionary) -> Result<Self, StartInfoProgramError> {
107 let notify_lifecycle_stop =
108 match runner::get_enum(program, STOP_EVENT_KEY, &STOP_EVENT_VARIANTS)? {
109 Some("notify") => true,
110 _ => false,
111 };
112
113 let job_policy_bad_handles =
114 match runner::get_enum(program, BAD_HANDLE_POLICY_KEY, &BAD_HANDLE_POLICY_VARIANTS)? {
115 Some("deny_exception") => Some(ElfProgramBadHandlesPolicy::DenyException),
116 Some("allow_exception") => Some(ElfProgramBadHandlesPolicy::AllowException),
117 _ => None,
118 };
119
120 Ok(ElfProgramConfig {
121 binary: runner::get_program_binary_from_dict(&program)?,
122 args: runner::get_program_args_from_dict(&program)?,
123 notify_lifecycle_stop,
124 ambient_mark_vmo_exec: runner::get_bool(program, VMEX_KEY)?,
125 main_process_critical: runner::get_bool(program, CRITICAL_KEY)?,
126 create_raw_processes: runner::get_bool(program, CREATE_RAW_PROCESSES_KEY)?,
127 job_policy_bad_handles,
128 is_shared_process: runner::get_bool(program, SHARED_PROCESS_KEY)?,
129 use_next_vdso: runner::get_bool(program, USE_NEXT_VDSO_KEY)?,
130 job_with_available_exception_channel: runner::get_bool(
131 program,
132 JOB_WITH_AVAILABLE_EXCEPTION_CHANNEL_KEY,
133 )?,
134 memory_attribution: runner::get_bool(program, MEMORY_ATTRIBUTION)?,
135 stdout_sink: get_stream_sink(&program, FORWARD_STDOUT_KEY)?,
136 stderr_sink: get_stream_sink(&program, FORWARD_STDERR_KEY)?,
137 environ: runner::get_environ(&program)?,
138 })
139 }
140
141 pub fn process_options(&self) -> zx::ProcessOptions {
142 if self.is_shared_process {
143 zx::ProcessOptions::SHARED
144 } else {
145 zx::ProcessOptions::empty()
146 }
147 }
148}
149
150pub fn get_stream_sink(
151 dict: &fdata::Dictionary,
152 key: &str,
153) -> Result<StreamSink, StartInfoProgramError> {
154 match runner::get_enum(dict, key, &["log", "none"])? {
155 Some("log") => Ok(StreamSink::Log),
156 Some("none") => Ok(StreamSink::None),
157 Some(_) => unreachable!("get_enum returns only values in variants"),
158 None => Ok(StreamSink::default()),
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use ::routing::policy::PolicyError;
166 use assert_matches::assert_matches;
167 use cm_config::{
168 AllowlistEntryBuilder, ChildPolicyAllowlists, JobPolicyAllowlists, SecurityPolicy,
169 };
170 use fidl_fuchsia_data as fdata;
171 use moniker::Moniker;
172 use std::collections::HashMap;
173 use std::sync::{Arc, LazyLock};
174 use test_case::test_case;
175
176 const BINARY_KEY: &str = "binary";
177 const TEST_BINARY: &str = "test_binary";
178
179 static TEST_MONIKER: LazyLock<Moniker> = LazyLock::new(|| Moniker::root());
180 static PERMISSIVE_SECURITY_POLICY: LazyLock<Arc<SecurityPolicy>> = LazyLock::new(|| {
181 Arc::new(SecurityPolicy {
182 job_policy: JobPolicyAllowlists {
183 ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::build_exact_from_moniker(
184 &TEST_MONIKER,
185 )],
186 main_process_critical: vec![AllowlistEntryBuilder::build_exact_from_moniker(
187 &TEST_MONIKER,
188 )],
189 create_raw_processes: vec![AllowlistEntryBuilder::build_exact_from_moniker(
190 &TEST_MONIKER,
191 )],
192 },
193 capability_policy: HashMap::new(),
194 debug_capability_policy: HashMap::new(),
195 child_policy: ChildPolicyAllowlists { reboot_on_terminate: vec![] },
196 })
197 });
198 static RESTRICTIVE_SECURITY_POLICY: LazyLock<Arc<SecurityPolicy>> =
199 LazyLock::new(|| Arc::new(SecurityPolicy::default()));
200
201 #[test_case("forward_stdout_to", new_string("log"), ElfProgramConfig { stdout_sink: StreamSink::Log, ..default_valid_config()} ; "when_stdout_log")]
202 #[test_case("forward_stdout_to", new_string("none"), ElfProgramConfig { stdout_sink: StreamSink::None, ..default_valid_config()} ; "when_stdout_none")]
203 #[test_case("forward_stderr_to", new_string("log"), ElfProgramConfig { stderr_sink: StreamSink::Log, ..default_valid_config()} ; "when_stderr_log")]
204 #[test_case("forward_stderr_to", new_string("none"), ElfProgramConfig { stderr_sink: StreamSink::None, ..default_valid_config()} ; "when_stderr_none")]
205 #[test_case("environ", new_empty_vec(), ElfProgramConfig { environ: None, ..default_valid_config()} ; "when_environ_empty")]
206 #[test_case("environ", new_vec(vec!["FOO=BAR"]), ElfProgramConfig { environ: Some(vec!["FOO=BAR".into()]), ..default_valid_config()} ; "when_environ_has_values")]
207 #[test_case("lifecycle.stop_event", new_string("notify"), ElfProgramConfig { notify_lifecycle_stop: true, ..default_valid_config()} ; "when_stop_event_notify")]
208 #[test_case("lifecycle.stop_event", new_string("ignore"), ElfProgramConfig { notify_lifecycle_stop: false, ..default_valid_config()} ; "when_stop_event_ignore")]
209 #[test_case("main_process_critical", new_string("true"), ElfProgramConfig { main_process_critical: true, ..default_valid_config()} ; "when_main_process_critical_true")]
210 #[test_case("main_process_critical", new_string("false"), ElfProgramConfig { main_process_critical: false, ..default_valid_config()} ; "when_main_process_critical_false")]
211 #[test_case("job_policy_ambient_mark_vmo_exec", new_string("true"), ElfProgramConfig { ambient_mark_vmo_exec: true, ..default_valid_config()} ; "when_ambient_mark_vmo_exec_true")]
212 #[test_case("job_policy_ambient_mark_vmo_exec", new_string("false"), ElfProgramConfig { ambient_mark_vmo_exec: false, ..default_valid_config()} ; "when_ambient_mark_vmo_exec_false")]
213 #[test_case("job_policy_create_raw_processes", new_string("true"), ElfProgramConfig { create_raw_processes: true, ..default_valid_config()} ; "when_create_raw_processes_true")]
214 #[test_case("job_policy_create_raw_processes", new_string("false"), ElfProgramConfig { create_raw_processes: false, ..default_valid_config()} ; "when_create_raw_processes_false")]
215 #[test_case("use_next_vdso", new_string("true"), ElfProgramConfig { use_next_vdso: true, ..default_valid_config()} ; "use_next_vdso_true")]
216 #[test_case("use_next_vdso", new_string("false"), ElfProgramConfig { use_next_vdso: false, ..default_valid_config()} ; "use_next_vdso_false")]
217 fn test_parse_and_check_with_permissive_policy(
218 key: &str,
219 value: fdata::DictionaryValue,
220 expected: ElfProgramConfig,
221 ) {
222 let checker =
223 ScopedPolicyChecker::new(PERMISSIVE_SECURITY_POLICY.clone(), TEST_MONIKER.clone());
224 let program = new_program_stanza(key, value);
225
226 let actual = ElfProgramConfig::parse_and_check(&program, Some(&checker)).unwrap();
227
228 assert_eq!(actual, expected);
229 }
230
231 #[test_case("job_policy_ambient_mark_vmo_exec", new_string("true") , "ambient_mark_vmo_exec" ; "when_ambient_mark_vmo_exec_true")]
232 #[test_case("main_process_critical", new_string("true"), "main_process_critical" ; "when_main_process_critical_true")]
233 #[test_case("job_policy_create_raw_processes", new_string("true"), "create_raw_processes" ; "when_create_raw_processes_true")]
234 fn test_parse_and_check_with_restrictive_policy(
235 key: &str,
236 value: fdata::DictionaryValue,
237 policy: &str,
238 ) {
239 let checker =
240 ScopedPolicyChecker::new(RESTRICTIVE_SECURITY_POLICY.clone(), TEST_MONIKER.clone());
241 let program = new_program_stanza(key, value);
242
243 let actual = ElfProgramConfig::parse_and_check(&program, Some(&checker));
244
245 assert_matches!(
246 actual,
247 Err(ProgramError::Policy(PolicyError::JobPolicyDisallowed {
248 policy: p,
249 ..
250 }))
251 if p == policy
252 );
253 }
254
255 #[test_case("lifecycle.stop_event", new_string("invalid") ; "for_stop_event")]
256 #[test_case("environ", new_empty_string() ; "for_environ")]
257 fn test_parse_and_check_with_invalid_value(key: &str, value: fdata::DictionaryValue) {
258 let checker =
261 ScopedPolicyChecker::new(PERMISSIVE_SECURITY_POLICY.clone(), TEST_MONIKER.clone());
262 let program = new_program_stanza(key, value);
263
264 let actual = ElfProgramConfig::parse_and_check(&program, Some(&checker));
265
266 assert_matches!(
267 actual,
268 Err(ProgramError::Parse(StartInfoProgramError::InvalidValue(k, _, _)))
269 if k == key
270 );
271 }
272
273 #[test_case("lifecycle.stop_event", new_empty_vec() ; "for_stop_event")]
274 #[test_case("job_policy_ambient_mark_vmo_exec", new_empty_vec() ; "for_ambient_mark_vmo_exec")]
275 #[test_case("main_process_critical", new_empty_vec() ; "for_main_process_critical")]
276 #[test_case("job_policy_create_raw_processes", new_empty_vec() ; "for_create_raw_processes")]
277 #[test_case("forward_stdout_to", new_empty_vec() ; "for_stdout")]
278 #[test_case("forward_stderr_to", new_empty_vec() ; "for_stderr")]
279 #[test_case("use_next_vdso", new_empty_vec() ; "for_use_next_vdso")]
280 fn test_parse_and_check_with_invalid_type(key: &str, value: fdata::DictionaryValue) {
281 let checker =
284 ScopedPolicyChecker::new(PERMISSIVE_SECURITY_POLICY.clone(), TEST_MONIKER.clone());
285 let program = new_program_stanza(key, value);
286
287 let actual = ElfProgramConfig::parse_and_check(&program, Some(&checker));
288
289 assert_matches!(
290 actual,
291 Err(ProgramError::Parse(StartInfoProgramError::InvalidType(k)))
292 if k == key
293 );
294 }
295
296 fn default_valid_config() -> ElfProgramConfig {
297 ElfProgramConfig { binary: TEST_BINARY.to_string(), args: Vec::new(), ..Default::default() }
298 }
299
300 fn new_program_stanza(key: &str, value: fdata::DictionaryValue) -> fdata::Dictionary {
301 fdata::Dictionary {
302 entries: Some(vec![
303 fdata::DictionaryEntry {
304 key: BINARY_KEY.to_owned(),
305 value: Some(Box::new(new_string(TEST_BINARY))),
306 },
307 fdata::DictionaryEntry { key: key.to_owned(), value: Some(Box::new(value)) },
308 ]),
309 ..Default::default()
310 }
311 }
312
313 fn new_string(value: &str) -> fdata::DictionaryValue {
314 fdata::DictionaryValue::Str(value.to_owned())
315 }
316
317 fn new_vec(values: Vec<&str>) -> fdata::DictionaryValue {
318 fdata::DictionaryValue::StrVec(values.into_iter().map(str::to_owned).collect())
319 }
320
321 fn new_empty_string() -> fdata::DictionaryValue {
322 fdata::DictionaryValue::Str("".to_owned())
323 }
324
325 fn new_empty_vec() -> fdata::DictionaryValue {
326 fdata::DictionaryValue::StrVec(vec![])
327 }
328}