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
try_from(s: &str) -> Result<LandlockAccess, LandlockError>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 {
new() -> Result<Landlock, LandlockError>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
add_rule( &mut self, path: PathBuf, access: BitFlags<AccessFs>, ) -> Result<(), LandlockError>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
add_rule_with_access( &mut self, path: PathBuf, access: &str, ) -> Result<(), LandlockError>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
restrict_self(self) -> Result<(), LandlockError>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]
test_try_from_access()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