1 // Copyright © 2024 Microsoft Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 #[cfg(test)] 6 use landlock::make_bitflags; 7 use landlock::{ 8 path_beneath_rules, Access, AccessFs, BitFlags, Ruleset, RulesetAttr, RulesetCreated, 9 RulesetCreatedAttr, RulesetError, ABI, 10 }; 11 use std::convert::TryFrom; 12 use std::io::Error as IoError; 13 use std::path::PathBuf; 14 use thiserror::Error; 15 16 #[derive(Debug, Error)] 17 pub enum LandlockError { 18 /// All RulesetErrors from Landlock library are wrapped in this error 19 #[error("Error creating/adding/restricting ruleset: {0}")] 20 ManageRuleset(#[source] RulesetError), 21 22 /// Error opening path 23 #[error("Error opening path: {0}")] 24 OpenPath(#[source] IoError), 25 26 /// Invalid Landlock access 27 #[error("Invalid Landlock access: {0}")] 28 InvalidLandlockAccess(String), 29 30 /// Invalid Path 31 #[error("Invalid path")] 32 InvalidPath, 33 } 34 35 // https://docs.rs/landlock/latest/landlock/enum.ABI.html for more info on ABI 36 static ABI: ABI = ABI::V3; 37 38 pub(crate) struct LandlockAccess { 39 access: BitFlags<AccessFs>, 40 } 41 42 impl TryFrom<&str> for LandlockAccess { 43 type Error = LandlockError; 44 45 fn try_from(s: &str) -> Result<LandlockAccess, LandlockError> { 46 if s.is_empty() { 47 return Err(LandlockError::InvalidLandlockAccess( 48 "Access cannot be empty".to_string(), 49 )); 50 } 51 52 let mut access = BitFlags::<AccessFs>::empty(); 53 for c in s.chars() { 54 match c { 55 'r' => access |= AccessFs::from_read(ABI), 56 'w' => access |= AccessFs::from_write(ABI), 57 _ => { 58 return Err(LandlockError::InvalidLandlockAccess( 59 format!("Invalid access: {c}").to_string(), 60 )) 61 } 62 }; 63 } 64 Ok(LandlockAccess { access }) 65 } 66 } 67 pub struct Landlock { 68 ruleset: RulesetCreated, 69 } 70 71 impl Landlock { 72 pub fn new() -> Result<Landlock, LandlockError> { 73 let file_access = AccessFs::from_all(ABI); 74 75 let def_ruleset = Ruleset::default() 76 .handle_access(file_access) 77 .map_err(LandlockError::ManageRuleset)?; 78 79 // By default, rulesets are created in `BestEffort` mode. This lets Landlock 80 // to enable all the supported rules and silently ignore the unsupported ones. 81 let ruleset = def_ruleset.create().map_err(LandlockError::ManageRuleset)?; 82 83 Ok(Landlock { ruleset }) 84 } 85 86 pub(crate) fn add_rule( 87 &mut self, 88 path: PathBuf, 89 access: BitFlags<AccessFs>, 90 ) -> Result<(), LandlockError> { 91 // path_beneath_rules in landlock crate handles file and directory access rules. 92 // Incoming path/s are passed to path_beneath_rules, so that we don't 93 // have to worry about the type of the path. 94 let paths = vec![path.clone()]; 95 let path_beneath_rules = path_beneath_rules(paths, access); 96 self.ruleset 97 .as_mut() 98 .add_rules(path_beneath_rules) 99 .map_err(LandlockError::ManageRuleset)?; 100 Ok(()) 101 } 102 103 pub(crate) fn add_rule_with_access( 104 &mut self, 105 path: PathBuf, 106 access: &str, 107 ) -> Result<(), LandlockError> { 108 self.add_rule(path, LandlockAccess::try_from(access)?.access)?; 109 Ok(()) 110 } 111 112 pub fn restrict_self(self) -> Result<(), LandlockError> { 113 self.ruleset 114 .restrict_self() 115 .map_err(LandlockError::ManageRuleset)?; 116 Ok(()) 117 } 118 } 119 120 #[test] 121 fn test_try_from_access() { 122 // These access rights could change in future versions of Landlock. Listing 123 // them here explicitly to raise their visibility during code reviews. 124 let read_access = make_bitflags!(AccessFs::{ 125 Execute 126 | ReadFile 127 | ReadDir 128 }); 129 let write_access = make_bitflags!(AccessFs::{ 130 WriteFile 131 | RemoveDir 132 | RemoveFile 133 | MakeChar 134 | MakeDir 135 | MakeReg 136 | MakeSock 137 | MakeFifo 138 | MakeBlock 139 | MakeSym 140 | Refer 141 | Truncate 142 }); 143 let landlock_access = LandlockAccess::try_from("rw").unwrap(); 144 assert!(landlock_access.access == read_access | write_access); 145 146 let landlock_access = LandlockAccess::try_from("r").unwrap(); 147 assert!(landlock_access.access == read_access); 148 149 let landlock_access = LandlockAccess::try_from("w").unwrap(); 150 assert!(landlock_access.access == write_access); 151 152 assert!(LandlockAccess::try_from("").is_err()); 153 } 154