xref: /cloud-hypervisor/vmm/src/landlock.rs (revision bc6acb842f1ebb263245cd95fe5a92fe5f350bd3)
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