Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/fs/config.rs
5394 views
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
43
const fn config_default_timeout() -> Duration {
44
Duration::from_secs(5)
45
}
46
47
const fn config_default_negative_timeout() -> Duration {
48
Duration::ZERO
49
}
50
51
const fn config_default_posix_acl() -> bool {
52
true
53
}
54
55
const fn config_default_security_ctx() -> bool {
56
true
57
}
58
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")]
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")]
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")]
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: {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")]
197
pub 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
359
impl 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"))]
384
mod 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
}
525
526