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}