lunatic_runtime/
config.rs1use std::{
2 fmt::Debug,
3 path::{Component, Path, PathBuf},
4};
5
6use lunatic_process::config::ProcessConfig;
7use lunatic_process_api::ProcessConfigCtx;
8use lunatic_wasi_api::LunaticWasiConfigCtx;
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Serialize, Deserialize)]
12pub struct DefaultProcessConfig {
13 max_memory: usize,
15 max_fuel: Option<u64>,
17 can_compile_modules: bool,
19 can_create_configs: bool,
21 can_spawn_processes: bool,
23 preopened_dirs: Vec<String>,
25 command_line_arguments: Vec<String>,
26 environment_variables: Vec<(String, String)>,
27}
28
29impl Debug for DefaultProcessConfig {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
31 f.debug_struct("EnvConfig")
32 .field("max_memory", &self.max_memory)
33 .field("max_fuel", &self.max_fuel)
34 .field("preopened_dirs", &self.preopened_dirs)
35 .field("args", &self.command_line_arguments)
36 .field("envs", &self.environment_variables)
37 .finish()
38 }
39}
40
41impl ProcessConfig for DefaultProcessConfig {
42 fn set_max_fuel(&mut self, max_fuel: Option<u64>) {
43 self.max_fuel = max_fuel;
44 }
45
46 fn get_max_fuel(&self) -> Option<u64> {
47 self.max_fuel
48 }
49
50 fn set_max_memory(&mut self, max_memory: usize) {
51 self.max_memory = max_memory
52 }
53
54 fn get_max_memory(&self) -> usize {
55 self.max_memory
56 }
57}
58
59impl LunaticWasiConfigCtx for DefaultProcessConfig {
60 fn add_environment_variable(&mut self, key: String, value: String) {
61 self.environment_variables.push((key, value));
62 }
63
64 fn add_command_line_argument(&mut self, argument: String) {
65 self.command_line_arguments.push(argument);
66 }
67
68 fn preopen_dir(&mut self, dir: String) {
69 self.preopened_dirs.push(dir);
70 }
71}
72
73impl DefaultProcessConfig {
74 pub fn preopened_dirs(&self) -> &[String] {
75 &self.preopened_dirs
76 }
77
78 pub fn preopen_dir<S: Into<String>>(&mut self, dir: S) {
80 self.preopened_dirs.push(dir.into())
81 }
82
83 pub fn set_command_line_arguments(&mut self, args: Vec<String>) {
84 self.command_line_arguments = args;
85 }
86
87 pub fn command_line_arguments(&self) -> &Vec<String> {
88 &self.command_line_arguments
89 }
90
91 pub fn set_environment_variables(&mut self, envs: Vec<(String, String)>) {
92 self.environment_variables = envs;
93 }
94
95 pub fn environment_variables(&self) -> &Vec<(String, String)> {
96 &self.environment_variables
97 }
98}
99
100impl ProcessConfigCtx for DefaultProcessConfig {
101 fn can_compile_modules(&self) -> bool {
102 self.can_compile_modules
103 }
104
105 fn set_can_compile_modules(&mut self, can: bool) {
106 self.can_compile_modules = can
107 }
108
109 fn can_create_configs(&self) -> bool {
110 self.can_create_configs
111 }
112
113 fn set_can_create_configs(&mut self, can: bool) {
114 self.can_create_configs = can
115 }
116
117 fn can_spawn_processes(&self) -> bool {
118 self.can_spawn_processes
119 }
120
121 fn set_can_spawn_processes(&mut self, can: bool) {
122 self.can_spawn_processes = can
123 }
124
125 fn can_access_fs_location(&self, path: &std::path::Path) -> Result<(), String> {
126 let (file_path, parent_dir) = match strip_file(path) {
127 Ok(p) => p,
128 Err(e) => {
129 return Err(e.to_string());
130 }
131 };
132 let has_access = self
133 .preopened_dirs()
134 .iter()
135 .filter_map(|dir| match get_absolute_path(Path::new(dir)) {
136 Ok(d) => Some(d),
137 _ => None,
138 })
139 .any(|dir| dir.exists() && path_is_ancestor(&dir, &parent_dir));
140
141 match has_access {
142 true => Ok(()),
143 false => Err(format!("Permission to '{file_path:?}' denied")),
144 }
145 }
146}
147
148fn path_is_ancestor(ancestor: &Path, descendant: &Path) -> bool {
149 let ancestor_path = Path::new(ancestor);
150 let descendant_path = Path::new(descendant);
151
152 if !ancestor_path.is_dir() {
153 return false;
154 }
155
156 if ancestor_path.as_os_str() == Path::new("/").as_os_str() {
158 return true;
159 }
160
161 let descendant_components = descendant_path.ancestors();
162
163 for component in descendant_components {
165 if component.as_os_str() == ancestor_path.as_os_str() {
166 return true;
167 }
168 }
169
170 false
171}
172
173fn strip_file(path: &Path) -> std::io::Result<(PathBuf, PathBuf)> {
177 let absolute_path = get_absolute_path(path)?;
178 if absolute_path.is_file() {
179 return Ok((absolute_path.clone(), absolute_path.join("..")));
180 }
181 Ok((absolute_path.clone(), absolute_path))
182}
183
184fn get_absolute_path(path: &std::path::Path) -> std::io::Result<PathBuf> {
185 let path = if path.is_relative() {
186 Path::join(std::env::current_dir().unwrap().as_path(), path)
187 } else {
188 path.to_path_buf()
189 };
190 Ok(normalize_path(&path))
191}
192
193fn normalize_path(path: &Path) -> PathBuf {
194 let mut components = path.components().peekable();
195 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
196 components.next();
197 PathBuf::from(c.as_os_str())
198 } else {
199 PathBuf::new()
200 };
201
202 for component in components {
203 match component {
204 Component::Prefix(..) => unreachable!(),
205 Component::RootDir => {
206 ret.push(component.as_os_str());
207 }
208 Component::CurDir => {}
209 Component::ParentDir => {
210 ret.pop();
211 }
212 Component::Normal(c) => {
213 ret.push(c);
214 }
215 }
216 }
217 ret
218}
219
220#[cfg(test)]
221mod tests {
222 use std::path::Path;
223
224 use crate::config::{get_absolute_path, path_is_ancestor};
225
226 use super::normalize_path;
227
228 #[test]
229 fn test_accessible_paths() {
230 let crates = get_absolute_path(Path::new("crates")).unwrap();
231 let sqlite = get_absolute_path(Path::new("crates/lunatic-sqlite-api")).unwrap();
232 let src = get_absolute_path(Path::new("crates/lunatic-sqlite-api/src")).unwrap();
233 let guest_api =
234 get_absolute_path(Path::new("crates/lunatic-sqlite-api/src/guest_api")).unwrap();
235 assert!(path_is_ancestor(&crates, &guest_api));
237 assert!(path_is_ancestor(&sqlite, &guest_api));
238 assert!(path_is_ancestor(&src, &guest_api));
239 assert!(path_is_ancestor(&guest_api, &guest_api));
240 }
241
242 #[test]
243 fn test_forbidden_paths() {
244 let crates = get_absolute_path(Path::new("crates")).unwrap();
245 let sqlite = get_absolute_path(Path::new("crates/lunatic-sqlite-api")).unwrap();
246 let src = get_absolute_path(Path::new("crates/lunatic-sqlite-api/src")).unwrap();
247 let guest_api =
248 get_absolute_path(Path::new("crates/lunatic-sqlite-api/src/guest_api")).unwrap();
249 assert!(!path_is_ancestor(&guest_api, &crates));
251 assert!(!path_is_ancestor(&guest_api, &sqlite));
252 assert!(!path_is_ancestor(&guest_api, &src));
253 }
254
255 #[test]
256 fn test_forbidden_absolute_paths() {
257 let src = get_absolute_path(Path::new("crates/lunatic-sqlite-api/src")).unwrap();
258 assert!(!path_is_ancestor(&src, Path::new("/")));
260 assert!(!path_is_ancestor(&src, Path::new("/etc/passwd")));
261 }
262
263 #[test]
264 fn normalized_paths() {
265 let crates = get_absolute_path(Path::new("crates")).unwrap();
266 let src = get_absolute_path(Path::new("crates/lunatic-sqlite-api/src")).unwrap();
267 let sneaky_src =
268 get_absolute_path(Path::new("crates/lunatic-sqlite-api/src/../src/.")).unwrap();
269 let sneaky_path =
270 get_absolute_path(Path::new("crates/lunatic-sqlite-api/src/../src/../../")).unwrap();
271 assert_eq!(src, normalize_path(&sneaky_src));
272 assert_eq!(crates, normalize_path(&sneaky_path));
273 }
274}
275
276impl Default for DefaultProcessConfig {
277 fn default() -> Self {
278 Self {
279 max_memory: u32::MAX as usize, max_fuel: None,
281 can_compile_modules: false,
282 can_create_configs: false,
283 can_spawn_processes: false,
284 preopened_dirs: vec![],
285 command_line_arguments: vec![],
286 environment_variables: vec![],
287 }
288 }
289}