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