blob: 2dbb6e2cd73c3e66352653241bbd01c3847af07b [file] [log] [blame]
David Brazdilafc9a9e2023-01-12 16:08:10 +00001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Implementation of the AIDL interface of the VirtualizationService.
16
17use crate::{get_calling_pid, get_calling_uid};
David Brazdil33a31022023-01-12 16:55:16 +000018use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
Alice Wangc2fec932023-02-23 16:24:02 +000019use crate::rkpvm::request_certificate;
David Brazdilafc9a9e2023-01-12 16:08:10 +000020use android_os_permissions_aidl::aidl::android::os::IPermissionController;
Alice Wangc2fec932023-02-23 16:24:02 +000021use android_system_virtualizationservice::{
Inseob Kim53d0b212023-07-20 16:58:37 +090022 aidl::android::system::virtualizationservice::AssignableDevice::AssignableDevice,
Alice Wangc2fec932023-02-23 16:24:02 +000023 aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo,
24 binder::ParcelFileDescriptor,
25};
David Brazdilafc9a9e2023-01-12 16:08:10 +000026use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
27 AtomVmBooted::AtomVmBooted,
28 AtomVmCreationRequested::AtomVmCreationRequested,
29 AtomVmExited::AtomVmExited,
30 IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
31 IVirtualizationServiceInternal::IVirtualizationServiceInternal,
32};
33use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
Alice Wangd1b11a02023-04-18 12:30:20 +000034use anyhow::{anyhow, ensure, Context, Result};
David Brazdilafc9a9e2023-01-12 16:08:10 +000035use binder::{self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong};
Inseob Kim1ca0f652023-07-20 17:18:12 +090036use lazy_static::lazy_static;
David Brazdilafc9a9e2023-01-12 16:08:10 +000037use libc::VMADDR_CID_HOST;
38use log::{error, info, warn};
39use rustutils::system_properties;
Inseob Kim1ca0f652023-07-20 17:18:12 +090040use std::collections::{HashMap, HashSet};
41use std::fs::{canonicalize, create_dir, remove_dir_all, set_permissions, File, Permissions};
David Brazdilafc9a9e2023-01-12 16:08:10 +000042use std::io::{Read, Write};
Inseob Kim1ca0f652023-07-20 17:18:12 +090043use std::os::fd::FromRawFd;
David Brazdilafc9a9e2023-01-12 16:08:10 +000044use std::os::unix::fs::PermissionsExt;
45use std::os::unix::raw::{pid_t, uid_t};
Inseob Kim1ca0f652023-07-20 17:18:12 +090046use std::path::{Path, PathBuf};
David Brazdilafc9a9e2023-01-12 16:08:10 +000047use std::sync::{Arc, Mutex, Weak};
48use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
49use vsock::{VsockListener, VsockStream};
Inseob Kim1ca0f652023-07-20 17:18:12 +090050use nix::fcntl::OFlag;
51use nix::unistd::{chown, pipe2, Uid};
David Brazdilafc9a9e2023-01-12 16:08:10 +000052
53/// The unique ID of a VM used (together with a port number) for vsock communication.
54pub type Cid = u32;
55
56pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
57
58/// Directory in which to write disk image files used while running VMs.
59pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
60
61/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
62/// are reserved for the host or other usage.
63const GUEST_CID_MIN: Cid = 2048;
64const GUEST_CID_MAX: Cid = 65535;
65
66const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
67
68const CHUNK_RECV_MAX_LEN: usize = 1024;
69
70fn is_valid_guest_cid(cid: Cid) -> bool {
71 (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
72}
73
74/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
75/// singleton servers, like tombstone receiver.
76#[derive(Debug, Default)]
77pub struct VirtualizationServiceInternal {
78 state: Arc<Mutex<GlobalState>>,
79}
80
81impl VirtualizationServiceInternal {
82 pub fn init() -> VirtualizationServiceInternal {
83 let service = VirtualizationServiceInternal::default();
84
85 std::thread::spawn(|| {
86 if let Err(e) = handle_stream_connection_tombstoned() {
87 warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
88 }
89 });
90
91 service
92 }
93}
94
95impl Interface for VirtualizationServiceInternal {}
96
97impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
98 fn removeMemlockRlimit(&self) -> binder::Result<()> {
99 let pid = get_calling_pid();
100 let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
101
Andrew Walbranb58d1b42023-07-07 13:54:49 +0100102 // SAFETY: borrowing the new limit struct only
David Brazdilafc9a9e2023-01-12 16:08:10 +0000103 let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
104
105 match ret {
106 0 => Ok(()),
107 -1 => Err(Status::new_exception_str(
108 ExceptionCode::ILLEGAL_STATE,
109 Some(std::io::Error::last_os_error().to_string()),
110 )),
111 n => Err(Status::new_exception_str(
112 ExceptionCode::ILLEGAL_STATE,
113 Some(format!("Unexpected return value from prlimit(): {n}")),
114 )),
115 }
116 }
117
118 fn allocateGlobalVmContext(
119 &self,
120 requester_debug_pid: i32,
121 ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
122 check_manage_access()?;
123
124 let requester_uid = get_calling_uid();
125 let requester_debug_pid = requester_debug_pid as pid_t;
126 let state = &mut *self.state.lock().unwrap();
127 state.allocate_vm_context(requester_uid, requester_debug_pid).map_err(|e| {
128 Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
129 })
130 }
131
132 fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
133 forward_vm_booted_atom(atom);
134 Ok(())
135 }
136
137 fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
138 forward_vm_creation_atom(atom);
139 Ok(())
140 }
141
142 fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
143 forward_vm_exited_atom(atom);
144 Ok(())
145 }
146
147 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
148 check_debug_access()?;
149
150 let state = &mut *self.state.lock().unwrap();
151 let cids = state
152 .held_contexts
153 .iter()
154 .filter_map(|(_, inst)| Weak::upgrade(inst))
155 .map(|vm| VirtualMachineDebugInfo {
156 cid: vm.cid as i32,
157 temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
158 requesterUid: vm.requester_uid as i32,
Charisee96113f32023-01-26 09:00:42 +0000159 requesterPid: vm.requester_debug_pid,
David Brazdilafc9a9e2023-01-12 16:08:10 +0000160 })
161 .collect();
162 Ok(cids)
163 }
Alice Wangc2fec932023-02-23 16:24:02 +0000164
165 fn requestCertificate(
166 &self,
167 csr: &[u8],
168 instance_img_fd: &ParcelFileDescriptor,
169 ) -> binder::Result<Vec<u8>> {
170 check_manage_access()?;
171 info!("Received csr. Getting certificate...");
172 request_certificate(csr, instance_img_fd).map_err(|e| {
173 error!("Failed to get certificate. Error: {e:?}");
174 Status::new_exception_str(ExceptionCode::SERVICE_SPECIFIC, Some(e.to_string()))
175 })
176 }
Inseob Kim53d0b212023-07-20 16:58:37 +0900177
178 fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
179 check_use_custom_virtual_machine()?;
180
181 // TODO(b/291191362): read VM DTBO to find assignable devices.
182 Ok(vec![AssignableDevice {
183 kind: "eh".to_owned(),
184 node: "/sys/bus/platform/devices/16d00000.eh".to_owned(),
185 }])
186 }
Inseob Kim1ca0f652023-07-20 17:18:12 +0900187
188 fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<ParcelFileDescriptor> {
189 check_use_custom_virtual_machine()?;
190
191 let mut set = HashSet::new();
192 for device in devices.iter() {
193 if !set.insert(device) {
194 return Err(Status::new_exception_str(
195 ExceptionCode::ILLEGAL_ARGUMENT,
196 Some(format!("duplicated device {device}")),
197 ));
198 }
199 bind_device(device)?;
200 }
201
202 // TODO(b/278008182): create a file descriptor containing DTBO for devices.
203 let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC).map_err(|e| {
204 Status::new_exception_str(
205 ExceptionCode::SERVICE_SPECIFIC,
206 Some(format!("can't create fd for DTBO: {e:?}")),
207 )
208 })?;
209 // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
210 let read_fd = unsafe { File::from_raw_fd(raw_read) };
211 // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
212 let _write_fd = unsafe { File::from_raw_fd(raw_write) };
213
214 Ok(ParcelFileDescriptor::new(read_fd))
215 }
216}
217
218lazy_static! {
219 static ref SYSFS_PLATFORM_DEVICES: &'static Path = Path::new("/sys/devices/platform/");
220 static ref VFIO_PLATFORM_DRIVER: &'static Path =
221 Path::new("/sys/bus/platform/drivers/vfio-platform");
222}
223
224fn bind_device(device: &str) -> binder::Result<()> {
225 // Check platform device exists
226 let dev_sysfs_path = canonicalize(device).map_err(|e| {
227 Status::new_exception_str(
228 ExceptionCode::SERVICE_SPECIFIC,
229 Some(format!("can't canonicalize: {e:?}")),
230 )
231 })?;
232 if !dev_sysfs_path.starts_with(*SYSFS_PLATFORM_DEVICES) {
233 return Err(Status::new_exception_str(
234 ExceptionCode::ILLEGAL_ARGUMENT,
235 Some(format!("{device} is not a platform device")),
236 ));
237 }
238
239 // Check platform device is bound to VFIO driver
240 let dev_driver_path = canonicalize(dev_sysfs_path.join("driver")).map_err(|e| {
241 Status::new_exception_str(
242 ExceptionCode::SERVICE_SPECIFIC,
243 Some(format!("can't canonicalize: {e:?}")),
244 )
245 })?;
246 if dev_driver_path != *VFIO_PLATFORM_DRIVER {
247 // TODO(b/278008182): unbind driver and bind to VFIO
248 return Err(Status::new_exception_str(
249 ExceptionCode::UNSUPPORTED_OPERATION,
250 Some("not implemented".to_owned()),
251 ));
252 }
253
254 // already bound to VFIO driver
255 Ok(())
David Brazdilafc9a9e2023-01-12 16:08:10 +0000256}
257
258#[derive(Debug, Default)]
259struct GlobalVmInstance {
260 /// The unique CID assigned to the VM for vsock communication.
261 cid: Cid,
262 /// UID of the client who requested this VM instance.
263 requester_uid: uid_t,
264 /// PID of the client who requested this VM instance.
265 requester_debug_pid: pid_t,
266}
267
268impl GlobalVmInstance {
269 fn get_temp_dir(&self) -> PathBuf {
270 let cid = self.cid;
271 format!("{TEMPORARY_DIRECTORY}/{cid}").into()
272 }
273}
274
275/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
276/// of this struct.
277#[derive(Debug, Default)]
278struct GlobalState {
279 /// VM contexts currently allocated to running VMs. A CID is never recycled as long
280 /// as there is a strong reference held by a GlobalVmContext.
281 held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
282}
283
284impl GlobalState {
285 /// Get the next available CID, or an error if we have run out. The last CID used is stored in
286 /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
287 /// Android is up.
288 fn get_next_available_cid(&mut self) -> Result<Cid> {
289 // Start trying to find a CID from the last used CID + 1. This ensures
290 // that we do not eagerly recycle CIDs. It makes debugging easier but
291 // also means that retrying to allocate a CID, eg. because it is
292 // erroneously occupied by a process, will not recycle the same CID.
293 let last_cid_prop =
294 system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
295 Ok(num) => {
296 if is_valid_guest_cid(num) {
297 Some(num)
298 } else {
299 error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
300 None
301 }
302 }
303 Err(_) => {
304 error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
305 None
306 }
307 });
308
309 let first_cid = if let Some(last_cid) = last_cid_prop {
310 if last_cid == GUEST_CID_MAX {
311 GUEST_CID_MIN
312 } else {
313 last_cid + 1
314 }
315 } else {
316 GUEST_CID_MIN
317 };
318
319 let cid = self
320 .find_available_cid(first_cid..=GUEST_CID_MAX)
321 .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
322 .ok_or_else(|| anyhow!("Could not find an available CID."))?;
323
324 system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
325 Ok(cid)
326 }
327
328 fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
329 where
330 I: Iterator<Item = Cid>,
331 {
332 range.find(|cid| !self.held_contexts.contains_key(cid))
333 }
334
335 fn allocate_vm_context(
336 &mut self,
337 requester_uid: uid_t,
338 requester_debug_pid: pid_t,
339 ) -> Result<Strong<dyn IGlobalVmContext>> {
340 // Garbage collect unused VM contexts.
341 self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
342
343 let cid = self.get_next_available_cid()?;
344 let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
345 create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
346
347 self.held_contexts.insert(cid, Arc::downgrade(&instance));
348 let binder = GlobalVmContext { instance, ..Default::default() };
349 Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
350 }
351}
352
353fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
354 if path.as_path().exists() {
355 remove_temporary_dir(path).unwrap_or_else(|e| {
356 warn!("Could not delete temporary directory {:?}: {}", path, e);
357 });
358 }
359 // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
360 // If the chown() fails, this will leave behind an empty directory that will get removed
361 // at the next attempt, or if virtualizationservice is restarted.
362 create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
363 chown(path, Some(Uid::from_raw(requester_uid)), None)
364 .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
365 Ok(())
366}
367
368/// Removes a directory owned by a different user by first changing its owner back
369/// to VirtualizationService.
370pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
Alice Wangd1b11a02023-04-18 12:30:20 +0000371 ensure!(path.as_path().is_dir(), "Path {:?} is not a directory", path);
David Brazdilafc9a9e2023-01-12 16:08:10 +0000372 chown(path, Some(Uid::current()), None)?;
373 set_permissions(path, Permissions::from_mode(0o700))?;
Alice Wangd1b11a02023-04-18 12:30:20 +0000374 remove_dir_all(path)?;
David Brazdilafc9a9e2023-01-12 16:08:10 +0000375 Ok(())
376}
377
378/// Implementation of the AIDL `IGlobalVmContext` interface.
379#[derive(Debug, Default)]
380struct GlobalVmContext {
381 /// Strong reference to the context's instance data structure.
382 instance: Arc<GlobalVmInstance>,
383 /// Keeps our service process running as long as this VM context exists.
384 #[allow(dead_code)]
385 lazy_service_guard: LazyServiceGuard,
386}
387
388impl Interface for GlobalVmContext {}
389
390impl IGlobalVmContext for GlobalVmContext {
391 fn getCid(&self) -> binder::Result<i32> {
392 Ok(self.instance.cid as i32)
393 }
394
395 fn getTemporaryDirectory(&self) -> binder::Result<String> {
396 Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
397 }
398}
399
400fn handle_stream_connection_tombstoned() -> Result<()> {
401 // Should not listen for tombstones on a guest VM's port.
402 assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
403 let listener =
404 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
405 for incoming_stream in listener.incoming() {
406 let mut incoming_stream = match incoming_stream {
407 Err(e) => {
408 warn!("invalid incoming connection: {:?}", e);
409 continue;
410 }
411 Ok(s) => s,
412 };
413 std::thread::spawn(move || {
414 if let Err(e) = handle_tombstone(&mut incoming_stream) {
415 error!("Failed to write tombstone- {:?}", e);
416 }
417 });
418 }
419 Ok(())
420}
421
422fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
423 if let Ok(addr) = stream.peer_addr() {
424 info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
425 }
426 let tb_connection =
427 TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
428 .context("Failed to connect to tombstoned")?;
429 let mut text_output = tb_connection
430 .text_output
431 .as_ref()
432 .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
433 let mut num_bytes_read = 0;
434 loop {
435 let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
436 let n = stream
437 .read(&mut chunk_recv)
438 .context("Failed to read tombstone data from Vsock stream")?;
439 if n == 0 {
440 break;
441 }
442 num_bytes_read += n;
443 text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
444 }
445 info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
446 tb_connection.notify_completion()?;
447 Ok(())
448}
449
450/// Checks whether the caller has a specific permission
451fn check_permission(perm: &str) -> binder::Result<()> {
452 let calling_pid = get_calling_pid();
453 let calling_uid = get_calling_uid();
454 // Root can do anything
455 if calling_uid == 0 {
456 return Ok(());
457 }
458 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
459 binder::get_interface("permission")?;
460 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
461 Ok(())
462 } else {
463 Err(Status::new_exception_str(
464 ExceptionCode::SECURITY,
465 Some(format!("does not have the {} permission", perm)),
466 ))
467 }
468}
469
470/// Check whether the caller of the current Binder method is allowed to call debug methods.
471fn check_debug_access() -> binder::Result<()> {
472 check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
473}
474
475/// Check whether the caller of the current Binder method is allowed to manage VMs
476fn check_manage_access() -> binder::Result<()> {
477 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
478}
Inseob Kim53d0b212023-07-20 16:58:37 +0900479
480/// Check whether the caller of the current Binder method is allowed to use custom VMs
481fn check_use_custom_virtual_machine() -> binder::Result<()> {
482 check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
483}