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