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