xref: /cloud-hypervisor/vmm/src/landlock.rs (revision 80b2c98a68d4c68f372f849e8d26f7cae5867000)
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: {0}")]
21     ManageRuleset(#[source] RulesetError),
22 
23     /// Error opening path
24     #[error("Error opening path: {0}")]
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