devices/virtio/fs/config.rs
1// Copyright 2023 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#[cfg(feature = "fs_permission_translation")]
6use std::io;
7#[cfg(feature = "fs_permission_translation")]
8use std::str::FromStr;
9use std::time::Duration;
10
11#[cfg(feature = "fs_permission_translation")]
12use libc;
13#[allow(unused_imports)]
14use serde::de::Error;
15use serde::Deserialize;
16use serde::Deserializer;
17use serde::Serialize;
18use serde_keyvalue::FromKeyValues;
19
20/// The caching policy that the file system should report to the FUSE client. By default the FUSE
21/// protocol uses close-to-open consistency. This means that any cached contents of the file are
22/// invalidated the next time that file is opened.
23#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, FromKeyValues)]
24#[serde(rename_all = "kebab-case")]
25pub enum CachePolicy {
26 /// The client should never cache file data and all I/O should be directly forwarded to the
27 /// server. This policy must be selected when file contents may change without the knowledge of
28 /// the FUSE client (i.e., the file system does not have exclusive access to the directory).
29 Never,
30
31 /// The client is free to choose when and how to cache file data. This is the default policy
32 /// and uses close-to-open consistency as described in the enum documentation.
33 #[default]
34 Auto,
35
36 /// The client should always cache file data. This means that the FUSE client will not
37 /// invalidate any cached data that was returned by the file system the last time the file was
38 /// opened. This policy should only be selected when the file system has exclusive access to
39 /// the directory.
40 Always,
41}
42
43const fn config_default_timeout() -> Duration {
44 Duration::from_secs(5)
45}
46
47const fn config_default_negative_timeout() -> Duration {
48 Duration::ZERO
49}
50
51const fn config_default_posix_acl() -> bool {
52 true
53}
54
55const fn config_default_security_ctx() -> bool {
56 true
57}
58
59fn deserialize_timeout<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Duration, D::Error> {
60 let secs = u64::deserialize(deserializer)?;
61
62 Ok(Duration::from_secs(secs))
63}
64
65#[cfg(feature = "arc_quota")]
66fn deserialize_privileged_quota_uids<'de, D: Deserializer<'de>>(
67 deserializer: D,
68) -> Result<Vec<libc::uid_t>, D::Error> {
69 // space-separated list
70 let s: &str = serde::Deserialize::deserialize(deserializer)?;
71 s.split(' ')
72 .map(|s| {
73 s.parse::<libc::uid_t>().map_err(|e| {
74 <D as Deserializer>::Error::custom(format!(
75 "failed to parse priviledged quota uid {s}: {e}"
76 ))
77 })
78 })
79 .collect()
80}
81
82/// Permission structure that is configured to map the UID-GID at runtime
83#[cfg(feature = "fs_permission_translation")]
84#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
85pub struct PermissionData {
86 /// UID to be set for all the files in the path inside guest.
87 pub guest_uid: libc::uid_t,
88
89 /// GID to be set for all the files in the path inside guest.
90 pub guest_gid: libc::gid_t,
91
92 /// UID to be set for all the files in the path in the host.
93 pub host_uid: libc::uid_t,
94
95 /// GID to be set for all the files in the path in the host.
96 pub host_gid: libc::gid_t,
97
98 /// umask to be set at runtime for the files in the path.
99 pub umask: libc::mode_t,
100
101 /// This is the absolute path from the root of the shared directory.
102 pub perm_path: String,
103}
104
105#[cfg(feature = "fs_runtime_ugid_map")]
106fn process_ugid_map(result: Vec<Vec<String>>) -> Result<Vec<PermissionData>, io::Error> {
107 let mut permissions = Vec::new();
108
109 for inner_vec in result {
110 let guest_uid = match libc::uid_t::from_str(&inner_vec[0]) {
111 Ok(uid) => uid,
112 Err(_) => {
113 return Err(io::Error::from_raw_os_error(libc::EINVAL));
114 }
115 };
116
117 let guest_gid = match libc::gid_t::from_str(&inner_vec[1]) {
118 Ok(gid) => gid,
119 Err(_) => {
120 return Err(io::Error::from_raw_os_error(libc::EINVAL));
121 }
122 };
123
124 let host_uid = match libc::uid_t::from_str(&inner_vec[2]) {
125 Ok(uid) => uid,
126 Err(_) => {
127 return Err(io::Error::from_raw_os_error(libc::EINVAL));
128 }
129 };
130
131 let host_gid = match libc::gid_t::from_str(&inner_vec[3]) {
132 Ok(gid) => gid,
133 Err(_) => {
134 return Err(io::Error::from_raw_os_error(libc::EINVAL));
135 }
136 };
137
138 let umask = match libc::mode_t::from_str(&inner_vec[4]) {
139 Ok(mode) => mode,
140 Err(_) => {
141 return Err(io::Error::from_raw_os_error(libc::EINVAL));
142 }
143 };
144
145 let perm_path = inner_vec[5].clone();
146
147 // Create PermissionData and push it to the vector
148 permissions.push(PermissionData {
149 guest_uid,
150 guest_gid,
151 host_uid,
152 host_gid,
153 umask,
154 perm_path,
155 });
156 }
157
158 Ok(permissions)
159}
160
161#[cfg(feature = "fs_runtime_ugid_map")]
162fn deserialize_ugid_map<'de, D: Deserializer<'de>>(
163 deserializer: D,
164) -> Result<Vec<PermissionData>, D::Error> {
165 // space-separated list
166 let s: &str = serde::Deserialize::deserialize(deserializer)?;
167
168 let result: Vec<Vec<String>> = s
169 .split(';')
170 .map(|group| group.trim().split(' ').map(String::from).collect())
171 .collect();
172
173 // Length Validation for each inner vector
174 for inner_vec in &result {
175 if inner_vec.len() != 6 {
176 return Err(D::Error::custom(
177 "Invalid ugid_map format. Each group must have 6 elements.",
178 ));
179 }
180 }
181
182 let permissions = match process_ugid_map(result) {
183 Ok(p) => p,
184 Err(e) => {
185 return Err(D::Error::custom(format!(
186 "Error processing uid_gid_map: {e}"
187 )));
188 }
189 };
190
191 Ok(permissions)
192}
193
194/// Options that configure the behavior of the file system.
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
196#[serde(deny_unknown_fields, rename_all = "snake_case")]
197pub struct Config {
198 /// How long the FUSE client should consider directory entries and file/directory attributes to
199 /// be valid.
200 /// This value corresponds to `entry_timeout` and `attr_timeout` in
201 /// [libfuse's `fuse_config`](https://libfuse.github.io/doxygen/structfuse__config.html), but
202 /// we use the same value for the two.
203 ///
204 /// If the contents of a directory or the attributes of a file or directory can only be
205 /// modified by the FUSE client (i.e., the file system has exclusive access), then this should
206 /// be a large value.
207 /// The default value for this option is 5 seconds.
208 #[serde(
209 default = "config_default_timeout",
210 deserialize_with = "deserialize_timeout"
211 )]
212 pub timeout: Duration,
213
214 /// How long the FUSE client can cache negative lookup results.
215 /// If a file lookup fails, the client can assume the file doesn't exist until the timeout and
216 /// won't send lookup.
217 /// The value 0 means that negative lookup shouldn't be cached.
218 ///
219 /// If the contents of a directory can only be modified by the FUSE client (i.e., the file
220 /// system has exclusive access), then this should be a large value.
221 /// The default value for this option is 0 seconds (= no negative cache).
222 #[serde(
223 default = "config_default_negative_timeout",
224 deserialize_with = "deserialize_timeout"
225 )]
226 pub negative_timeout: Duration,
227
228 /// The caching policy the file system should use. See the documentation of `CachePolicy` for
229 /// more details.
230 #[serde(default, alias = "cache")]
231 pub cache_policy: CachePolicy,
232
233 /// Whether the file system should enabled writeback caching. This can improve performance as
234 /// it allows the FUSE client to cache and coalesce multiple writes before sending them to
235 /// the file system. However, enabling this option can increase the risk of data corruption
236 /// if the file contents can change without the knowledge of the FUSE client (i.e., the
237 /// server does **NOT** have exclusive access). Additionally, the file system should have
238 /// read access to all files in the directory it is serving as the FUSE client may send
239 /// read requests even for files opened with `O_WRONLY`.
240 ///
241 /// Therefore callers should only enable this option when they can guarantee that: 1) the file
242 /// system has exclusive access to the directory and 2) the file system has read permissions
243 /// for all files in that directory.
244 ///
245 /// The default value for this option is `false`.
246 #[serde(default)]
247 pub writeback: bool,
248
249 /// Controls whether security.* xattrs (except for security.selinux) are re-written. When this
250 /// is set to true, the server will add a "user.virtiofs" prefix to xattrs in the security
251 /// namespace. Setting these xattrs requires CAP_SYS_ADMIN in the namespace where the file
252 /// system was mounted and since the server usually runs in an unprivileged user namespace,
253 /// it's unlikely to have that capability.
254 ///
255 /// The default value for this option is `false`.
256 #[serde(default, alias = "rewrite-security-xattrs")]
257 pub rewrite_security_xattrs: bool,
258
259 /// Use case-insensitive lookups for directory entries (ASCII only).
260 ///
261 /// The default value for this option is `false`.
262 #[serde(default)]
263 pub ascii_casefold: bool,
264
265 // UIDs which are privileged to perform quota-related operations. We cannot perform a
266 // CAP_FOWNER check so we consult this list when the VM tries to set the project quota and
267 // the process uid doesn't match the owner uid. In that case, all uids in this list are
268 // treated as if they have CAP_FOWNER.
269 #[cfg(feature = "arc_quota")]
270 #[serde(default, deserialize_with = "deserialize_privileged_quota_uids")]
271 pub privileged_quota_uids: Vec<libc::uid_t>,
272
273 /// Use DAX for shared files.
274 ///
275 /// Enabling DAX can improve performance for frequently accessed files by mapping regions of
276 /// the file directly into the VM's memory region, allowing direct access with the cost of
277 /// slightly increased latency the first time the file is accessed. Additionally, since the
278 /// mapping is shared directly from the host kernel's file cache, enabling DAX can improve
279 /// performance even when the cache policy is `Never`.
280 ///
281 /// The default value for this option is `false`.
282 #[serde(default, alias = "dax")]
283 pub use_dax: bool,
284
285 /// Enable support for POSIX acls.
286 ///
287 /// Enable POSIX acl support for the shared directory. This requires that the underlying file
288 /// system also supports POSIX acls.
289 ///
290 /// The default value for this option is `true`.
291 #[serde(default = "config_default_posix_acl")]
292 pub posix_acl: bool,
293
294 // Maximum number of dynamic permission paths.
295 //
296 // The dynamic permission paths are used to set specific paths certain uid/gid after virtiofs
297 // device is created. It is for arcvm special usage, normal device should not support
298 // this feature.
299 //
300 // The default value for this option is 0.
301 #[serde(default)]
302 pub max_dynamic_perm: usize,
303
304 // Maximum number of dynamic xattr paths.
305 //
306 // The dynamic xattr paths are used to set specific paths certain xattr after virtiofs
307 // device is created. It is for arcvm special usage, normal device should not support
308 // this feature.
309 //
310 // The default value for this option is 0.
311 #[serde(default)]
312 pub max_dynamic_xattr: usize,
313
314 // Controls whether fuse_security_context feature is enabled
315 //
316 // The FUSE_SECURITY_CONTEXT feature needs write data into /proc/thread-self/attr/fscreate.
317 // For the hosts that prohibit the write operation, the option should be set to false to
318 // disable the FUSE_SECURITY_CONTEXT feature. When FUSE_SECURITY_CONTEXT is disabled, the
319 // security context won't be passed with fuse request, which makes guest created files/dir
320 // having unlabeled security context or empty security context.
321 //
322 // The default value for this option is true
323 #[serde(default = "config_default_security_ctx")]
324 pub security_ctx: bool,
325
326 // Specifies run-time UID/GID mapping that works without user namespaces.
327 //
328 // The virtio-fs usually does mapping of UIDs/GIDs between host and guest with user namespace.
329 // In Android, however, user namespace isn't available for non-root users.
330 // This allows mapping UIDs and GIDs without user namespace by intercepting FUSE
331 // requests and translating UID/GID in virito-fs's process at runtime.
332 //
333 // The format is "guest-uid, guest-gid, host-uid, host-gid, umask, path;{repeat}"
334 //
335 // guest-uid: UID to be set for all the files in the path inside guest.
336 // guest-gid: GID to be set for all the files in the path inside guest.
337 // host-uid: UID to be set for all the files in the path in the host.
338 // host-gid: GID to be set for all the files in the path in the host.
339 // umask: umask to be set at runtime for the files in the path.
340 // path: This is the absolute path from the root of the shared directory.
341 //
342 // This follows similar format to ARCVM IOCTL "FS_IOC_SETPERMISSION"
343 #[cfg(feature = "fs_runtime_ugid_map")]
344 #[serde(default, deserialize_with = "deserialize_ugid_map")]
345 pub ugid_map: Vec<PermissionData>,
346
347 #[cfg(any(target_os = "android", target_os = "linux"))]
348 #[serde(default)]
349 /// set MADV_DONTFORK on guest memory
350 ///
351 /// Intended for use in combination with protected VMs, where the guest memory can be dangerous
352 /// to access. Some systems, e.g. Android, have tools that fork processes and examine their
353 /// memory. This flag effectively hides the guest memory from those tools.
354 ///
355 /// Not compatible with sandboxing.
356 pub unmap_guest_memory_on_fork: bool,
357}
358
359impl Default for Config {
360 fn default() -> Self {
361 Config {
362 timeout: config_default_timeout(),
363 negative_timeout: config_default_negative_timeout(),
364 cache_policy: Default::default(),
365 writeback: false,
366 rewrite_security_xattrs: false,
367 ascii_casefold: false,
368 #[cfg(feature = "arc_quota")]
369 privileged_quota_uids: Default::default(),
370 use_dax: false,
371 posix_acl: config_default_posix_acl(),
372 max_dynamic_perm: 0,
373 max_dynamic_xattr: 0,
374 security_ctx: config_default_security_ctx(),
375 #[cfg(feature = "fs_runtime_ugid_map")]
376 ugid_map: Vec::new(),
377 #[cfg(any(target_os = "android", target_os = "linux"))]
378 unmap_guest_memory_on_fork: false,
379 }
380 }
381}
382
383#[cfg(all(test, feature = "fs_runtime_ugid_map"))]
384mod tests {
385
386 use super::*;
387 #[test]
388 fn test_deserialize_ugid_map_valid() {
389 let input_string =
390 "\"1000 1000 1000 1000 0022 /path/to/dir;2000 2000 2000 2000 0022 /path/to/other/dir\"";
391
392 let mut deserializer = serde_json::Deserializer::from_str(input_string);
393 let result = deserialize_ugid_map(&mut deserializer).unwrap();
394
395 assert_eq!(result.len(), 2);
396 assert_eq!(
397 result,
398 vec![
399 PermissionData {
400 guest_uid: 1000,
401 guest_gid: 1000,
402 host_uid: 1000,
403 host_gid: 1000,
404 umask: 22,
405 perm_path: "/path/to/dir".to_string(),
406 },
407 PermissionData {
408 guest_uid: 2000,
409 guest_gid: 2000,
410 host_uid: 2000,
411 host_gid: 2000,
412 umask: 22,
413 perm_path: "/path/to/other/dir".to_string(),
414 },
415 ]
416 );
417 }
418
419 #[test]
420 fn test_process_ugid_map_valid() {
421 let input_vec = vec![
422 vec![
423 "1000".to_string(),
424 "1000".to_string(),
425 "1000".to_string(),
426 "1000".to_string(),
427 "0022".to_string(),
428 "/path/to/dir".to_string(),
429 ],
430 vec![
431 "2000".to_string(),
432 "2000".to_string(),
433 "2000".to_string(),
434 "2000".to_string(),
435 "0022".to_string(),
436 "/path/to/other/dir".to_string(),
437 ],
438 ];
439
440 let result = process_ugid_map(input_vec).unwrap();
441 assert_eq!(result.len(), 2);
442 assert_eq!(
443 result,
444 vec![
445 PermissionData {
446 guest_uid: 1000,
447 guest_gid: 1000,
448 host_uid: 1000,
449 host_gid: 1000,
450 umask: 22,
451 perm_path: "/path/to/dir".to_string(),
452 },
453 PermissionData {
454 guest_uid: 2000,
455 guest_gid: 2000,
456 host_uid: 2000,
457 host_gid: 2000,
458 umask: 22,
459 perm_path: "/path/to/other/dir".to_string(),
460 },
461 ]
462 );
463 }
464
465 #[test]
466 fn test_deserialize_ugid_map_invalid_format() {
467 let input_string = "\"1000 1000 1000 0022 /path/to/dir\""; // Missing one element
468
469 // Create a Deserializer from the input string
470 let mut deserializer = serde_json::Deserializer::from_str(input_string);
471 let result = deserialize_ugid_map(&mut deserializer);
472 assert!(result.is_err());
473 }
474
475 #[test]
476 fn test_deserialize_ugid_map_invalid_guest_uid() {
477 let input_string = "\"invalid 1000 1000 1000 0022 /path/to/dir\""; // Invalid guest-UID
478
479 // Create a Deserializer from the input string
480 let mut deserializer = serde_json::Deserializer::from_str(input_string);
481 let result = deserialize_ugid_map(&mut deserializer);
482 assert!(result.is_err());
483 }
484
485 #[test]
486 fn test_deserialize_ugid_map_invalid_guest_gid() {
487 let input_string = "\"1000 invalid 1000 1000 0022 /path/to/dir\""; // Invalid guest-GID
488
489 // Create a Deserializer from the input string
490 let mut deserializer = serde_json::Deserializer::from_str(input_string);
491 let result = deserialize_ugid_map(&mut deserializer);
492 assert!(result.is_err());
493 }
494
495 #[test]
496 fn test_deserialize_ugid_map_invalid_umask() {
497 let input_string = "\"1000 1000 1000 1000 invalid /path/to/dir\""; // Invalid umask
498
499 // Create a Deserializer from the input string
500 let mut deserializer = serde_json::Deserializer::from_str(input_string);
501 let result = deserialize_ugid_map(&mut deserializer);
502 assert!(result.is_err());
503 }
504
505 #[test]
506 fn test_deserialize_ugid_map_invalid_host_uid() {
507 let input_string = "\"1000 1000 invalid 1000 0022 /path/to/dir\""; // Invalid host-UID
508
509 // Create a Deserializer from the input string
510 let mut deserializer = serde_json::Deserializer::from_str(input_string);
511 let result = deserialize_ugid_map(&mut deserializer);
512 assert!(result.is_err());
513 }
514
515 #[test]
516 fn test_deserialize_ugid_map_invalid_host_gid() {
517 let input_string = "\"1000 1000 1000 invalid 0022 /path/to/dir\""; // Invalid host-UID
518
519 // Create a Deserializer from the input string
520 let mut deserializer = serde_json::Deserializer::from_str(input_string);
521 let result = deserialize_ugid_map(&mut deserializer);
522 assert!(result.is_err());
523 }
524}