blob: 5c5a7e43a3aa02d0f72730ea2b5eb4c1d29be814 [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::{
22 aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo,
23 binder::ParcelFileDescriptor,
24};
David Brazdilafc9a9e2023-01-12 16:08:10 +000025use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
26 AtomVmBooted::AtomVmBooted,
27 AtomVmCreationRequested::AtomVmCreationRequested,
28 AtomVmExited::AtomVmExited,
29 IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
30 IVirtualizationServiceInternal::IVirtualizationServiceInternal,
31};
32use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
Alice Wangd1b11a02023-04-18 12:30:20 +000033use anyhow::{anyhow, ensure, Context, Result};
David Brazdilafc9a9e2023-01-12 16:08:10 +000034use binder::{self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong};
35use libc::VMADDR_CID_HOST;
36use log::{error, info, warn};
37use rustutils::system_properties;
38use std::collections::HashMap;
Alice Wangd1b11a02023-04-18 12:30:20 +000039use std::fs::{create_dir, remove_dir_all, set_permissions, Permissions};
David Brazdilafc9a9e2023-01-12 16:08:10 +000040use std::io::{Read, Write};
41use std::os::unix::fs::PermissionsExt;
42use std::os::unix::raw::{pid_t, uid_t};
43use std::path::PathBuf;
44use std::sync::{Arc, Mutex, Weak};
45use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
46use vsock::{VsockListener, VsockStream};
47use nix::unistd::{chown, Uid};
48
49/// The unique ID of a VM used (together with a port number) for vsock communication.
50pub type Cid = u32;
51
52pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
53
54/// Directory in which to write disk image files used while running VMs.
55pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
56
57/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
58/// are reserved for the host or other usage.
59const GUEST_CID_MIN: Cid = 2048;
60const GUEST_CID_MAX: Cid = 65535;
61
62const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
63
64const CHUNK_RECV_MAX_LEN: usize = 1024;
65
66fn is_valid_guest_cid(cid: Cid) -> bool {
67 (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
68}
69
70/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
71/// singleton servers, like tombstone receiver.
72#[derive(Debug, Default)]
73pub struct VirtualizationServiceInternal {
74 state: Arc<Mutex<GlobalState>>,
75}
76
77impl VirtualizationServiceInternal {
78 pub fn init() -> VirtualizationServiceInternal {
79 let service = VirtualizationServiceInternal::default();
80
81 std::thread::spawn(|| {
82 if let Err(e) = handle_stream_connection_tombstoned() {
83 warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
84 }
85 });
86
87 service
88 }
89}
90
91impl Interface for VirtualizationServiceInternal {}
92
93impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
94 fn removeMemlockRlimit(&self) -> binder::Result<()> {
95 let pid = get_calling_pid();
96 let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
97
98 // SAFETY - borrowing the new limit struct only
99 let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
100
101 match ret {
102 0 => Ok(()),
103 -1 => Err(Status::new_exception_str(
104 ExceptionCode::ILLEGAL_STATE,
105 Some(std::io::Error::last_os_error().to_string()),
106 )),
107 n => Err(Status::new_exception_str(
108 ExceptionCode::ILLEGAL_STATE,
109 Some(format!("Unexpected return value from prlimit(): {n}")),
110 )),
111 }
112 }
113
114 fn allocateGlobalVmContext(
115 &self,
116 requester_debug_pid: i32,
117 ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
118 check_manage_access()?;
119
120 let requester_uid = get_calling_uid();
121 let requester_debug_pid = requester_debug_pid as pid_t;
122 let state = &mut *self.state.lock().unwrap();
123 state.allocate_vm_context(requester_uid, requester_debug_pid).map_err(|e| {
124 Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
125 })
126 }
127
128 fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
129 forward_vm_booted_atom(atom);
130 Ok(())
131 }
132
133 fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
134 forward_vm_creation_atom(atom);
135 Ok(())
136 }
137
138 fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
139 forward_vm_exited_atom(atom);
140 Ok(())
141 }
142
143 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
144 check_debug_access()?;
145
146 let state = &mut *self.state.lock().unwrap();
147 let cids = state
148 .held_contexts
149 .iter()
150 .filter_map(|(_, inst)| Weak::upgrade(inst))
151 .map(|vm| VirtualMachineDebugInfo {
152 cid: vm.cid as i32,
153 temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
154 requesterUid: vm.requester_uid as i32,
Charisee96113f32023-01-26 09:00:42 +0000155 requesterPid: vm.requester_debug_pid,
David Brazdilafc9a9e2023-01-12 16:08:10 +0000156 })
157 .collect();
158 Ok(cids)
159 }
Alice Wangc2fec932023-02-23 16:24:02 +0000160
161 fn requestCertificate(
162 &self,
163 csr: &[u8],
164 instance_img_fd: &ParcelFileDescriptor,
165 ) -> binder::Result<Vec<u8>> {
166 check_manage_access()?;
167 info!("Received csr. Getting certificate...");
168 request_certificate(csr, instance_img_fd).map_err(|e| {
169 error!("Failed to get certificate. Error: {e:?}");
170 Status::new_exception_str(ExceptionCode::SERVICE_SPECIFIC, Some(e.to_string()))
171 })
172 }
David Brazdilafc9a9e2023-01-12 16:08:10 +0000173}
174
175#[derive(Debug, Default)]
176struct GlobalVmInstance {
177 /// The unique CID assigned to the VM for vsock communication.
178 cid: Cid,
179 /// UID of the client who requested this VM instance.
180 requester_uid: uid_t,
181 /// PID of the client who requested this VM instance.
182 requester_debug_pid: pid_t,
183}
184
185impl GlobalVmInstance {
186 fn get_temp_dir(&self) -> PathBuf {
187 let cid = self.cid;
188 format!("{TEMPORARY_DIRECTORY}/{cid}").into()
189 }
190}
191
192/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
193/// of this struct.
194#[derive(Debug, Default)]
195struct GlobalState {
196 /// VM contexts currently allocated to running VMs. A CID is never recycled as long
197 /// as there is a strong reference held by a GlobalVmContext.
198 held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
199}
200
201impl GlobalState {
202 /// Get the next available CID, or an error if we have run out. The last CID used is stored in
203 /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
204 /// Android is up.
205 fn get_next_available_cid(&mut self) -> Result<Cid> {
206 // Start trying to find a CID from the last used CID + 1. This ensures
207 // that we do not eagerly recycle CIDs. It makes debugging easier but
208 // also means that retrying to allocate a CID, eg. because it is
209 // erroneously occupied by a process, will not recycle the same CID.
210 let last_cid_prop =
211 system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
212 Ok(num) => {
213 if is_valid_guest_cid(num) {
214 Some(num)
215 } else {
216 error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
217 None
218 }
219 }
220 Err(_) => {
221 error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
222 None
223 }
224 });
225
226 let first_cid = if let Some(last_cid) = last_cid_prop {
227 if last_cid == GUEST_CID_MAX {
228 GUEST_CID_MIN
229 } else {
230 last_cid + 1
231 }
232 } else {
233 GUEST_CID_MIN
234 };
235
236 let cid = self
237 .find_available_cid(first_cid..=GUEST_CID_MAX)
238 .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
239 .ok_or_else(|| anyhow!("Could not find an available CID."))?;
240
241 system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
242 Ok(cid)
243 }
244
245 fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
246 where
247 I: Iterator<Item = Cid>,
248 {
249 range.find(|cid| !self.held_contexts.contains_key(cid))
250 }
251
252 fn allocate_vm_context(
253 &mut self,
254 requester_uid: uid_t,
255 requester_debug_pid: pid_t,
256 ) -> Result<Strong<dyn IGlobalVmContext>> {
257 // Garbage collect unused VM contexts.
258 self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
259
260 let cid = self.get_next_available_cid()?;
261 let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
262 create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
263
264 self.held_contexts.insert(cid, Arc::downgrade(&instance));
265 let binder = GlobalVmContext { instance, ..Default::default() };
266 Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
267 }
268}
269
270fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
271 if path.as_path().exists() {
272 remove_temporary_dir(path).unwrap_or_else(|e| {
273 warn!("Could not delete temporary directory {:?}: {}", path, e);
274 });
275 }
276 // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
277 // If the chown() fails, this will leave behind an empty directory that will get removed
278 // at the next attempt, or if virtualizationservice is restarted.
279 create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
280 chown(path, Some(Uid::from_raw(requester_uid)), None)
281 .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
282 Ok(())
283}
284
285/// Removes a directory owned by a different user by first changing its owner back
286/// to VirtualizationService.
287pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
Alice Wangd1b11a02023-04-18 12:30:20 +0000288 ensure!(path.as_path().is_dir(), "Path {:?} is not a directory", path);
David Brazdilafc9a9e2023-01-12 16:08:10 +0000289 chown(path, Some(Uid::current()), None)?;
290 set_permissions(path, Permissions::from_mode(0o700))?;
Alice Wangd1b11a02023-04-18 12:30:20 +0000291 remove_dir_all(path)?;
David Brazdilafc9a9e2023-01-12 16:08:10 +0000292 Ok(())
293}
294
295/// Implementation of the AIDL `IGlobalVmContext` interface.
296#[derive(Debug, Default)]
297struct GlobalVmContext {
298 /// Strong reference to the context's instance data structure.
299 instance: Arc<GlobalVmInstance>,
300 /// Keeps our service process running as long as this VM context exists.
301 #[allow(dead_code)]
302 lazy_service_guard: LazyServiceGuard,
303}
304
305impl Interface for GlobalVmContext {}
306
307impl IGlobalVmContext for GlobalVmContext {
308 fn getCid(&self) -> binder::Result<i32> {
309 Ok(self.instance.cid as i32)
310 }
311
312 fn getTemporaryDirectory(&self) -> binder::Result<String> {
313 Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
314 }
315}
316
317fn handle_stream_connection_tombstoned() -> Result<()> {
318 // Should not listen for tombstones on a guest VM's port.
319 assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
320 let listener =
321 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
322 for incoming_stream in listener.incoming() {
323 let mut incoming_stream = match incoming_stream {
324 Err(e) => {
325 warn!("invalid incoming connection: {:?}", e);
326 continue;
327 }
328 Ok(s) => s,
329 };
330 std::thread::spawn(move || {
331 if let Err(e) = handle_tombstone(&mut incoming_stream) {
332 error!("Failed to write tombstone- {:?}", e);
333 }
334 });
335 }
336 Ok(())
337}
338
339fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
340 if let Ok(addr) = stream.peer_addr() {
341 info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
342 }
343 let tb_connection =
344 TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
345 .context("Failed to connect to tombstoned")?;
346 let mut text_output = tb_connection
347 .text_output
348 .as_ref()
349 .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
350 let mut num_bytes_read = 0;
351 loop {
352 let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
353 let n = stream
354 .read(&mut chunk_recv)
355 .context("Failed to read tombstone data from Vsock stream")?;
356 if n == 0 {
357 break;
358 }
359 num_bytes_read += n;
360 text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
361 }
362 info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
363 tb_connection.notify_completion()?;
364 Ok(())
365}
366
367/// Checks whether the caller has a specific permission
368fn check_permission(perm: &str) -> binder::Result<()> {
369 let calling_pid = get_calling_pid();
370 let calling_uid = get_calling_uid();
371 // Root can do anything
372 if calling_uid == 0 {
373 return Ok(());
374 }
375 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
376 binder::get_interface("permission")?;
377 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
378 Ok(())
379 } else {
380 Err(Status::new_exception_str(
381 ExceptionCode::SECURITY,
382 Some(format!("does not have the {} permission", perm)),
383 ))
384 }
385}
386
387/// Check whether the caller of the current Binder method is allowed to call debug methods.
388fn check_debug_access() -> binder::Result<()> {
389 check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
390}
391
392/// Check whether the caller of the current Binder method is allowed to manage VMs
393fn check_manage_access() -> binder::Result<()> {
394 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
395}