| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::config; |
| use crate::group::Group; |
| use cargo_metadata::{Node, Package, PackageId}; |
| use std::collections::HashMap; |
| |
| fn is_ancestor( |
| ancestor_id: &PackageId, |
| id: &PackageId, |
| nodes: &HashMap<&PackageId, &Node>, |
| ) -> bool { |
| if id == ancestor_id { |
| return true; |
| } |
| for dep in &nodes[ancestor_id].dependencies { |
| if dep == id || is_ancestor(dep, id, nodes) { |
| return true; |
| } |
| } |
| false |
| } |
| |
| fn get_group( |
| id: &PackageId, |
| packages: &HashMap<&PackageId, &Package>, |
| config: &config::BuildConfig, |
| ) -> Option<Group> { |
| config.per_crate_config.get(&packages[id].name)?.group |
| } |
| |
| pub fn find_inherited_privilege_group( |
| id: &PackageId, |
| root: &PackageId, |
| packages: &HashMap<&PackageId, &Package>, |
| nodes: &HashMap<&PackageId, &Node>, |
| config: &config::BuildConfig, |
| ) -> Group { |
| // A group is inherited from its ancestors and its dependencies, including |
| // from itself. |
| // - It inherits the highest privilege of any ancestor. If everything only uses |
| // it in the sandbox, then it only needs to be in the sandbox. Same for tests. |
| // - It inherits the lowest privilege of any dependency. If a dependency that is |
| // part of it needs a sandbox, then so does it. |
| // - If the group is specified on the crate itself, it replaces all ancestors. |
| let mut ancestor_groups = Vec::<Group>::new(); |
| let mut dependency_groups = Vec::<Group>::new(); |
| |
| for each_id in packages.keys() { |
| let found_group = get_group(each_id, packages, config).or_else(|| { |
| if nodes[root].deps.iter().any(|d| d.pkg == **each_id) { |
| // If the dependency is a top-level dep of Chromium, then it defaults to this |
| // privilege level. |
| // TODO: Default should be sandbox?? |
| Some(Group::Safe) |
| } else { |
| None |
| } |
| }); |
| |
| if let Some(group) = found_group { |
| if id == *each_id || is_ancestor(each_id, id, nodes) { |
| // `each_id` is an ancestor of `id`, or is the same crate. |
| log::debug!("{} ance {} ({:?})", packages[id].name, packages[each_id].name, group); |
| ancestor_groups.push(group); |
| } else if is_ancestor(id, each_id, nodes) { |
| // `each_id` is an descendent of `id`, or is the same crate. |
| log::debug!("{} depe {} ({:?})", packages[id].name, packages[each_id].name, group); |
| dependency_groups.push(group); |
| } |
| }; |
| } |
| |
| if let Some(self_group) = get_group(id, packages, config) { |
| ancestor_groups.clear(); |
| ancestor_groups.push(self_group); |
| } |
| |
| // Combine the privileges together. Ancestors work to increase privilege, |
| // and dependencies work to decrease it. |
| let ancestor_privilege = ancestor_groups.into_iter().fold(Group::Test, std::cmp::max); |
| let depedency_privilege = dependency_groups.into_iter().fold(Group::Safe, std::cmp::min); |
| let privilege = std::cmp::min(ancestor_privilege, depedency_privilege); |
| log::debug!("privilege = {:?}", privilege); |
| privilege |
| } |
| |
| /// Finds the value of a config flag for a crate that is inherited from |
| /// ancestors. The inherited value will be true if its true for the crate |
| /// itself or for any ancestor. |
| /// |
| /// The `get_flag` function retrieves the flag value (if any is set) for |
| /// each crate. |
| /// |
| /// If the crate (or an ancestor crate) is a top-level dependency and does not |
| /// have a value for its flag defined by `get_flag`, the |
| /// `get_flag_for_top_level` function defines its value based on its |
| /// [`Group`]. |
| fn find_inherited_bool_flag( |
| id: &PackageId, |
| root: &PackageId, |
| packages: &HashMap<&PackageId, &Package>, |
| nodes: &HashMap<&PackageId, &Node>, |
| config: &config::BuildConfig, |
| mut get_flag: impl FnMut(&PackageId) -> Option<bool>, |
| mut get_flag_for_top_level: impl FnMut(Option<Group>) -> Option<bool>, |
| ) -> Option<bool> { |
| let mut inherited_flag = None; |
| |
| for each_id in packages.keys() { |
| let group = get_group(each_id, packages, config); |
| |
| if let Some(flag) = get_flag(each_id).or_else(|| { |
| if nodes[root].deps.iter().any(|d| d.pkg == **each_id) { |
| get_flag_for_top_level(group) |
| } else { |
| None |
| } |
| }) { |
| if id == *each_id || is_ancestor(each_id, id, nodes) { |
| log::debug!("{} ance {} ({:?})", packages[id].name, packages[each_id].name, flag); |
| inherited_flag = Some(inherited_flag.unwrap_or_default() || flag); |
| } |
| }; |
| } |
| inherited_flag |
| } |
| |
| /// Finds the security_critical flag to be used for a package `id`. |
| /// |
| /// A package is considered security_critical if any ancestor is explicitly |
| /// marked security_critical. If the package and ancestors do not specify it, |
| /// then this function returns None. |
| pub fn find_inherited_security_critical_flag( |
| id: &PackageId, |
| root: &PackageId, |
| packages: &HashMap<&PackageId, &Package>, |
| nodes: &HashMap<&PackageId, &Node>, |
| config: &config::BuildConfig, |
| ) -> Option<bool> { |
| let get_security_critical = |id: &PackageId| { |
| config.per_crate_config.get(&packages[id].name).and_then(|config| config.security_critical) |
| }; |
| let get_top_level_security_critical = |group: Option<Group>| { |
| // If the dependency is a top-level dep of Chromium and is not put into the test |
| // group, then it defaults to security_critical. |
| match group { |
| Some(Group::Safe) | Some(Group::Sandbox) | None => Some(true), |
| Some(Group::Test) => None, |
| } |
| }; |
| |
| let inherited_flag = find_inherited_bool_flag( |
| id, |
| root, |
| packages, |
| nodes, |
| config, |
| get_security_critical, |
| get_top_level_security_critical, |
| ); |
| log::debug!("{} security_critical {:?}", packages[id].name, inherited_flag); |
| inherited_flag |
| } |
| |
| /// Finds the shipped flag to be used for a package `id`. |
| /// |
| /// A package is considered shipped if any ancestor is explicitly marked |
| /// shipped. If the package and ancestors do not specify it, then this |
| /// function returns None. |
| pub fn find_inherited_shipped_flag( |
| id: &PackageId, |
| root: &PackageId, |
| packages: &HashMap<&PackageId, &Package>, |
| nodes: &HashMap<&PackageId, &Node>, |
| config: &config::BuildConfig, |
| ) -> Option<bool> { |
| let get_shipped = |id: &PackageId| { |
| config.per_crate_config.get(&packages[id].name).and_then(|config| config.shipped) |
| }; |
| let get_top_level_shipped = |group: Option<Group>| { |
| // If the dependency is a top-level dep of Chromium and is not put into the test |
| // group, then it defaults to shipped. |
| match group { |
| Some(Group::Safe) | Some(Group::Sandbox) | None => Some(true), |
| Some(Group::Test) => None, |
| } |
| }; |
| |
| let inherited_flag = find_inherited_bool_flag( |
| id, |
| root, |
| packages, |
| nodes, |
| config, |
| get_shipped, |
| get_top_level_shipped, |
| ); |
| log::debug!("{} shipped {:?}", packages[id].name, inherited_flag); |
| inherited_flag |
| } |