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