1 // Copyright 2024 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 //! Provides a function to lanunches a process of creating ext2 filesystem on memory region
6 //! asynchronously for pmem-ext2 device.
7 //!
8 //! The ext2 file system is created in the memory area for pmem by the following three processes:
9 //! (a). The main process
10 //! (b). ext2 process launched by the `launch()` below.
11 //! (c). The virtio-pmem process
12 //!
13 //! By executing mkfs in the multiple processes, mkfs won't block other initalization steps. Also,
14 //! we can use different seccopm poliy for (b) and (c).
15 //!
16 //! The overall workflow is like the followings:
17 //! 1. At (a): `launch()` is called from (a)
18 //! 2. At (a): (b) is foked from (a) in `launch()`
19 //! 3. At (b): The given directory is traversed and metadata is constructed.
20 //! 4. At (b): File descriptors are sent to (a) with `VmMemoryRequest::MmapAndRegisterMemory`.
21 //! 5. At (a): mmap() for the file descriptors are called. The reply is sent to (b).
22 //! 6. At (b): memory slot number is sent to (c).
23 //! 7. At (c): device activation finished.
24
25 use std::path::Path;
26
27 use anyhow::Context;
28 use anyhow::Result;
29 use base::error;
30 use base::AsRawDescriptor;
31 use base::Pid;
32 use base::SharedMemory;
33 use base::Tube;
34 use jail::create_base_minijail;
35 use jail::create_sandbox_minijail;
36 use jail::fork_process;
37 use jail::JailConfig;
38 use jail::RunAsUser;
39 use jail::SandboxConfig;
40 use vm_control::api::VmMemoryClient;
41 use vm_control::VmMemoryFileMapping;
42 use vm_memory::GuestAddress;
43
44 /// Starts a process to create an ext2 filesystem on a given shared memory region.
launch( mapping_address: GuestAddress, vm_memory_client: VmMemoryClient, device_tube: Tube, path: &Path, ugid: &(Option<u32>, Option<u32>), ugid_map: (&str, &str), mut builder: ext2::Builder, jail_config: &Option<JailConfig>, ) -> Result<Pid>45 pub fn launch(
46 mapping_address: GuestAddress,
47 vm_memory_client: VmMemoryClient,
48 device_tube: Tube, // Connects to a virtio device to send a memory slot number.
49 path: &Path,
50 ugid: &(Option<u32>, Option<u32>),
51 ugid_map: (&str, &str),
52 mut builder: ext2::Builder,
53 jail_config: &Option<JailConfig>,
54 ) -> Result<Pid> {
55 let max_open_files = base::linux::max_open_files()
56 .context("failed to get max number of open files")?
57 .rlim_max;
58
59 let jail = if let Some(jail_config) = jail_config {
60 let mut config = SandboxConfig::new(jail_config, "virtual_ext2");
61 config.limit_caps = false;
62 config.ugid_map = Some(ugid_map);
63 // We want bind mounts from the parent namespaces to propagate into the mkfs's
64 // namespace.
65 config.remount_mode = Some(libc::MS_SLAVE);
66 config.run_as = match *ugid {
67 (None, None) => RunAsUser::Unspecified,
68 (uid_opt, gid_opt) => RunAsUser::Specified(uid_opt.unwrap_or(0), gid_opt.unwrap_or(0)),
69 };
70 create_sandbox_minijail(path, max_open_files, &config)?
71 } else {
72 create_base_minijail(path, max_open_files)?
73 };
74
75 // Use "/" in the new mount namespace as the root for mkfs.
76 builder.root_dir = Some(std::path::PathBuf::from("/"));
77
78 let shm = SharedMemory::new("pmem_ext2_shm", builder.size as u64)
79 .context("failed to create shared memory")?;
80 let mut keep_rds = vec![
81 shm.as_raw_descriptor(),
82 vm_memory_client.as_raw_descriptor(),
83 device_tube.as_raw_descriptor(),
84 ];
85 base::syslog::push_descriptors(&mut keep_rds);
86
87 let child_process = fork_process(jail, keep_rds, Some(String::from("mkfs process")), || {
88 if let Err(e) = mkfs_callback(vm_memory_client, mapping_address, device_tube, builder, shm)
89 {
90 error!("failed to create file system: {:#}", e);
91 // SAFETY: exit() is trivially safe.
92 unsafe { libc::exit(1) };
93 }
94 })
95 .context("failed to fork a process for mkfs")?;
96 Ok(child_process.pid)
97 }
98
99 /// A callback to create a ext2 file system on `shm`.
100 /// This is supposed to be run in a jailed child process so operations are sandboxed and limited.
mkfs_callback( mem_client: VmMemoryClient, mapping_address: GuestAddress, device_tube: Tube, builder: ext2::Builder, shm: SharedMemory, ) -> Result<()>101 fn mkfs_callback(
102 mem_client: VmMemoryClient,
103 mapping_address: GuestAddress,
104 device_tube: Tube, // Connects to a virtio device to send a memory slot number.
105 builder: ext2::Builder,
106 shm: SharedMemory,
107 ) -> Result<()> {
108 let file_mappings = builder
109 .build_on_shm(&shm)
110 .context("failed to build memory region")?
111 .build_mmap_info()
112 .context("failed to build ext2")?
113 .mapping_info;
114
115 let file_mapping_info: Vec<_> = file_mappings
116 .into_iter()
117 .map(|info| VmMemoryFileMapping {
118 file: info.file,
119 length: info.length,
120 mem_offset: info.mem_offset,
121 file_offset: info.file_offset as u64,
122 })
123 .collect();
124
125 let slot = mem_client
126 .mmap_and_register_memory(mapping_address, shm, file_mapping_info)
127 .context("failed to request mmaping and registering memory")?;
128 device_tube
129 .send(&slot)
130 .context("failed to send VmMemoryRequest::RegisterMemory")?;
131 Ok(())
132 }
133