// Copyright 2023 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34#[cfg(feature = "fs_permission_translation")]5use std::io;6#[cfg(feature = "fs_permission_translation")]7use std::str::FromStr;8use std::time::Duration;910#[cfg(feature = "fs_permission_translation")]11use libc;12#[allow(unused_imports)]13use serde::de::Error;14use serde::Deserialize;15use serde::Deserializer;16use serde::Serialize;17use serde_keyvalue::FromKeyValues;1819/// The caching policy that the file system should report to the FUSE client. By default the FUSE20/// protocol uses close-to-open consistency. This means that any cached contents of the file are21/// invalidated the next time that file is opened.22#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, FromKeyValues)]23#[serde(rename_all = "kebab-case")]24pub enum CachePolicy {25/// The client should never cache file data and all I/O should be directly forwarded to the26/// server. This policy must be selected when file contents may change without the knowledge of27/// the FUSE client (i.e., the file system does not have exclusive access to the directory).28Never,2930/// The client is free to choose when and how to cache file data. This is the default policy31/// and uses close-to-open consistency as described in the enum documentation.32#[default]33Auto,3435/// The client should always cache file data. This means that the FUSE client will not36/// invalidate any cached data that was returned by the file system the last time the file was37/// opened. This policy should only be selected when the file system has exclusive access to38/// the directory.39Always,40}4142const fn config_default_timeout() -> Duration {43Duration::from_secs(5)44}4546const fn config_default_negative_timeout() -> Duration {47Duration::ZERO48}4950const fn config_default_posix_acl() -> bool {51true52}5354const fn config_default_security_ctx() -> bool {55true56}5758fn deserialize_timeout<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Duration, D::Error> {59let secs = u64::deserialize(deserializer)?;6061Ok(Duration::from_secs(secs))62}6364#[cfg(feature = "arc_quota")]65fn deserialize_privileged_quota_uids<'de, D: Deserializer<'de>>(66deserializer: D,67) -> Result<Vec<libc::uid_t>, D::Error> {68// space-separated list69let s: &str = serde::Deserialize::deserialize(deserializer)?;70s.split(' ')71.map(|s| {72s.parse::<libc::uid_t>().map_err(|e| {73<D as Deserializer>::Error::custom(format!(74"failed to parse priviledged quota uid {s}: {e}"75))76})77})78.collect()79}8081/// Permission structure that is configured to map the UID-GID at runtime82#[cfg(feature = "fs_permission_translation")]83#[derive(Debug, Clone, Eq, PartialEq, Serialize)]84pub struct PermissionData {85/// UID to be set for all the files in the path inside guest.86pub guest_uid: libc::uid_t,8788/// GID to be set for all the files in the path inside guest.89pub guest_gid: libc::gid_t,9091/// UID to be set for all the files in the path in the host.92pub host_uid: libc::uid_t,9394/// GID to be set for all the files in the path in the host.95pub host_gid: libc::gid_t,9697/// umask to be set at runtime for the files in the path.98pub umask: libc::mode_t,99100/// This is the absolute path from the root of the shared directory.101pub perm_path: String,102}103104#[cfg(feature = "fs_runtime_ugid_map")]105fn process_ugid_map(result: Vec<Vec<String>>) -> Result<Vec<PermissionData>, io::Error> {106let mut permissions = Vec::new();107108for inner_vec in result {109let guest_uid = match libc::uid_t::from_str(&inner_vec[0]) {110Ok(uid) => uid,111Err(_) => {112return Err(io::Error::from_raw_os_error(libc::EINVAL));113}114};115116let guest_gid = match libc::gid_t::from_str(&inner_vec[1]) {117Ok(gid) => gid,118Err(_) => {119return Err(io::Error::from_raw_os_error(libc::EINVAL));120}121};122123let host_uid = match libc::uid_t::from_str(&inner_vec[2]) {124Ok(uid) => uid,125Err(_) => {126return Err(io::Error::from_raw_os_error(libc::EINVAL));127}128};129130let host_gid = match libc::gid_t::from_str(&inner_vec[3]) {131Ok(gid) => gid,132Err(_) => {133return Err(io::Error::from_raw_os_error(libc::EINVAL));134}135};136137let umask = match libc::mode_t::from_str(&inner_vec[4]) {138Ok(mode) => mode,139Err(_) => {140return Err(io::Error::from_raw_os_error(libc::EINVAL));141}142};143144let perm_path = inner_vec[5].clone();145146// Create PermissionData and push it to the vector147permissions.push(PermissionData {148guest_uid,149guest_gid,150host_uid,151host_gid,152umask,153perm_path,154});155}156157Ok(permissions)158}159160#[cfg(feature = "fs_runtime_ugid_map")]161fn deserialize_ugid_map<'de, D: Deserializer<'de>>(162deserializer: D,163) -> Result<Vec<PermissionData>, D::Error> {164// space-separated list165let s: &str = serde::Deserialize::deserialize(deserializer)?;166167let result: Vec<Vec<String>> = s168.split(';')169.map(|group| group.trim().split(' ').map(String::from).collect())170.collect();171172// Length Validation for each inner vector173for inner_vec in &result {174if inner_vec.len() != 6 {175return Err(D::Error::custom(176"Invalid ugid_map format. Each group must have 6 elements.",177));178}179}180181let permissions = match process_ugid_map(result) {182Ok(p) => p,183Err(e) => {184return Err(D::Error::custom(format!(185"Error processing uid_gid_map: {e}"186)));187}188};189190Ok(permissions)191}192193/// Options that configure the behavior of the file system.194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]195#[serde(deny_unknown_fields, rename_all = "snake_case")]196pub struct Config {197/// How long the FUSE client should consider directory entries and file/directory attributes to198/// be valid.199/// This value corresponds to `entry_timeout` and `attr_timeout` in200/// [libfuse's `fuse_config`](https://libfuse.github.io/doxygen/structfuse__config.html), but201/// we use the same value for the two.202///203/// If the contents of a directory or the attributes of a file or directory can only be204/// modified by the FUSE client (i.e., the file system has exclusive access), then this should205/// be a large value.206/// The default value for this option is 5 seconds.207#[serde(208default = "config_default_timeout",209deserialize_with = "deserialize_timeout"210)]211pub timeout: Duration,212213/// How long the FUSE client can cache negative lookup results.214/// If a file lookup fails, the client can assume the file doesn't exist until the timeout and215/// won't send lookup.216/// The value 0 means that negative lookup shouldn't be cached.217///218/// If the contents of a directory can only be modified by the FUSE client (i.e., the file219/// system has exclusive access), then this should be a large value.220/// The default value for this option is 0 seconds (= no negative cache).221#[serde(222default = "config_default_negative_timeout",223deserialize_with = "deserialize_timeout"224)]225pub negative_timeout: Duration,226227/// The caching policy the file system should use. See the documentation of `CachePolicy` for228/// more details.229#[serde(default, alias = "cache")]230pub cache_policy: CachePolicy,231232/// Whether the file system should enabled writeback caching. This can improve performance as233/// it allows the FUSE client to cache and coalesce multiple writes before sending them to234/// the file system. However, enabling this option can increase the risk of data corruption235/// if the file contents can change without the knowledge of the FUSE client (i.e., the236/// server does **NOT** have exclusive access). Additionally, the file system should have237/// read access to all files in the directory it is serving as the FUSE client may send238/// read requests even for files opened with `O_WRONLY`.239///240/// Therefore callers should only enable this option when they can guarantee that: 1) the file241/// system has exclusive access to the directory and 2) the file system has read permissions242/// for all files in that directory.243///244/// The default value for this option is `false`.245#[serde(default)]246pub writeback: bool,247248/// Controls whether security.* xattrs (except for security.selinux) are re-written. When this249/// is set to true, the server will add a "user.virtiofs" prefix to xattrs in the security250/// namespace. Setting these xattrs requires CAP_SYS_ADMIN in the namespace where the file251/// system was mounted and since the server usually runs in an unprivileged user namespace,252/// it's unlikely to have that capability.253///254/// The default value for this option is `false`.255#[serde(default, alias = "rewrite-security-xattrs")]256pub rewrite_security_xattrs: bool,257258/// Use case-insensitive lookups for directory entries (ASCII only).259///260/// The default value for this option is `false`.261#[serde(default)]262pub ascii_casefold: bool,263264// UIDs which are privileged to perform quota-related operations. We cannot perform a265// CAP_FOWNER check so we consult this list when the VM tries to set the project quota and266// the process uid doesn't match the owner uid. In that case, all uids in this list are267// treated as if they have CAP_FOWNER.268#[cfg(feature = "arc_quota")]269#[serde(default, deserialize_with = "deserialize_privileged_quota_uids")]270pub privileged_quota_uids: Vec<libc::uid_t>,271272/// Use DAX for shared files.273///274/// Enabling DAX can improve performance for frequently accessed files by mapping regions of275/// the file directly into the VM's memory region, allowing direct access with the cost of276/// slightly increased latency the first time the file is accessed. Additionally, since the277/// mapping is shared directly from the host kernel's file cache, enabling DAX can improve278/// performance even when the cache policy is `Never`.279///280/// The default value for this option is `false`.281#[serde(default, alias = "dax")]282pub use_dax: bool,283284/// Enable support for POSIX acls.285///286/// Enable POSIX acl support for the shared directory. This requires that the underlying file287/// system also supports POSIX acls.288///289/// The default value for this option is `true`.290#[serde(default = "config_default_posix_acl")]291pub posix_acl: bool,292293// Maximum number of dynamic permission paths.294//295// The dynamic permission paths are used to set specific paths certain uid/gid after virtiofs296// device is created. It is for arcvm special usage, normal device should not support297// this feature.298//299// The default value for this option is 0.300#[serde(default)]301pub max_dynamic_perm: usize,302303// Maximum number of dynamic xattr paths.304//305// The dynamic xattr paths are used to set specific paths certain xattr after virtiofs306// device is created. It is for arcvm special usage, normal device should not support307// this feature.308//309// The default value for this option is 0.310#[serde(default)]311pub max_dynamic_xattr: usize,312313// Controls whether fuse_security_context feature is enabled314//315// The FUSE_SECURITY_CONTEXT feature needs write data into /proc/thread-self/attr/fscreate.316// For the hosts that prohibit the write operation, the option should be set to false to317// disable the FUSE_SECURITY_CONTEXT feature. When FUSE_SECURITY_CONTEXT is disabled, the318// security context won't be passed with fuse request, which makes guest created files/dir319// having unlabeled security context or empty security context.320//321// The default value for this option is true322#[serde(default = "config_default_security_ctx")]323pub security_ctx: bool,324325// Specifies run-time UID/GID mapping that works without user namespaces.326//327// The virtio-fs usually does mapping of UIDs/GIDs between host and guest with user namespace.328// In Android, however, user namespace isn't available for non-root users.329// This allows mapping UIDs and GIDs without user namespace by intercepting FUSE330// requests and translating UID/GID in virito-fs's process at runtime.331//332// The format is "guest-uid, guest-gid, host-uid, host-gid, umask, path;{repeat}"333//334// guest-uid: UID to be set for all the files in the path inside guest.335// guest-gid: GID to be set for all the files in the path inside guest.336// host-uid: UID to be set for all the files in the path in the host.337// host-gid: GID to be set for all the files in the path in the host.338// umask: umask to be set at runtime for the files in the path.339// path: This is the absolute path from the root of the shared directory.340//341// This follows similar format to ARCVM IOCTL "FS_IOC_SETPERMISSION"342#[cfg(feature = "fs_runtime_ugid_map")]343#[serde(default, deserialize_with = "deserialize_ugid_map")]344pub ugid_map: Vec<PermissionData>,345346#[cfg(any(target_os = "android", target_os = "linux"))]347#[serde(default)]348/// set MADV_DONTFORK on guest memory349///350/// Intended for use in combination with protected VMs, where the guest memory can be dangerous351/// to access. Some systems, e.g. Android, have tools that fork processes and examine their352/// memory. This flag effectively hides the guest memory from those tools.353///354/// Not compatible with sandboxing.355pub unmap_guest_memory_on_fork: bool,356}357358impl Default for Config {359fn default() -> Self {360Config {361timeout: config_default_timeout(),362negative_timeout: config_default_negative_timeout(),363cache_policy: Default::default(),364writeback: false,365rewrite_security_xattrs: false,366ascii_casefold: false,367#[cfg(feature = "arc_quota")]368privileged_quota_uids: Default::default(),369use_dax: false,370posix_acl: config_default_posix_acl(),371max_dynamic_perm: 0,372max_dynamic_xattr: 0,373security_ctx: config_default_security_ctx(),374#[cfg(feature = "fs_runtime_ugid_map")]375ugid_map: Vec::new(),376#[cfg(any(target_os = "android", target_os = "linux"))]377unmap_guest_memory_on_fork: false,378}379}380}381382#[cfg(all(test, feature = "fs_runtime_ugid_map"))]383mod tests {384385use super::*;386#[test]387fn test_deserialize_ugid_map_valid() {388let input_string =389"\"1000 1000 1000 1000 0022 /path/to/dir;2000 2000 2000 2000 0022 /path/to/other/dir\"";390391let mut deserializer = serde_json::Deserializer::from_str(input_string);392let result = deserialize_ugid_map(&mut deserializer).unwrap();393394assert_eq!(result.len(), 2);395assert_eq!(396result,397vec![398PermissionData {399guest_uid: 1000,400guest_gid: 1000,401host_uid: 1000,402host_gid: 1000,403umask: 22,404perm_path: "/path/to/dir".to_string(),405},406PermissionData {407guest_uid: 2000,408guest_gid: 2000,409host_uid: 2000,410host_gid: 2000,411umask: 22,412perm_path: "/path/to/other/dir".to_string(),413},414]415);416}417418#[test]419fn test_process_ugid_map_valid() {420let input_vec = vec![421vec![422"1000".to_string(),423"1000".to_string(),424"1000".to_string(),425"1000".to_string(),426"0022".to_string(),427"/path/to/dir".to_string(),428],429vec![430"2000".to_string(),431"2000".to_string(),432"2000".to_string(),433"2000".to_string(),434"0022".to_string(),435"/path/to/other/dir".to_string(),436],437];438439let result = process_ugid_map(input_vec).unwrap();440assert_eq!(result.len(), 2);441assert_eq!(442result,443vec![444PermissionData {445guest_uid: 1000,446guest_gid: 1000,447host_uid: 1000,448host_gid: 1000,449umask: 22,450perm_path: "/path/to/dir".to_string(),451},452PermissionData {453guest_uid: 2000,454guest_gid: 2000,455host_uid: 2000,456host_gid: 2000,457umask: 22,458perm_path: "/path/to/other/dir".to_string(),459},460]461);462}463464#[test]465fn test_deserialize_ugid_map_invalid_format() {466let input_string = "\"1000 1000 1000 0022 /path/to/dir\""; // Missing one element467468// Create a Deserializer from the input string469let mut deserializer = serde_json::Deserializer::from_str(input_string);470let result = deserialize_ugid_map(&mut deserializer);471assert!(result.is_err());472}473474#[test]475fn test_deserialize_ugid_map_invalid_guest_uid() {476let input_string = "\"invalid 1000 1000 1000 0022 /path/to/dir\""; // Invalid guest-UID477478// Create a Deserializer from the input string479let mut deserializer = serde_json::Deserializer::from_str(input_string);480let result = deserialize_ugid_map(&mut deserializer);481assert!(result.is_err());482}483484#[test]485fn test_deserialize_ugid_map_invalid_guest_gid() {486let input_string = "\"1000 invalid 1000 1000 0022 /path/to/dir\""; // Invalid guest-GID487488// Create a Deserializer from the input string489let mut deserializer = serde_json::Deserializer::from_str(input_string);490let result = deserialize_ugid_map(&mut deserializer);491assert!(result.is_err());492}493494#[test]495fn test_deserialize_ugid_map_invalid_umask() {496let input_string = "\"1000 1000 1000 1000 invalid /path/to/dir\""; // Invalid umask497498// Create a Deserializer from the input string499let mut deserializer = serde_json::Deserializer::from_str(input_string);500let result = deserialize_ugid_map(&mut deserializer);501assert!(result.is_err());502}503504#[test]505fn test_deserialize_ugid_map_invalid_host_uid() {506let input_string = "\"1000 1000 invalid 1000 0022 /path/to/dir\""; // Invalid host-UID507508// Create a Deserializer from the input string509let mut deserializer = serde_json::Deserializer::from_str(input_string);510let result = deserialize_ugid_map(&mut deserializer);511assert!(result.is_err());512}513514#[test]515fn test_deserialize_ugid_map_invalid_host_gid() {516let input_string = "\"1000 1000 1000 invalid 0022 /path/to/dir\""; // Invalid host-UID517518// Create a Deserializer from the input string519let mut deserializer = serde_json::Deserializer::from_str(input_string);520let result = deserialize_ugid_map(&mut deserializer);521assert!(result.is_err());522}523}524525526