1use crate::capability_source::{
6 AnonymizedAggregateSource, BuiltinSource, CapabilitySource, CapabilityToCapabilitySource,
7 ComponentSource, EnvironmentSource, FilteredAggregateProviderSource, FilteredProviderSource,
8 FrameworkSource, NamespaceSource, VoidSource,
9};
10use cm_config::{
11 AllowlistEntry, AllowlistMatcher, CapabilityAllowlistKey, CapabilityAllowlistSource,
12 DebugCapabilityKey, SecurityPolicy,
13};
14use log::{error, warn};
15use moniker::{ExtendedMoniker, Moniker};
16use std::sync::Arc;
17use thiserror::Error;
18use zx_status as zx;
19
20use cm_rust::CapabilityTypeName;
21#[cfg(feature = "serde")]
22use serde::{Deserialize, Serialize};
23
24#[cfg_attr(feature = "serde", derive(Deserialize, Serialize), serde(rename_all = "snake_case"))]
26#[derive(Debug, Clone, Error, PartialEq)]
27pub enum PolicyError {
28 #[error("security policy disallows \"{policy}\" job policy for \"{moniker}\"")]
29 JobPolicyDisallowed { policy: String, moniker: Moniker },
30
31 #[error("security policy disallows \"{policy}\" child policy for \"{moniker}\"")]
32 ChildPolicyDisallowed { policy: String, moniker: Moniker },
33
34 #[error(
35 "security policy was unable to extract the source from the routed capability at component \"{moniker}\""
36 )]
37 InvalidCapabilitySource { moniker: ExtendedMoniker },
38
39 #[error(
40 "security policy disallows \"{cap}\" from \"{source_moniker}\" being used at \"{target_moniker}\""
41 )]
42 CapabilityUseDisallowed {
43 cap: String,
44 source_moniker: ExtendedMoniker,
45 target_moniker: Moniker,
46 },
47
48 #[error(
49 "debug security policy disallows \"{cap}\" from being registered in \
50 environment \"{env_name}\" at \"{env_moniker}\""
51 )]
52 DebugCapabilityUseDisallowed { cap: String, env_moniker: Moniker, env_name: String },
53}
54
55impl PolicyError {
56 pub fn as_zx_status(&self) -> zx::Status {
58 zx::Status::ACCESS_DENIED
59 }
60}
61
62impl From<PolicyError> for ExtendedMoniker {
63 fn from(err: PolicyError) -> ExtendedMoniker {
64 match err {
65 PolicyError::ChildPolicyDisallowed { moniker, .. }
66 | PolicyError::DebugCapabilityUseDisallowed { env_moniker: moniker, .. }
67 | PolicyError::JobPolicyDisallowed { moniker, .. } => moniker.into(),
68
69 PolicyError::CapabilityUseDisallowed { source_moniker: moniker, .. }
70 | PolicyError::InvalidCapabilitySource { moniker } => moniker,
71 }
72 }
73}
74
75#[derive(Clone, Debug, Default)]
80pub struct GlobalPolicyChecker {
81 policy: Arc<SecurityPolicy>,
83}
84
85impl GlobalPolicyChecker {
86 pub fn new(policy: Arc<SecurityPolicy>) -> Self {
88 Self { policy }
89 }
90
91 fn get_policy_key(
92 capability_source: &CapabilitySource,
93 ) -> Result<CapabilityAllowlistKey, PolicyError> {
94 Ok(match &capability_source {
95 CapabilitySource::Namespace(NamespaceSource { capability, .. }) => {
96 CapabilityAllowlistKey {
97 source_moniker: ExtendedMoniker::ComponentManager,
98 source_name: capability
99 .source_name()
100 .ok_or(PolicyError::InvalidCapabilitySource {
101 moniker: capability_source.source_moniker(),
102 })?
103 .clone(),
104 source: CapabilityAllowlistSource::Self_,
105 capability: capability.type_name(),
106 }
107 }
108 CapabilitySource::Component(ComponentSource { capability, moniker }) => {
109 CapabilityAllowlistKey {
110 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
111 source_name: capability
112 .source_name()
113 .ok_or(PolicyError::InvalidCapabilitySource {
114 moniker: capability_source.source_moniker(),
115 })?
116 .clone(),
117 source: CapabilityAllowlistSource::Self_,
118 capability: capability.type_name(),
119 }
120 }
121 CapabilitySource::Builtin(BuiltinSource { capability, .. }) => CapabilityAllowlistKey {
122 source_moniker: ExtendedMoniker::ComponentManager,
123 source_name: capability.source_name().clone(),
124 source: CapabilityAllowlistSource::Self_,
125 capability: capability.type_name(),
126 },
127 CapabilitySource::Framework(FrameworkSource { capability, moniker }) => {
128 CapabilityAllowlistKey {
129 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
130 source_name: capability.source_name().clone(),
131 source: CapabilityAllowlistSource::Framework,
132 capability: capability.type_name(),
133 }
134 }
135 CapabilitySource::Void(VoidSource { capability, moniker }) => CapabilityAllowlistKey {
136 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
137 source_name: capability.source_name().clone(),
138 source: CapabilityAllowlistSource::Void,
139 capability: capability.type_name(),
140 },
141 CapabilitySource::Capability(CapabilityToCapabilitySource {
142 source_capability,
143 moniker,
144 }) => CapabilityAllowlistKey {
145 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
146 source_name: source_capability
147 .source_name()
148 .ok_or(PolicyError::InvalidCapabilitySource {
149 moniker: capability_source.source_moniker(),
150 })?
151 .clone(),
152 source: CapabilityAllowlistSource::Capability,
153 capability: source_capability.type_name(),
154 },
155 CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
156 capability,
157 moniker,
158 ..
159 })
160 | CapabilitySource::FilteredProvider(FilteredProviderSource {
161 capability,
162 moniker,
163 ..
164 })
165 | CapabilitySource::FilteredAggregateProvider(FilteredAggregateProviderSource {
166 capability,
167 moniker,
168 ..
169 }) => CapabilityAllowlistKey {
170 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
171 source_name: capability.source_name().clone(),
172 source: CapabilityAllowlistSource::Self_,
173 capability: capability.type_name(),
174 },
175 CapabilitySource::Environment(EnvironmentSource { capability, .. }) => {
176 CapabilityAllowlistKey {
177 source_moniker: ExtendedMoniker::ComponentManager,
178 source_name: capability
179 .source_name()
180 .ok_or(PolicyError::InvalidCapabilitySource {
181 moniker: capability_source.source_moniker(),
182 })?
183 .clone(),
184 source: CapabilityAllowlistSource::Environment,
185 capability: capability.type_name(),
186 }
187 }
188 CapabilitySource::RemotedAt(moniker) => {
189 return Err(PolicyError::InvalidCapabilitySource {
190 moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
191 });
192 }
193 })
194 }
195
196 pub fn can_route_capability<'a>(
199 &self,
200 capability_source: &'a CapabilitySource,
201 target_moniker: &'a Moniker,
202 ) -> Result<(), PolicyError> {
203 let policy_key = Self::get_policy_key(capability_source).map_err(|e| {
204 error!("Security policy could not generate a policy key for `{}`", capability_source);
205 e
206 })?;
207
208 match self.policy.capability_policy.get(&policy_key) {
209 Some(entries) => {
210 let parts = target_moniker
211 .path()
212 .iter()
213 .map(|c| AllowlistMatcher::Exact((*c).into()))
214 .collect();
215 let entry = AllowlistEntry { matchers: parts };
216
217 if entries.contains(&entry) {
219 return Ok(());
220 }
221
222 if entries.iter().any(|entry| entry.matches(&target_moniker)) {
224 Ok(())
225 } else {
226 warn!(
227 "Security policy prevented `{}` from `{}` being routed to `{}`.",
228 policy_key.source_name, policy_key.source_moniker, target_moniker
229 );
230 Err(PolicyError::CapabilityUseDisallowed {
231 cap: policy_key.source_name.to_string(),
232 source_moniker: policy_key.source_moniker.to_owned(),
233 target_moniker: target_moniker.to_owned(),
234 })
235 }
236 }
237 None => Ok(()),
238 }
239 }
240
241 pub fn can_register_debug_capability<'a>(
244 &self,
245 capability_type: CapabilityTypeName,
246 name: &'a cm_types::Name,
247 env_moniker: &'a Moniker,
248 env_name: &'a cm_types::Name,
249 ) -> Result<(), PolicyError> {
250 let debug_key = DebugCapabilityKey {
251 name: name.clone(),
252 source: CapabilityAllowlistSource::Self_,
253 capability: capability_type,
254 env_name: env_name.clone(),
255 };
256 let route_allowed = match self.policy.debug_capability_policy.get(&debug_key) {
257 None => false,
258 Some(allowlist_set) => allowlist_set.iter().any(|entry| entry.matches(env_moniker)),
259 };
260 if route_allowed {
261 return Ok(());
262 }
263
264 warn!(
265 "Debug security policy prevented `{}` from being registered to environment `{}` in `{}`.",
266 debug_key.name, env_name, env_moniker,
267 );
268 Err(PolicyError::DebugCapabilityUseDisallowed {
269 cap: debug_key.name.to_string(),
270 env_moniker: env_moniker.to_owned(),
271 env_name: env_name.to_string(),
272 })
273 }
274
275 pub fn reboot_on_terminate_allowed(&self, target_moniker: &Moniker) -> Result<(), PolicyError> {
277 self.policy
278 .child_policy
279 .reboot_on_terminate
280 .iter()
281 .any(|entry| entry.matches(&target_moniker))
282 .then_some(())
283 .ok_or_else(|| PolicyError::ChildPolicyDisallowed {
284 policy: "reboot_on_terminate".to_owned(),
285 moniker: target_moniker.to_owned(),
286 })
287 }
288}
289
290#[derive(Clone)]
293pub struct ScopedPolicyChecker {
294 policy: Arc<SecurityPolicy>,
296
297 pub scope: Moniker,
299}
300
301impl ScopedPolicyChecker {
302 pub fn new(policy: Arc<SecurityPolicy>, scope: Moniker) -> Self {
303 ScopedPolicyChecker { policy, scope }
304 }
305
306 pub fn ambient_mark_vmo_exec_allowed(&self) -> Result<(), PolicyError> {
310 self.policy
311 .job_policy
312 .ambient_mark_vmo_exec
313 .iter()
314 .any(|entry| entry.matches(&self.scope))
315 .then_some(())
316 .ok_or_else(|| PolicyError::JobPolicyDisallowed {
317 policy: "ambient_mark_vmo_exec".to_owned(),
318 moniker: self.scope.to_owned(),
319 })
320 }
321
322 pub fn main_process_critical_allowed(&self) -> Result<(), PolicyError> {
323 self.policy
324 .job_policy
325 .main_process_critical
326 .iter()
327 .any(|entry| entry.matches(&self.scope))
328 .then_some(())
329 .ok_or_else(|| PolicyError::JobPolicyDisallowed {
330 policy: "main_process_critical".to_owned(),
331 moniker: self.scope.to_owned(),
332 })
333 }
334
335 pub fn create_raw_processes_allowed(&self) -> Result<(), PolicyError> {
336 self.policy
337 .job_policy
338 .create_raw_processes
339 .iter()
340 .any(|entry| entry.matches(&self.scope))
341 .then_some(())
342 .ok_or_else(|| PolicyError::JobPolicyDisallowed {
343 policy: "create_raw_processes".to_owned(),
344 moniker: self.scope.to_owned(),
345 })
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use assert_matches::assert_matches;
353 use cm_config::{AllowlistEntryBuilder, ChildPolicyAllowlists, JobPolicyAllowlists};
354 use moniker::ChildName;
355 use std::collections::HashMap;
356
357 #[test]
358 fn scoped_policy_checker_vmex() {
359 macro_rules! assert_vmex_allowed_matches {
360 ($policy:expr, $moniker:expr, $expected:pat) => {
361 let result = ScopedPolicyChecker::new($policy.clone(), $moniker.clone())
362 .ambient_mark_vmo_exec_allowed();
363 assert_matches!(result, $expected);
364 };
365 }
366 macro_rules! assert_vmex_disallowed {
367 ($policy:expr, $moniker:expr) => {
368 assert_vmex_allowed_matches!(
369 $policy,
370 $moniker,
371 Err(PolicyError::JobPolicyDisallowed { .. })
372 );
373 };
374 }
375 let policy = Arc::new(SecurityPolicy::default());
376 assert_vmex_disallowed!(policy, Moniker::root());
377 assert_vmex_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
378
379 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
380 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
381 let policy = Arc::new(SecurityPolicy {
382 job_policy: JobPolicyAllowlists {
383 ambient_mark_vmo_exec: vec![
384 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
385 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
386 ],
387 main_process_critical: vec![
388 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
389 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
390 ],
391 create_raw_processes: vec![
392 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
393 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
394 ],
395 },
396 capability_policy: HashMap::new(),
397 debug_capability_policy: HashMap::new(),
398 child_policy: ChildPolicyAllowlists {
399 reboot_on_terminate: vec![
400 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
401 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
402 ],
403 },
404 });
405 assert_vmex_allowed_matches!(policy, allowed1, Ok(()));
406 assert_vmex_allowed_matches!(policy, allowed2, Ok(()));
407 assert_vmex_disallowed!(policy, Moniker::root());
408 assert_vmex_disallowed!(policy, allowed1.parent().unwrap());
409 assert_vmex_disallowed!(policy, allowed1.child(ChildName::try_from("baz").unwrap()));
410 }
411
412 #[test]
413 fn scoped_policy_checker_create_raw_processes() {
414 macro_rules! assert_create_raw_processes_allowed_matches {
415 ($policy:expr, $moniker:expr, $expected:pat) => {
416 let result = ScopedPolicyChecker::new($policy.clone(), $moniker.clone())
417 .create_raw_processes_allowed();
418 assert_matches!(result, $expected);
419 };
420 }
421 macro_rules! assert_create_raw_processes_disallowed {
422 ($policy:expr, $moniker:expr) => {
423 assert_create_raw_processes_allowed_matches!(
424 $policy,
425 $moniker,
426 Err(PolicyError::JobPolicyDisallowed { .. })
427 );
428 };
429 }
430 let policy = Arc::new(SecurityPolicy::default());
431 assert_create_raw_processes_disallowed!(policy, Moniker::root());
432 assert_create_raw_processes_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
433
434 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
435 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
436 let policy = Arc::new(SecurityPolicy {
437 job_policy: JobPolicyAllowlists {
438 ambient_mark_vmo_exec: vec![],
439 main_process_critical: vec![],
440 create_raw_processes: vec![
441 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
442 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
443 ],
444 },
445 capability_policy: HashMap::new(),
446 debug_capability_policy: HashMap::new(),
447 child_policy: ChildPolicyAllowlists { reboot_on_terminate: vec![] },
448 });
449 assert_create_raw_processes_allowed_matches!(policy, allowed1, Ok(()));
450 assert_create_raw_processes_allowed_matches!(policy, allowed2, Ok(()));
451 assert_create_raw_processes_disallowed!(policy, Moniker::root());
452 assert_create_raw_processes_disallowed!(policy, allowed1.parent().unwrap());
453 assert_create_raw_processes_disallowed!(
454 policy,
455 allowed1.child(ChildName::try_from("baz").unwrap())
456 );
457 }
458
459 #[test]
460 fn scoped_policy_checker_main_process_critical_allowed() {
461 macro_rules! assert_critical_allowed_matches {
462 ($policy:expr, $moniker:expr, $expected:pat) => {
463 let result = ScopedPolicyChecker::new($policy.clone(), $moniker.clone())
464 .main_process_critical_allowed();
465 assert_matches!(result, $expected);
466 };
467 }
468 macro_rules! assert_critical_disallowed {
469 ($policy:expr, $moniker:expr) => {
470 assert_critical_allowed_matches!(
471 $policy,
472 $moniker,
473 Err(PolicyError::JobPolicyDisallowed { .. })
474 );
475 };
476 }
477 let policy = Arc::new(SecurityPolicy::default());
478 assert_critical_disallowed!(policy, Moniker::root());
479 assert_critical_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
480
481 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
482 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
483 let policy = Arc::new(SecurityPolicy {
484 job_policy: JobPolicyAllowlists {
485 ambient_mark_vmo_exec: vec![
486 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
487 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
488 ],
489 main_process_critical: vec![
490 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
491 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
492 ],
493 create_raw_processes: vec![
494 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
495 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
496 ],
497 },
498 capability_policy: HashMap::new(),
499 debug_capability_policy: HashMap::new(),
500 child_policy: ChildPolicyAllowlists { reboot_on_terminate: vec![] },
501 });
502 assert_critical_allowed_matches!(policy, allowed1, Ok(()));
503 assert_critical_allowed_matches!(policy, allowed2, Ok(()));
504 assert_critical_disallowed!(policy, Moniker::root());
505 assert_critical_disallowed!(policy, allowed1.parent().unwrap());
506 assert_critical_disallowed!(policy, allowed1.child(ChildName::try_from("baz").unwrap()));
507 }
508
509 #[test]
510 fn scoped_policy_checker_reboot_policy_allowed() {
511 macro_rules! assert_reboot_allowed_matches {
512 ($policy:expr, $moniker:expr, $expected:pat) => {
513 let result = GlobalPolicyChecker::new($policy.clone())
514 .reboot_on_terminate_allowed(&$moniker);
515 assert_matches!(result, $expected);
516 };
517 }
518 macro_rules! assert_reboot_disallowed {
519 ($policy:expr, $moniker:expr) => {
520 assert_reboot_allowed_matches!(
521 $policy,
522 $moniker,
523 Err(PolicyError::ChildPolicyDisallowed { .. })
524 );
525 };
526 }
527
528 let policy = Arc::new(SecurityPolicy::default());
530 assert_reboot_disallowed!(policy, Moniker::root());
531 assert_reboot_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
532
533 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
535 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
536 let policy = Arc::new(SecurityPolicy {
537 job_policy: JobPolicyAllowlists {
538 ambient_mark_vmo_exec: vec![],
539 main_process_critical: vec![],
540 create_raw_processes: vec![],
541 },
542 capability_policy: HashMap::new(),
543 debug_capability_policy: HashMap::new(),
544 child_policy: ChildPolicyAllowlists {
545 reboot_on_terminate: vec![
546 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
547 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
548 ],
549 },
550 });
551 assert_reboot_allowed_matches!(policy, allowed1, Ok(()));
552 assert_reboot_allowed_matches!(policy, allowed2, Ok(()));
553 assert_reboot_disallowed!(policy, Moniker::root());
554 assert_reboot_disallowed!(policy, allowed1.parent().unwrap());
555 assert_reboot_disallowed!(policy, allowed1.child(ChildName::try_from("baz").unwrap()));
556 }
557}