1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <aidl/android/system/virtualizationcommon/DeathReason.h>
17 #include <aidl/android/system/virtualizationcommon/ErrorCode.h>
18 #include <aidl/android/system/virtualizationservice/BnVirtualMachineCallback.h>
19 #include <aidl/android/system/virtualizationservice/IVirtualMachine.h>
20 #include <aidl/android/system/virtualizationservice/IVirtualMachineCallback.h>
21 #include <aidl/android/system/virtualizationservice/IVirtualizationService.h>
22 #include <aidl/android/system/virtualizationservice/VirtualMachineConfig.h>
23 #include <aidl/android/system/virtualizationservice/VirtualMachineState.h>
24 #include <aidl/com/android/microdroid/testservice/ITestService.h>
25 #include <android-base/errors.h>
26 #include <android-base/file.h>
27 #include <android-base/result.h>
28 #include <android-base/unique_fd.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 
32 #include <binder_rpc_unstable.hpp>
33 #include <chrono>
34 #include <condition_variable>
35 #include <cstddef>
36 #include <memory>
37 #include <mutex>
38 #include <thread>
39 
40 using namespace std::chrono_literals;
41 
42 using android::base::ErrnoError;
43 using android::base::Error;
44 using android::base::Pipe;
45 using android::base::Result;
46 using android::base::Socketpair;
47 using android::base::unique_fd;
48 
49 using ndk::ScopedAStatus;
50 using ndk::ScopedFileDescriptor;
51 using ndk::SharedRefBase;
52 using ndk::SpAIBinder;
53 
54 using aidl::android::system::virtualizationcommon::DeathReason;
55 using aidl::android::system::virtualizationcommon::ErrorCode;
56 using aidl::android::system::virtualizationservice::BnVirtualMachineCallback;
57 using aidl::android::system::virtualizationservice::IVirtualizationService;
58 using aidl::android::system::virtualizationservice::IVirtualMachine;
59 using aidl::android::system::virtualizationservice::PartitionType;
60 using aidl::android::system::virtualizationservice::toString;
61 using aidl::android::system::virtualizationservice::VirtualMachineAppConfig;
62 using aidl::android::system::virtualizationservice::VirtualMachineConfig;
63 using aidl::android::system::virtualizationservice::VirtualMachinePayloadConfig;
64 using aidl::android::system::virtualizationservice::VirtualMachineState;
65 
66 using aidl::com::android::microdroid::testservice::ITestService;
67 
68 // This program demonstrates a way to run a VM and do something in the VM using AVF in the C++
69 // language. Instructions for building and running this demo can be found in `README.md` in this
70 // directory.
71 
72 //--------------------------------------------------------------------------------------------------
73 // Step 1: connect to IVirtualizationService
74 //--------------------------------------------------------------------------------------------------
75 static constexpr const char VIRTMGR_PATH[] = "/apex/com.android.virt/bin/virtmgr";
76 static constexpr size_t VIRTMGR_THREADS = 2;
77 
78 // Start IVirtualizationService instance and get FD for the unix domain socket that is connected to
79 // the service. The returned FD should be kept open until the service is no longer needed.
get_service_fd()80 Result<unique_fd> get_service_fd() {
81     unique_fd server_fd, client_fd;
82     if (!Socketpair(SOCK_STREAM, &server_fd, &client_fd)) {
83         return ErrnoError() << "Failed to create socketpair";
84     }
85 
86     unique_fd wait_fd, ready_fd;
87     if (!Pipe(&wait_fd, &ready_fd, 0)) {
88         return ErrnoError() << "Failed to create pipe";
89     }
90 
91     if (fork() == 0) {
92         client_fd.reset();
93         wait_fd.reset();
94 
95         auto server_fd_str = std::to_string(server_fd.get());
96         auto ready_fd_str = std::to_string(ready_fd.get());
97 
98         if (execl(VIRTMGR_PATH, VIRTMGR_PATH, "--rpc-server-fd", server_fd_str.c_str(),
99                   "--ready-fd", ready_fd_str.c_str(), nullptr) == -1) {
100             return ErrnoError() << "Failed to execute virtmgr";
101         }
102     }
103 
104     server_fd.reset();
105     ready_fd.reset();
106 
107     char buf;
108     if (read(wait_fd.get(), &buf, sizeof(buf)) < 0) {
109         return ErrnoError() << "Failed to wait for VirtualizationService to be ready";
110     }
111 
112     return client_fd;
113 }
114 
115 // Establish a binder communication channel over the unix domain socket and returns the remote
116 // IVirtualizationService.
connect_service(int fd)117 Result<std::shared_ptr<IVirtualizationService>> connect_service(int fd) {
118     std::unique_ptr<ARpcSession, decltype(&ARpcSession_free)> session(ARpcSession_new(),
119                                                                       &ARpcSession_free);
120     ARpcSession_setFileDescriptorTransportMode(session.get(),
121                                                ARpcSession_FileDescriptorTransportMode::Unix);
122     ARpcSession_setMaxIncomingThreads(session.get(), VIRTMGR_THREADS);
123     ARpcSession_setMaxOutgoingConnections(session.get(), VIRTMGR_THREADS);
124     AIBinder* binder = ARpcSession_setupUnixDomainBootstrapClient(session.get(), fd);
125     if (binder == nullptr) {
126         return Error() << "Failed to connect to VirtualizationService";
127     }
128     return IVirtualizationService::fromBinder(SpAIBinder{binder});
129 }
130 
131 //--------------------------------------------------------------------------------------------------
132 // Step 2: construct VirtualMachineAppConfig
133 //--------------------------------------------------------------------------------------------------
134 
135 // Utility function for opening a file at a given path and wrap the resulting FD in
136 // ScopedFileDescriptor so that it can be passed to the service.
open_file(const std::string & path,int flags)137 Result<ScopedFileDescriptor> open_file(const std::string& path, int flags) {
138     int fd = open(path.c_str(), flags, S_IWUSR);
139     if (fd == -1) {
140         return ErrnoError() << "Failed to open " << path;
141     }
142     return ScopedFileDescriptor(fd);
143 }
144 
145 // Create or update idsig file for the given APK file. The idsig is essentially a hashtree of the
146 // APK file's content
create_or_update_idsig_file(IVirtualizationService & service,const std::string & work_dir,ScopedFileDescriptor & main_apk)147 Result<ScopedFileDescriptor> create_or_update_idsig_file(IVirtualizationService& service,
148                                                          const std::string& work_dir,
149                                                          ScopedFileDescriptor& main_apk) {
150     std::string path = work_dir + "/apk.idsig";
151     ScopedFileDescriptor idsig = OR_RETURN(open_file(path, O_CREAT | O_RDWR));
152     ScopedAStatus ret = service.createOrUpdateIdsigFile(main_apk, idsig);
153     if (!ret.isOk()) {
154         return Error() << "Failed to create or update idsig file: " << path;
155     }
156     return idsig;
157 }
158 
159 // Get or create the instance disk image file, if it doesn't exist. The VM will fill this disk with
160 // its own identity information in an encrypted form.
create_instance_image_file_if_needed(IVirtualizationService & service,const std::string & work_dir)161 Result<ScopedFileDescriptor> create_instance_image_file_if_needed(IVirtualizationService& service,
162                                                                   const std::string& work_dir) {
163     std::string path = work_dir + "/instance.img";
164 
165     // If instance.img already exists, use it.
166     if (access(path.c_str(), F_OK) == 0) {
167         return open_file(path, O_RDWR);
168     }
169 
170     // If not, create a new one.
171     ScopedFileDescriptor instance = OR_RETURN(open_file(path, O_CREAT | O_RDWR));
172     long size = 10 * 1024 * 1024; // 10MB, but could be smaller.
173     ScopedAStatus ret =
174             service.initializeWritablePartition(instance, size, PartitionType::ANDROID_VM_INSTANCE);
175     if (!ret.isOk()) {
176         return Error() << "Failed to create instance disk image: " << path;
177     }
178     return instance;
179 }
180 
181 // Construct VirtualMachineAppConfig for a Microdroid-based VM named `vm_name` that executes a
182 // shared library named `paylaod_binary_name` in the apk `main_apk_path`.
create_vm_config(IVirtualizationService & service,const std::string & work_dir,const std::string & vm_name,const std::string & main_apk_path,const std::string & payload_binary_name,bool debuggable,bool protected_vm,int32_t memory_mib)183 Result<VirtualMachineAppConfig> create_vm_config(
184         IVirtualizationService& service, const std::string& work_dir, const std::string& vm_name,
185         const std::string& main_apk_path, const std::string& payload_binary_name, bool debuggable,
186         bool protected_vm, int32_t memory_mib) {
187     ScopedFileDescriptor main_apk = OR_RETURN(open_file(main_apk_path, O_RDONLY));
188     ScopedFileDescriptor idsig =
189             OR_RETURN(create_or_update_idsig_file(service, work_dir, main_apk));
190     ScopedFileDescriptor instance =
191             OR_RETURN(create_instance_image_file_if_needed(service, work_dir));
192 
193     // There are two ways to specify the payload. The simpler way is by specifying the name of the
194     // payload binary as shown below. The other way (which is allowed only to system-level VMs) is
195     // by passing the path to the JSON file in the main APK which has detailed specification about
196     // what to load in Microdroid. See packages/modules/Virtualization/compos/apk/assets/*.json as
197     // examples.
198     VirtualMachinePayloadConfig payload;
199     payload.payloadBinaryName = payload_binary_name;
200 
201     VirtualMachineAppConfig app_config;
202     app_config.name = vm_name;
203     app_config.apk = std::move(main_apk);
204     app_config.idsig = std::move(idsig);
205     app_config.instanceImage = std::move(instance);
206     app_config.payload = std::move(payload);
207     if (debuggable) {
208         app_config.debugLevel = VirtualMachineAppConfig::DebugLevel::FULL;
209     }
210     app_config.protectedVm = protected_vm;
211     app_config.memoryMib = memory_mib;
212 
213     return app_config;
214 }
215 
216 //--------------------------------------------------------------------------------------------------
217 // Step 3: create a VM and start it
218 //--------------------------------------------------------------------------------------------------
219 
220 // Create a virtual machine with the config, but doesn't start it yet.
create_virtual_machine(IVirtualizationService & service,VirtualMachineAppConfig & app_config)221 Result<std::shared_ptr<IVirtualMachine>> create_virtual_machine(
222         IVirtualizationService& service, VirtualMachineAppConfig& app_config) {
223     std::shared_ptr<IVirtualMachine> vm;
224 
225     VirtualMachineConfig config = std::move(app_config);
226     ScopedFileDescriptor console_out_fd(fcntl(fileno(stdout), F_DUPFD_CLOEXEC));
227     ScopedFileDescriptor console_in_fd(fcntl(fileno(stdin), F_DUPFD_CLOEXEC));
228     ScopedFileDescriptor log_fd(fcntl(fileno(stdout), F_DUPFD_CLOEXEC));
229     ScopedFileDescriptor dump_dt_fd(-1);
230 
231     ScopedAStatus ret =
232             service.createVm(config, console_out_fd, console_in_fd, log_fd, dump_dt_fd, &vm);
233     if (!ret.isOk()) {
234         return Error() << "Failed to create VM";
235     }
236     return vm;
237 }
238 
239 // When a VM lifecycle changes, a corresponding method in this class is called. This also provides
240 // methods for blocking the current thread until the VM reaches a specific state.
241 class Callback : public BnVirtualMachineCallback {
242 public:
Callback(const std::shared_ptr<IVirtualMachine> & vm)243     Callback(const std::shared_ptr<IVirtualMachine>& vm) : mVm(vm) {}
244 
onPayloadStarted(int32_t)245     ScopedAStatus onPayloadStarted(int32_t) {
246         std::unique_lock lock(mMutex);
247         mCv.notify_all();
248         return ScopedAStatus::ok();
249     }
250 
onPayloadReady(int32_t)251     ScopedAStatus onPayloadReady(int32_t) {
252         std::unique_lock lock(mMutex);
253         mCv.notify_all();
254         return ScopedAStatus::ok();
255     }
256 
onPayloadFinished(int32_t,int32_t)257     ScopedAStatus onPayloadFinished(int32_t, int32_t) {
258         std::unique_lock lock(mMutex);
259         mCv.notify_all();
260         return ScopedAStatus::ok();
261     }
262 
onError(int32_t,ErrorCode,const std::string &)263     ScopedAStatus onError(int32_t, ErrorCode, const std::string&) {
264         std::unique_lock lock(mMutex);
265         mCv.notify_all();
266         return ScopedAStatus::ok();
267     }
268 
onDied(int32_t,DeathReason)269     ScopedAStatus onDied(int32_t, DeathReason) {
270         std::unique_lock lock(mMutex);
271         mCv.notify_all();
272         return ScopedAStatus::ok();
273     }
274 
wait_for_state(VirtualMachineState state)275     Result<void> wait_for_state(VirtualMachineState state) {
276         std::unique_lock lock(mMutex);
277         mCv.wait_for(lock, 5s, [this, &state] {
278             auto cur_state = get_vm_state();
279             return cur_state.ok() && *cur_state == state;
280         });
281         auto cur_state = get_vm_state();
282         if (cur_state.ok()) {
283             if (*cur_state == state) {
284                 return {};
285             } else {
286                 return Error() << "Timeout waiting for state becomes " << toString(state);
287             }
288         }
289         return cur_state.error();
290     }
291 
292 private:
293     std::shared_ptr<IVirtualMachine> mVm;
294     std::condition_variable mCv;
295     std::mutex mMutex;
296 
get_vm_state()297     Result<VirtualMachineState> get_vm_state() {
298         VirtualMachineState state;
299         ScopedAStatus ret = mVm->getState(&state);
300         if (!ret.isOk()) {
301             return Error() << "Failed to get state of virtual machine";
302         }
303         return state;
304     }
305 };
306 
307 // Start (i.e. boot) the virtual machine and return Callback monitoring the lifecycle event of the
308 // VM.
start_virtual_machine(std::shared_ptr<IVirtualMachine> vm)309 Result<std::shared_ptr<Callback>> start_virtual_machine(std::shared_ptr<IVirtualMachine> vm) {
310     std::shared_ptr<Callback> cb = SharedRefBase::make<Callback>(vm);
311     ScopedAStatus ret = vm->registerCallback(cb);
312     if (!ret.isOk()) {
313         return Error() << "Failed to register callback to virtual machine";
314     }
315     ret = vm->start();
316     if (!ret.isOk()) {
317         return Error() << "Failed to start virtual machine";
318     }
319     return cb;
320 }
321 
322 //--------------------------------------------------------------------------------------------------
323 // Step 4: connect to the payload and communicate with it over binder/vsock
324 //--------------------------------------------------------------------------------------------------
325 
326 // Connect to the binder service running in the payload.
connect_to_vm_payload(std::shared_ptr<IVirtualMachine> vm)327 Result<std::shared_ptr<ITestService>> connect_to_vm_payload(std::shared_ptr<IVirtualMachine> vm) {
328     std::unique_ptr<ARpcSession, decltype(&ARpcSession_free)> session(ARpcSession_new(),
329                                                                       &ARpcSession_free);
330     ARpcSession_setMaxIncomingThreads(session.get(), 1);
331 
332     AIBinder* binder = ARpcSession_setupPreconnectedClient(
333             session.get(),
334             [](void* param) {
335                 std::shared_ptr<IVirtualMachine> vm =
336                         *static_cast<std::shared_ptr<IVirtualMachine>*>(param);
337                 ScopedFileDescriptor sock_fd;
338                 ScopedAStatus ret = vm->connectVsock(ITestService::PORT, &sock_fd);
339                 if (!ret.isOk()) {
340                     return -1;
341                 }
342                 return sock_fd.release();
343             },
344             &vm);
345     if (binder == nullptr) {
346         return Error() << "Failed to connect to vm payload";
347     }
348     return ITestService::fromBinder(SpAIBinder{binder});
349 }
350 
351 // Do something with the service in the VM
do_something(ITestService & payload)352 Result<void> do_something(ITestService& payload) {
353     int32_t result;
354     ScopedAStatus ret = payload.addInteger(10, 20, &result);
355     if (!ret.isOk()) {
356         return Error() << "Failed to call addInteger";
357     }
358     std::cout << "The answer from VM is " << result << std::endl;
359     return {};
360 }
361 
362 // This is the main routine that follows the steps in order
inner_main()363 Result<void> inner_main() {
364     std::string work_dir_path("/data/local/tmp/vm_demo/");
365     if (mkdir(work_dir_path.c_str(), 0700) == -1 && errno != EEXIST) {
366         return ErrnoError() << "failed to create working directory " << work_dir_path.c_str();
367     }
368 
369     // Step 1: connect to the virtualizationservice
370     unique_fd fd = OR_RETURN(get_service_fd());
371     std::shared_ptr<IVirtualizationService> service = OR_RETURN(connect_service(fd.get()));
372 
373     // Step 2: create vm config
374     VirtualMachineAppConfig app_config = OR_RETURN(
375             create_vm_config(*service, work_dir_path, "my_vm",
376                              "/data/local/tmp/MicrodroidTestApp.apk", "MicrodroidTestNativeLib.so",
377                              /* debuggable = */ true, // should be false for production VMs
378                              /* protected_vm = */ true, 150));
379 
380     // Step 3: start vm
381     std::shared_ptr<IVirtualMachine> vm = OR_RETURN(create_virtual_machine(*service, app_config));
382     std::shared_ptr<Callback> cb = OR_RETURN(start_virtual_machine(vm));
383     OR_RETURN(cb->wait_for_state(VirtualMachineState::READY));
384 
385     // Step 4: do something in the vm
386     std::shared_ptr<ITestService> payload = OR_RETURN(connect_to_vm_payload(vm));
387     OR_RETURN(do_something(*payload));
388 
389     // Step 5: let VM quit by itself, and wait for the graceful shutdown
390     ScopedAStatus ret = payload->quit();
391     if (!ret.isOk()) {
392         return Error() << "Failed to command quit to the VM";
393     }
394     OR_RETURN(cb->wait_for_state(VirtualMachineState::DEAD));
395 
396     return {};
397 }
398 
main()399 int main() {
400     if (auto ret = inner_main(); !ret.ok()) {
401         std::cerr << ret.error() << std::endl;
402         return EXIT_FAILURE;
403     }
404     std::cout << "Done" << std::endl;
405     return EXIT_SUCCESS;
406 }
407