1use crate::capability_source::{
6 AnonymizedAggregateSource, BuiltinSource, CapabilitySource, CapabilityToCapabilitySource,
7 ComponentSource, EnvironmentSource, FilteredAggregateProviderSource, FilteredProviderSource,
8 FrameworkSource, NamespaceSource, StorageBackingDirectorySource, 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 | CapabilitySource::StorageBackingDirectory(StorageBackingDirectorySource {
110 capability,
111 moniker,
112 ..
113 }) => CapabilityAllowlistKey {
114 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
115 source_name: capability
116 .source_name()
117 .ok_or(PolicyError::InvalidCapabilitySource {
118 moniker: capability_source.source_moniker(),
119 })?
120 .clone(),
121 source: CapabilityAllowlistSource::Self_,
122 capability: capability.type_name(),
123 },
124 CapabilitySource::Builtin(BuiltinSource { capability, .. }) => CapabilityAllowlistKey {
125 source_moniker: ExtendedMoniker::ComponentManager,
126 source_name: capability.source_name().clone(),
127 source: CapabilityAllowlistSource::Self_,
128 capability: capability.type_name(),
129 },
130 CapabilitySource::Framework(FrameworkSource { capability, moniker }) => {
131 CapabilityAllowlistKey {
132 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
133 source_name: capability.source_name().clone(),
134 source: CapabilityAllowlistSource::Framework,
135 capability: capability.type_name(),
136 }
137 }
138 CapabilitySource::Void(VoidSource { capability, moniker }) => CapabilityAllowlistKey {
139 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
140 source_name: capability.source_name().clone(),
141 source: CapabilityAllowlistSource::Void,
142 capability: capability.type_name(),
143 },
144 CapabilitySource::Capability(CapabilityToCapabilitySource {
145 source_capability,
146 moniker,
147 }) => CapabilityAllowlistKey {
148 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
149 source_name: source_capability
150 .source_name()
151 .ok_or(PolicyError::InvalidCapabilitySource {
152 moniker: capability_source.source_moniker(),
153 })?
154 .clone(),
155 source: CapabilityAllowlistSource::Capability,
156 capability: source_capability.type_name(),
157 },
158 CapabilitySource::AnonymizedAggregate(AnonymizedAggregateSource {
159 capability,
160 moniker,
161 ..
162 })
163 | CapabilitySource::FilteredProvider(FilteredProviderSource {
164 capability,
165 moniker,
166 ..
167 })
168 | CapabilitySource::FilteredAggregateProvider(FilteredAggregateProviderSource {
169 capability,
170 moniker,
171 ..
172 }) => CapabilityAllowlistKey {
173 source_moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
174 source_name: capability.source_name().clone(),
175 source: CapabilityAllowlistSource::Self_,
176 capability: capability.type_name(),
177 },
178 CapabilitySource::Environment(EnvironmentSource { capability, .. }) => {
179 CapabilityAllowlistKey {
180 source_moniker: ExtendedMoniker::ComponentManager,
181 source_name: capability
182 .source_name()
183 .ok_or(PolicyError::InvalidCapabilitySource {
184 moniker: capability_source.source_moniker(),
185 })?
186 .clone(),
187 source: CapabilityAllowlistSource::Environment,
188 capability: capability.type_name(),
189 }
190 }
191 CapabilitySource::RemotedAt(moniker) => {
192 return Err(PolicyError::InvalidCapabilitySource {
193 moniker: ExtendedMoniker::ComponentInstance(moniker.clone()),
194 });
195 }
196 })
197 }
198
199 pub fn can_route_capability<'a>(
202 &self,
203 capability_source: &'a CapabilitySource,
204 target_moniker: &'a Moniker,
205 ) -> Result<(), PolicyError> {
206 let policy_key = Self::get_policy_key(capability_source).map_err(|e| {
207 error!("Security policy could not generate a policy key for `{}`", capability_source);
208 e
209 })?;
210
211 match self.policy.capability_policy.get(&policy_key) {
212 Some(entries) => {
213 let parts = target_moniker
214 .path()
215 .iter()
216 .map(|c| AllowlistMatcher::Exact((*c).into()))
217 .collect();
218 let entry = AllowlistEntry { matchers: parts };
219
220 if entries.contains(&entry) {
222 return Ok(());
223 }
224
225 if entries.iter().any(|entry| entry.matches(&target_moniker)) {
227 Ok(())
228 } else {
229 warn!(
230 "Security policy prevented `{}` from `{}` being routed to `{}`.",
231 policy_key.source_name, policy_key.source_moniker, target_moniker
232 );
233 Err(PolicyError::CapabilityUseDisallowed {
234 cap: policy_key.source_name.to_string(),
235 source_moniker: policy_key.source_moniker.to_owned(),
236 target_moniker: target_moniker.to_owned(),
237 })
238 }
239 }
240 None => Ok(()),
241 }
242 }
243
244 pub fn can_register_debug_capability<'a>(
247 &self,
248 capability_type: CapabilityTypeName,
249 name: &'a cm_types::Name,
250 env_moniker: &'a Moniker,
251 env_name: &'a cm_types::Name,
252 ) -> Result<(), PolicyError> {
253 let debug_key = DebugCapabilityKey {
254 name: name.clone(),
255 source: CapabilityAllowlistSource::Self_,
256 capability: capability_type,
257 env_name: env_name.clone(),
258 };
259 let route_allowed = match self.policy.debug_capability_policy.get(&debug_key) {
260 None => false,
261 Some(allowlist_set) => allowlist_set.iter().any(|entry| entry.matches(env_moniker)),
262 };
263 if route_allowed {
264 return Ok(());
265 }
266
267 warn!(
268 "Debug security policy prevented `{}` from being registered to environment `{}` in `{}`.",
269 debug_key.name, env_name, env_moniker,
270 );
271 Err(PolicyError::DebugCapabilityUseDisallowed {
272 cap: debug_key.name.to_string(),
273 env_moniker: env_moniker.to_owned(),
274 env_name: env_name.to_string(),
275 })
276 }
277
278 pub fn reboot_on_terminate_allowed(&self, target_moniker: &Moniker) -> Result<(), PolicyError> {
280 self.policy
281 .child_policy
282 .reboot_on_terminate
283 .iter()
284 .any(|entry| entry.matches(&target_moniker))
285 .then_some(())
286 .ok_or_else(|| PolicyError::ChildPolicyDisallowed {
287 policy: "reboot_on_terminate".to_owned(),
288 moniker: target_moniker.to_owned(),
289 })
290 }
291}
292
293#[derive(Clone)]
296pub struct ScopedPolicyChecker {
297 policy: Arc<SecurityPolicy>,
299
300 pub scope: Moniker,
302}
303
304impl ScopedPolicyChecker {
305 pub fn new(policy: Arc<SecurityPolicy>, scope: Moniker) -> Self {
306 ScopedPolicyChecker { policy, scope }
307 }
308
309 pub fn ambient_mark_vmo_exec_allowed(&self) -> Result<(), PolicyError> {
313 self.policy
314 .job_policy
315 .ambient_mark_vmo_exec
316 .iter()
317 .any(|entry| entry.matches(&self.scope))
318 .then_some(())
319 .ok_or_else(|| PolicyError::JobPolicyDisallowed {
320 policy: "ambient_mark_vmo_exec".to_owned(),
321 moniker: self.scope.to_owned(),
322 })
323 }
324
325 pub fn main_process_critical_allowed(&self) -> Result<(), PolicyError> {
326 self.policy
327 .job_policy
328 .main_process_critical
329 .iter()
330 .any(|entry| entry.matches(&self.scope))
331 .then_some(())
332 .ok_or_else(|| PolicyError::JobPolicyDisallowed {
333 policy: "main_process_critical".to_owned(),
334 moniker: self.scope.to_owned(),
335 })
336 }
337
338 pub fn create_raw_processes_allowed(&self) -> Result<(), PolicyError> {
339 self.policy
340 .job_policy
341 .create_raw_processes
342 .iter()
343 .any(|entry| entry.matches(&self.scope))
344 .then_some(())
345 .ok_or_else(|| PolicyError::JobPolicyDisallowed {
346 policy: "create_raw_processes".to_owned(),
347 moniker: self.scope.to_owned(),
348 })
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use assert_matches::assert_matches;
356 use cm_config::{AllowlistEntryBuilder, ChildPolicyAllowlists, JobPolicyAllowlists};
357 use moniker::ChildName;
358 use std::collections::HashMap;
359
360 #[test]
361 fn scoped_policy_checker_vmex() {
362 macro_rules! assert_vmex_allowed_matches {
363 ($policy:expr, $moniker:expr, $expected:pat) => {
364 let result = ScopedPolicyChecker::new($policy.clone(), $moniker.clone())
365 .ambient_mark_vmo_exec_allowed();
366 assert_matches!(result, $expected);
367 };
368 }
369 macro_rules! assert_vmex_disallowed {
370 ($policy:expr, $moniker:expr) => {
371 assert_vmex_allowed_matches!(
372 $policy,
373 $moniker,
374 Err(PolicyError::JobPolicyDisallowed { .. })
375 );
376 };
377 }
378 let policy = Arc::new(SecurityPolicy::default());
379 assert_vmex_disallowed!(policy, Moniker::root());
380 assert_vmex_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
381
382 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
383 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
384 let policy = Arc::new(SecurityPolicy {
385 job_policy: JobPolicyAllowlists {
386 ambient_mark_vmo_exec: vec![
387 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
388 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
389 ],
390 main_process_critical: vec![
391 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
392 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
393 ],
394 create_raw_processes: vec![
395 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
396 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
397 ],
398 },
399 capability_policy: HashMap::new(),
400 debug_capability_policy: HashMap::new(),
401 child_policy: ChildPolicyAllowlists {
402 reboot_on_terminate: vec![
403 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
404 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
405 ],
406 },
407 });
408 assert_vmex_allowed_matches!(policy, allowed1, Ok(()));
409 assert_vmex_allowed_matches!(policy, allowed2, Ok(()));
410 assert_vmex_disallowed!(policy, Moniker::root());
411 assert_vmex_disallowed!(policy, allowed1.parent().unwrap());
412 assert_vmex_disallowed!(policy, allowed1.child(ChildName::try_from("baz").unwrap()));
413 }
414
415 #[test]
416 fn scoped_policy_checker_create_raw_processes() {
417 macro_rules! assert_create_raw_processes_allowed_matches {
418 ($policy:expr, $moniker:expr, $expected:pat) => {
419 let result = ScopedPolicyChecker::new($policy.clone(), $moniker.clone())
420 .create_raw_processes_allowed();
421 assert_matches!(result, $expected);
422 };
423 }
424 macro_rules! assert_create_raw_processes_disallowed {
425 ($policy:expr, $moniker:expr) => {
426 assert_create_raw_processes_allowed_matches!(
427 $policy,
428 $moniker,
429 Err(PolicyError::JobPolicyDisallowed { .. })
430 );
431 };
432 }
433 let policy = Arc::new(SecurityPolicy::default());
434 assert_create_raw_processes_disallowed!(policy, Moniker::root());
435 assert_create_raw_processes_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
436
437 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
438 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
439 let policy = Arc::new(SecurityPolicy {
440 job_policy: JobPolicyAllowlists {
441 ambient_mark_vmo_exec: vec![],
442 main_process_critical: vec![],
443 create_raw_processes: vec![
444 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
445 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
446 ],
447 },
448 capability_policy: HashMap::new(),
449 debug_capability_policy: HashMap::new(),
450 child_policy: ChildPolicyAllowlists { reboot_on_terminate: vec![] },
451 });
452 assert_create_raw_processes_allowed_matches!(policy, allowed1, Ok(()));
453 assert_create_raw_processes_allowed_matches!(policy, allowed2, Ok(()));
454 assert_create_raw_processes_disallowed!(policy, Moniker::root());
455 assert_create_raw_processes_disallowed!(policy, allowed1.parent().unwrap());
456 assert_create_raw_processes_disallowed!(
457 policy,
458 allowed1.child(ChildName::try_from("baz").unwrap())
459 );
460 }
461
462 #[test]
463 fn scoped_policy_checker_main_process_critical_allowed() {
464 macro_rules! assert_critical_allowed_matches {
465 ($policy:expr, $moniker:expr, $expected:pat) => {
466 let result = ScopedPolicyChecker::new($policy.clone(), $moniker.clone())
467 .main_process_critical_allowed();
468 assert_matches!(result, $expected);
469 };
470 }
471 macro_rules! assert_critical_disallowed {
472 ($policy:expr, $moniker:expr) => {
473 assert_critical_allowed_matches!(
474 $policy,
475 $moniker,
476 Err(PolicyError::JobPolicyDisallowed { .. })
477 );
478 };
479 }
480 let policy = Arc::new(SecurityPolicy::default());
481 assert_critical_disallowed!(policy, Moniker::root());
482 assert_critical_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
483
484 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
485 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
486 let policy = Arc::new(SecurityPolicy {
487 job_policy: JobPolicyAllowlists {
488 ambient_mark_vmo_exec: vec![
489 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
490 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
491 ],
492 main_process_critical: vec![
493 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
494 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
495 ],
496 create_raw_processes: vec![
497 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
498 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
499 ],
500 },
501 capability_policy: HashMap::new(),
502 debug_capability_policy: HashMap::new(),
503 child_policy: ChildPolicyAllowlists { reboot_on_terminate: vec![] },
504 });
505 assert_critical_allowed_matches!(policy, allowed1, Ok(()));
506 assert_critical_allowed_matches!(policy, allowed2, Ok(()));
507 assert_critical_disallowed!(policy, Moniker::root());
508 assert_critical_disallowed!(policy, allowed1.parent().unwrap());
509 assert_critical_disallowed!(policy, allowed1.child(ChildName::try_from("baz").unwrap()));
510 }
511
512 #[test]
513 fn scoped_policy_checker_reboot_policy_allowed() {
514 macro_rules! assert_reboot_allowed_matches {
515 ($policy:expr, $moniker:expr, $expected:pat) => {
516 let result = GlobalPolicyChecker::new($policy.clone())
517 .reboot_on_terminate_allowed(&$moniker);
518 assert_matches!(result, $expected);
519 };
520 }
521 macro_rules! assert_reboot_disallowed {
522 ($policy:expr, $moniker:expr) => {
523 assert_reboot_allowed_matches!(
524 $policy,
525 $moniker,
526 Err(PolicyError::ChildPolicyDisallowed { .. })
527 );
528 };
529 }
530
531 let policy = Arc::new(SecurityPolicy::default());
533 assert_reboot_disallowed!(policy, Moniker::root());
534 assert_reboot_disallowed!(policy, Moniker::try_from(["foo"]).unwrap());
535
536 let allowed1 = Moniker::try_from(["foo", "bar"]).unwrap();
538 let allowed2 = Moniker::try_from(["baz", "fiz"]).unwrap();
539 let policy = Arc::new(SecurityPolicy {
540 job_policy: JobPolicyAllowlists {
541 ambient_mark_vmo_exec: vec![],
542 main_process_critical: vec![],
543 create_raw_processes: vec![],
544 },
545 capability_policy: HashMap::new(),
546 debug_capability_policy: HashMap::new(),
547 child_policy: ChildPolicyAllowlists {
548 reboot_on_terminate: vec![
549 AllowlistEntryBuilder::build_exact_from_moniker(&allowed1),
550 AllowlistEntryBuilder::build_exact_from_moniker(&allowed2),
551 ],
552 },
553 });
554 assert_reboot_allowed_matches!(policy, allowed1, Ok(()));
555 assert_reboot_allowed_matches!(policy, allowed2, Ok(()));
556 assert_reboot_disallowed!(policy, Moniker::root());
557 assert_reboot_disallowed!(policy, allowed1.parent().unwrap());
558 assert_reboot_disallowed!(policy, allowed1.child(ChildName::try_from("baz").unwrap()));
559 }
560}