xref: /aosp_15_r20/external/crosvm/devices/src/virtio/fs/config.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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")]
6 use std::io;
7 #[cfg(feature = "fs_permission_translation")]
8 use std::str::FromStr;
9 use std::time::Duration;
10 
11 #[cfg(feature = "fs_permission_translation")]
12 use libc;
13 #[allow(unused_imports)]
14 use serde::de::Error;
15 use serde::Deserialize;
16 use serde::Deserializer;
17 use serde::Serialize;
18 use 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")]
25 pub 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 
config_default_timeout() -> Duration43 const fn config_default_timeout() -> Duration {
44     Duration::from_secs(5)
45 }
46 
config_default_negative_timeout() -> Duration47 const fn config_default_negative_timeout() -> Duration {
48     Duration::ZERO
49 }
50 
config_default_posix_acl() -> bool51 const fn config_default_posix_acl() -> bool {
52     true
53 }
54 
config_default_security_ctx() -> bool55 const fn config_default_security_ctx() -> bool {
56     true
57 }
58 
deserialize_timeout<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Duration, D::Error>59 fn 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")]
deserialize_privileged_quota_uids<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result<Vec<libc::uid_t>, D::Error>66 fn 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)]
85 pub 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")]
process_ugid_map(result: Vec<Vec<String>>) -> Result<Vec<PermissionData>, io::Error>106 fn 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")]
deserialize_ugid_map<'de, D: Deserializer<'de>>( deserializer: D, ) -> Result<Vec<PermissionData>, D::Error>162 fn 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: {}",
187                 e
188             )));
189         }
190     };
191 
192     Ok(permissions)
193 }
194 
195 /// Options that configure the behavior of the file system.
196 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
197 #[serde(deny_unknown_fields, rename_all = "snake_case")]
198 pub struct Config {
199     /// How long the FUSE client should consider directory entries and file/directory attributes to
200     /// be valid.
201     /// This value corresponds to `entry_timeout` and `attr_timeout` in
202     /// [libfuse's `fuse_config`](https://libfuse.github.io/doxygen/structfuse__config.html), but
203     /// we use the same value for the two.
204     ///
205     /// If the contents of a directory or the attributes of a file or directory can only be
206     /// modified by the FUSE client (i.e., the file system has exclusive access), then this should
207     /// be a large value.
208     /// The default value for this option is 5 seconds.
209     #[serde(
210         default = "config_default_timeout",
211         deserialize_with = "deserialize_timeout"
212     )]
213     pub timeout: Duration,
214 
215     /// How long the FUSE client can cache negative lookup results.
216     /// If a file lookup fails, the client can assume the file doesn't exist until the timeout and
217     ///  won't send lookup.
218     /// The value 0 means that negative lookup shouldn't be cached.
219     ///
220     /// If the contents of a directory can only be modified by the FUSE client (i.e., the file
221     /// system has exclusive access), then this should be a large value.
222     /// The default value for this option is 0 seconds (= no negative cache).
223     #[serde(
224         default = "config_default_negative_timeout",
225         deserialize_with = "deserialize_timeout"
226     )]
227     pub negative_timeout: Duration,
228 
229     /// The caching policy the file system should use. See the documentation of `CachePolicy` for
230     /// more details.
231     #[serde(default, alias = "cache")]
232     pub cache_policy: CachePolicy,
233 
234     /// Whether the file system should enabled writeback caching. This can improve performance as
235     /// it allows the FUSE client to cache and coalesce multiple writes before sending them to
236     /// the file system. However, enabling this option can increase the risk of data corruption
237     /// if the file contents can change without the knowledge of the FUSE client (i.e., the
238     /// server does **NOT** have exclusive access). Additionally, the file system should have
239     /// read access to all files in the directory it is serving as the FUSE client may send
240     /// read requests even for files opened with `O_WRONLY`.
241     ///
242     /// Therefore callers should only enable this option when they can guarantee that: 1) the file
243     /// system has exclusive access to the directory and 2) the file system has read permissions
244     /// for all files in that directory.
245     ///
246     /// The default value for this option is `false`.
247     #[serde(default)]
248     pub writeback: bool,
249 
250     /// Controls whether security.* xattrs (except for security.selinux) are re-written. When this
251     /// is set to true, the server will add a "user.virtiofs" prefix to xattrs in the security
252     /// namespace. Setting these xattrs requires CAP_SYS_ADMIN in the namespace where the file
253     /// system was mounted and since the server usually runs in an unprivileged user namespace,
254     /// it's unlikely to have that capability.
255     ///
256     /// The default value for this option is `false`.
257     #[serde(default, alias = "rewrite-security-xattrs")]
258     pub rewrite_security_xattrs: bool,
259 
260     /// Use case-insensitive lookups for directory entries (ASCII only).
261     ///
262     /// The default value for this option is `false`.
263     #[serde(default)]
264     pub ascii_casefold: bool,
265 
266     // UIDs which are privileged to perform quota-related operations. We cannot perform a
267     // CAP_FOWNER check so we consult this list when the VM tries to set the project quota and
268     // the process uid doesn't match the owner uid. In that case, all uids in this list are
269     // treated as if they have CAP_FOWNER.
270     #[cfg(feature = "arc_quota")]
271     #[serde(default, deserialize_with = "deserialize_privileged_quota_uids")]
272     pub privileged_quota_uids: Vec<libc::uid_t>,
273 
274     /// Use DAX for shared files.
275     ///
276     /// Enabling DAX can improve performance for frequently accessed files by mapping regions of
277     /// the file directly into the VM's memory region, allowing direct access with the cost of
278     /// slightly increased latency the first time the file is accessed. Additionally, since the
279     /// mapping is shared directly from the host kernel's file cache, enabling DAX can improve
280     /// performance even when the cache policy is `Never`.
281     ///
282     /// The default value for this option is `false`.
283     #[serde(default, alias = "dax")]
284     pub use_dax: bool,
285 
286     /// Enable support for POSIX acls.
287     ///
288     /// Enable POSIX acl support for the shared directory. This requires that the underlying file
289     /// system also supports POSIX acls.
290     ///
291     /// The default value for this option is `true`.
292     #[serde(default = "config_default_posix_acl")]
293     pub posix_acl: bool,
294 
295     // Maximum number of dynamic permission paths.
296     //
297     // The dynamic permission paths are used to set specific paths certain uid/gid after virtiofs
298     // device is created. It is for arcvm special usage, normal device should not support
299     // this feature.
300     //
301     // The default value for this option is 0.
302     #[serde(default)]
303     pub max_dynamic_perm: usize,
304 
305     // Maximum number of dynamic xattr paths.
306     //
307     // The dynamic xattr paths are used to set specific paths certain xattr after virtiofs
308     // device is created. It is for arcvm special usage, normal device should not support
309     // this feature.
310     //
311     // The default value for this option is 0.
312     #[serde(default)]
313     pub max_dynamic_xattr: usize,
314 
315     // Controls whether fuse_security_context feature is enabled
316     //
317     // The FUSE_SECURITY_CONTEXT feature needs write data into /proc/thread-self/attr/fscreate.
318     // For the hosts that prohibit the write operation, the option should be set to false to
319     // disable the FUSE_SECURITY_CONTEXT feature. When FUSE_SECURITY_CONTEXT is disabled, the
320     // security context won't be passed with fuse request, which makes guest created files/dir
321     // having unlabeled security context or empty security context.
322     //
323     // The default value for this option is true
324     #[serde(default = "config_default_security_ctx")]
325     pub security_ctx: bool,
326 
327     // Specifies run-time UID/GID mapping that works without user namespaces.
328     //
329     // The virtio-fs usually does mapping of UIDs/GIDs between host and guest with user namespace.
330     // In Android, however, user namespace isn't available for non-root users.
331     // This allows mapping UIDs and GIDs without user namespace by intercepting FUSE
332     // requests and translating UID/GID in virito-fs's process at runtime.
333     //
334     // The format is "guest-uid, guest-gid, host-uid, host-gid, umask, path;{repeat}"
335     //
336     // guest-uid: UID to be set for all the files in the path inside guest.
337     // guest-gid: GID to be set for all the files in the path inside guest.
338     // host-uid: UID to be set for all the files in the path in the host.
339     // host-gid: GID to be set for all the files in the path in the host.
340     // umask: umask to be set at runtime for the files in the path.
341     // path: This is the absolute path from the root of the shared directory.
342     //
343     // This follows similar format to ARCVM IOCTL "FS_IOC_SETPERMISSION"
344     #[cfg(feature = "fs_runtime_ugid_map")]
345     #[serde(default, deserialize_with = "deserialize_ugid_map")]
346     pub ugid_map: Vec<PermissionData>,
347 }
348 
349 impl Default for Config {
default() -> Self350     fn default() -> Self {
351         Config {
352             timeout: config_default_timeout(),
353             negative_timeout: config_default_negative_timeout(),
354             cache_policy: Default::default(),
355             writeback: false,
356             rewrite_security_xattrs: false,
357             ascii_casefold: false,
358             #[cfg(feature = "arc_quota")]
359             privileged_quota_uids: Default::default(),
360             use_dax: false,
361             posix_acl: config_default_posix_acl(),
362             max_dynamic_perm: 0,
363             max_dynamic_xattr: 0,
364             security_ctx: config_default_security_ctx(),
365             #[cfg(feature = "fs_runtime_ugid_map")]
366             ugid_map: Vec::new(),
367         }
368     }
369 }
370 
371 #[cfg(all(test, feature = "fs_runtime_ugid_map"))]
372 mod tests {
373 
374     use super::*;
375     #[test]
test_deserialize_ugid_map_valid()376     fn test_deserialize_ugid_map_valid() {
377         let input_string =
378             "\"1000 1000 1000 1000 0022 /path/to/dir;2000 2000 2000 2000 0022 /path/to/other/dir\"";
379 
380         let mut deserializer = serde_json::Deserializer::from_str(input_string);
381         let result = deserialize_ugid_map(&mut deserializer).unwrap();
382 
383         assert_eq!(result.len(), 2);
384         assert_eq!(
385             result,
386             vec![
387                 PermissionData {
388                     guest_uid: 1000,
389                     guest_gid: 1000,
390                     host_uid: 1000,
391                     host_gid: 1000,
392                     umask: 22,
393                     perm_path: "/path/to/dir".to_string(),
394                 },
395                 PermissionData {
396                     guest_uid: 2000,
397                     guest_gid: 2000,
398                     host_uid: 2000,
399                     host_gid: 2000,
400                     umask: 22,
401                     perm_path: "/path/to/other/dir".to_string(),
402                 },
403             ]
404         );
405     }
406 
407     #[test]
test_process_ugid_map_valid()408     fn test_process_ugid_map_valid() {
409         let input_vec = vec![
410             vec![
411                 "1000".to_string(),
412                 "1000".to_string(),
413                 "1000".to_string(),
414                 "1000".to_string(),
415                 "0022".to_string(),
416                 "/path/to/dir".to_string(),
417             ],
418             vec![
419                 "2000".to_string(),
420                 "2000".to_string(),
421                 "2000".to_string(),
422                 "2000".to_string(),
423                 "0022".to_string(),
424                 "/path/to/other/dir".to_string(),
425             ],
426         ];
427 
428         let result = process_ugid_map(input_vec).unwrap();
429         assert_eq!(result.len(), 2);
430         assert_eq!(
431             result,
432             vec![
433                 PermissionData {
434                     guest_uid: 1000,
435                     guest_gid: 1000,
436                     host_uid: 1000,
437                     host_gid: 1000,
438                     umask: 22,
439                     perm_path: "/path/to/dir".to_string(),
440                 },
441                 PermissionData {
442                     guest_uid: 2000,
443                     guest_gid: 2000,
444                     host_uid: 2000,
445                     host_gid: 2000,
446                     umask: 22,
447                     perm_path: "/path/to/other/dir".to_string(),
448                 },
449             ]
450         );
451     }
452 
453     #[test]
test_deserialize_ugid_map_invalid_format()454     fn test_deserialize_ugid_map_invalid_format() {
455         let input_string = "\"1000 1000 1000 0022 /path/to/dir\""; // Missing one element
456 
457         // Create a Deserializer from the input string
458         let mut deserializer = serde_json::Deserializer::from_str(input_string);
459         let result = deserialize_ugid_map(&mut deserializer);
460         assert!(result.is_err());
461     }
462 
463     #[test]
test_deserialize_ugid_map_invalid_guest_uid()464     fn test_deserialize_ugid_map_invalid_guest_uid() {
465         let input_string = "\"invalid 1000 1000 1000 0022 /path/to/dir\""; // Invalid guest-UID
466 
467         // Create a Deserializer from the input string
468         let mut deserializer = serde_json::Deserializer::from_str(input_string);
469         let result = deserialize_ugid_map(&mut deserializer);
470         assert!(result.is_err());
471     }
472 
473     #[test]
test_deserialize_ugid_map_invalid_guest_gid()474     fn test_deserialize_ugid_map_invalid_guest_gid() {
475         let input_string = "\"1000 invalid 1000 1000 0022 /path/to/dir\""; // Invalid guest-GID
476 
477         // Create a Deserializer from the input string
478         let mut deserializer = serde_json::Deserializer::from_str(input_string);
479         let result = deserialize_ugid_map(&mut deserializer);
480         assert!(result.is_err());
481     }
482 
483     #[test]
test_deserialize_ugid_map_invalid_umask()484     fn test_deserialize_ugid_map_invalid_umask() {
485         let input_string = "\"1000 1000 1000 1000 invalid /path/to/dir\""; // Invalid umask
486 
487         // Create a Deserializer from the input string
488         let mut deserializer = serde_json::Deserializer::from_str(input_string);
489         let result = deserialize_ugid_map(&mut deserializer);
490         assert!(result.is_err());
491     }
492 
493     #[test]
test_deserialize_ugid_map_invalid_host_uid()494     fn test_deserialize_ugid_map_invalid_host_uid() {
495         let input_string = "\"1000 1000 invalid 1000 0022 /path/to/dir\""; // Invalid host-UID
496 
497         // Create a Deserializer from the input string
498         let mut deserializer = serde_json::Deserializer::from_str(input_string);
499         let result = deserialize_ugid_map(&mut deserializer);
500         assert!(result.is_err());
501     }
502 
503     #[test]
test_deserialize_ugid_map_invalid_host_gid()504     fn test_deserialize_ugid_map_invalid_host_gid() {
505         let input_string = "\"1000 1000 1000 invalid 0022 /path/to/dir\""; // Invalid host-UID
506 
507         // Create a Deserializer from the input string
508         let mut deserializer = serde_json::Deserializer::from_str(input_string);
509         let result = deserialize_ugid_map(&mut deserializer);
510         assert!(result.is_err());
511     }
512 }
513