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