1 /*
2  * Copyright (C) 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 
17 package com.android.microdroid.test.sharevm;
18 
19 import android.app.Service;
20 import android.content.Intent;
21 import android.os.IBinder;
22 import android.os.RemoteException;
23 import android.system.virtualmachine.VirtualMachine;
24 import android.system.virtualmachine.VirtualMachineCallback;
25 import android.system.virtualmachine.VirtualMachineDescriptor;
26 import android.system.virtualmachine.VirtualMachineException;
27 import android.system.virtualmachine.VirtualMachineManager;
28 import android.util.Log;
29 
30 import com.android.microdroid.test.vmshare.IVmShareTestService;
31 import com.android.microdroid.testservice.ITestService;
32 import com.android.microdroid.testservice.IAppCallback;
33 
34 import java.util.UUID;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.TimeoutException;
38 
39 /**
40  * A {@link Service} that is used in end-to-end tests of the {@link VirtualMachine} sharing
41  * functionality.
42  *
43  * <p>During the test {@link com.android.microdroid.test.MicrodroidTests} will bind to this service,
44  * and call {@link #startVm(VirtualMachineDescriptor)} to share the VM. This service then will
45  * create a {@link VirtualMachine} from that descriptor, {@link VirtualMachine#run() run} it, and
46  * send back {@link RemoteTestServiceDelegate}. The {@code MicrodroidTests} can use that {@link
47  * RemoteTestServiceDelegate} to assert conditions on the VM running in the {@link
48  * VmShareServiceImpl}.
49  *
50  * <p>The {@link VirtualMachine} running in this service will be stopped on {@link
51  * #onUnbind(Intent)}.
52  *
53  * @see com.android.microdroid.test.MicrodroidTests#testShareVmWithAnotherApp
54  */
55 public class VmShareServiceImpl extends Service {
56 
57     private static final String TAG = "VmShareApp";
58 
59     private IVmShareTestService.Stub mBinder;
60 
61     private VirtualMachine mVirtualMachine;
62 
63     @Override
onCreate()64     public void onCreate() {
65         mBinder = new ServiceImpl();
66     }
67 
68     @Override
onBind(Intent intent)69     public IBinder onBind(Intent intent) {
70         Log.i(TAG, "onBind " + intent + " binder = " + mBinder);
71         return mBinder;
72     }
73 
74     @Override
onUnbind(Intent intent)75     public boolean onUnbind(Intent intent) {
76         deleteVm();
77         // Tell framework that it shouldn't call onRebind.
78         return false;
79     }
80 
deleteVm()81     private void deleteVm() {
82         if (mVirtualMachine == null) {
83             return;
84         }
85         try {
86             mVirtualMachine.stop();
87             String name = mVirtualMachine.getName();
88             VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
89             vmm.delete(name);
90             mVirtualMachine = null;
91         } catch (VirtualMachineException e) {
92             Log.e(TAG, "Failed to stop " + mVirtualMachine, e);
93         }
94     }
95 
importVm(VirtualMachineDescriptor vmDesc)96     public void importVm(VirtualMachineDescriptor vmDesc) throws Exception {
97         // Cleanup VM left from the previous test.
98         deleteVm();
99 
100         // Add random uuid to make sure that different tests that bind to this service don't trip
101         // over each other.
102         String vmName = "imported_vm" + UUID.randomUUID();
103 
104         VirtualMachineManager vmm = getSystemService(VirtualMachineManager.class);
105         mVirtualMachine = vmm.importFromDescriptor(vmName, vmDesc);
106     }
107 
startVm()108     public ITestService startVm() throws Exception {
109         final CountDownLatch latch = new CountDownLatch(1);
110         VirtualMachineCallback callback =
111                 new VirtualMachineCallback() {
112 
113                     @Override
114                     public void onPayloadStarted(VirtualMachine vm) {
115                         // Ignored
116                     }
117 
118                     @Override
119                     public void onPayloadReady(VirtualMachine vm) {
120                         latch.countDown();
121                     }
122 
123                     @Override
124                     public void onPayloadFinished(VirtualMachine vm, int exitCode) {
125                         // Ignored
126                     }
127 
128                     @Override
129                     public void onError(VirtualMachine vm, int errorCode, String message) {
130                         throw new RuntimeException(
131                                 "VM failed with error " + errorCode + " : " + message);
132                     }
133 
134                     @Override
135                     public void onStopped(VirtualMachine vm, int reason) {
136                         // Ignored
137                     }
138                 };
139 
140         mVirtualMachine.setCallback(getMainExecutor(), callback);
141 
142         Log.i(TAG, "Starting VM " + mVirtualMachine.getName());
143         mVirtualMachine.run();
144         if (!latch.await(1, TimeUnit.MINUTES)) {
145             throw new TimeoutException("Timed out starting VM");
146         }
147 
148         Log.i(
149                 TAG,
150                 "Payload is ready, connecting to the vsock service at port " + ITestService.PORT);
151         ITestService testService =
152                 ITestService.Stub.asInterface(
153                         mVirtualMachine.connectToVsockServer(ITestService.PORT));
154         return new RemoteTestServiceDelegate(testService);
155     }
156 
157     final class ServiceImpl extends IVmShareTestService.Stub {
158 
159         @Override
importVm(VirtualMachineDescriptor vmDesc)160         public void importVm(VirtualMachineDescriptor vmDesc) {
161             Log.i(TAG, "importVm binder call received");
162             try {
163                 VmShareServiceImpl.this.importVm(vmDesc);
164             } catch (Exception e) {
165                 Log.e(TAG, "Failed to importVm", e);
166                 throw new IllegalStateException("Failed to importVm", e);
167             }
168         }
169 
170         @Override
startVm()171         public ITestService startVm() {
172             Log.i(TAG, "startVm binder call received");
173             try {
174                 return VmShareServiceImpl.this.startVm();
175             } catch (Exception e) {
176                 Log.e(TAG, "Failed to startVm", e);
177                 throw new IllegalStateException("Failed to startVm", e);
178             }
179         }
180     }
181 
182     private static class RemoteTestServiceDelegate extends ITestService.Stub {
183 
184         private final ITestService mServiceInVm;
185 
RemoteTestServiceDelegate(ITestService serviceInVm)186         private RemoteTestServiceDelegate(ITestService serviceInVm) {
187             mServiceInVm = serviceInVm;
188         }
189 
190         @Override
addInteger(int a, int b)191         public int addInteger(int a, int b) throws RemoteException {
192             return mServiceInVm.addInteger(a, b);
193         }
194 
195         @Override
readProperty(String prop)196         public String readProperty(String prop) throws RemoteException {
197             throw new UnsupportedOperationException("Not supported");
198         }
199 
200         @Override
insecurelyExposeVmInstanceSecret()201         public byte[] insecurelyExposeVmInstanceSecret() throws RemoteException {
202             throw new UnsupportedOperationException("Not supported");
203         }
204 
205         @Override
insecurelyExposeAttestationCdi()206         public byte[] insecurelyExposeAttestationCdi() throws RemoteException {
207             throw new UnsupportedOperationException("Not supported");
208         }
209 
210         @Override
getBcc()211         public byte[] getBcc() throws RemoteException {
212             throw new UnsupportedOperationException("Not supported");
213         }
214 
215         @Override
getApkContentsPath()216         public String getApkContentsPath() throws RemoteException {
217             throw new UnsupportedOperationException("Not supported");
218         }
219 
220         @Override
getEncryptedStoragePath()221         public String getEncryptedStoragePath() throws RemoteException {
222             throw new UnsupportedOperationException("Not supported");
223         }
224 
225         @Override
runEchoReverseServer()226         public void runEchoReverseServer() throws RemoteException {
227             throw new UnsupportedOperationException("Not supported");
228         }
229 
230         @Override
getEffectiveCapabilities()231         public String[] getEffectiveCapabilities() throws RemoteException {
232             throw new UnsupportedOperationException("Not supported");
233         }
234 
235         @Override
getUid()236         public int getUid() throws RemoteException {
237             throw new UnsupportedOperationException("Not supported");
238         }
239 
240         @Override
writeToFile(String content, String path)241         public void writeToFile(String content, String path) throws RemoteException {
242             throw new UnsupportedOperationException("Not supported");
243         }
244 
245         @Override
readFromFile(String path)246         public String readFromFile(String path) throws RemoteException {
247             return mServiceInVm.readFromFile(path);
248         }
249 
250         @Override
getFilePermissions(String path)251         public int getFilePermissions(String path) throws RemoteException {
252             throw new UnsupportedOperationException("Not supported");
253         }
254 
255         @Override
getMountFlags(String path)256         public int getMountFlags(String path) throws RemoteException {
257             throw new UnsupportedOperationException("Not supported");
258         }
259 
260         @Override
requestCallback(IAppCallback appCallback)261         public void requestCallback(IAppCallback appCallback) {
262             throw new UnsupportedOperationException("Not supported");
263         }
264 
265         @Override
readLineFromConsole()266         public String readLineFromConsole() {
267             throw new UnsupportedOperationException("Not supported");
268         }
269 
270         @Override
quit()271         public void quit() throws RemoteException {
272             throw new UnsupportedOperationException("Not supported");
273         }
274     }
275 }
276