1 /*
2  * Copyright (C) 2016 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 android.externalservice.cts;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.ConditionVariable;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.Messenger;
29 import android.os.Parcel;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.test.AndroidTestCase;
33 import android.util.Log;
34 
35 import android.externalservice.common.ServiceMessages;
36 
37 public class ExternalServiceTest extends AndroidTestCase {
38     private static final String TAG = "ExternalServiceTest";
39 
40     static final String sServicePackage = "android.externalservice.service";
41 
42     private Connection mConnection = new Connection();
43 
44     private ConditionVariable mCondition = new ConditionVariable(false);
45 
46     static final int CONDITION_TIMEOUT = 10 * 1000 /* 10 seconds */;
47 
tearDown()48     public void tearDown() {
49         if (mConnection.service != null)
50             getContext().unbindService(mConnection);
51     }
52 
53     /** Tests that an isolatedProcess service cannot be bound to by an external package. */
testFailBindIsolated()54     public void testFailBindIsolated() {
55         Intent intent = new Intent();
56         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".IsolatedService"));
57         try {
58             getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
59             fail("Should not be able to bind to non-exported, non-external service");
60         } catch (SecurityException e) {
61         }
62     }
63 
64     /** Tests that BIND_EXTERNAL_SERVICE does not work with plain isolatedProcess services. */
testFailBindExternalIsolated()65     public void testFailBindExternalIsolated() {
66         Intent intent = new Intent();
67         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".IsolatedService"));
68         try {
69             getContext().bindService(intent, mConnection,
70                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE);
71             fail("Should not be able to BIND_EXTERNAL_SERVICE to non-exported, non-external service");
72         } catch (SecurityException e) {
73         }
74     }
75 
76     /** Tests that BIND_EXTERNAL_SERVICE does not work with exported, isolatedProcess services (
77      * requires externalService as well). */
testFailBindExternalExported()78     public void testFailBindExternalExported() {
79         Intent intent = new Intent();
80         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".ExportedService"));
81         try {
82             getContext().bindService(intent, mConnection,
83                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE);
84             fail("Should not be able to BIND_EXTERNAL_SERVICE to non-external service");
85         } catch (SecurityException e) {
86         }
87     }
88 
89     /** Tests that BIND_EXTERNAL_SERVICE requires that an externalService be exported. */
testFailBindExternalNonExported()90     public void testFailBindExternalNonExported() {
91         Intent intent = new Intent();
92         intent.setComponent(
93                 new ComponentName(sServicePackage, sServicePackage+".ExternalNonExportedService"));
94         try {
95             getContext().bindService(intent, mConnection,
96                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE);
97             fail("Should not be able to BIND_EXTERNAL_SERVICE to non-exported service");
98         } catch (SecurityException e) {
99         }
100     }
101 
102     /** Tests that BIND_EXTERNAL_SERVICE requires the service be an isolatedProcess. */
testFailBindExternalNonIsolated()103     public void testFailBindExternalNonIsolated() {
104         Intent intent = new Intent();
105         intent.setComponent(
106                 new ComponentName(sServicePackage, sServicePackage+".ExternalNonIsolatedService"));
107         try {
108             getContext().bindService(intent, mConnection,
109                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE);
110             fail("Should not be able to BIND_EXTERNAL_SERVICE to non-isolated service");
111         } catch (SecurityException e) {
112         }
113     }
114 
115     /** Tests that an externalService can only be bound with BIND_EXTERNAL_SERVICE. */
testFailBindWithoutBindExternal()116     public void testFailBindWithoutBindExternal() {
117         Intent intent = new Intent();
118         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".ExternalService"));
119         try {
120             getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
121             fail("Should not be able to bind to an external service without BIND_EXTERNAL_SERVICE");
122         } catch (SecurityException e) {
123         }
124     }
125 
126     /** Tests that an external service can be bound, and that it runs as a different principal. */
testBindExternalService()127     public void testBindExternalService() {
128         // Start the service and wait for connection.
129         Intent intent = new Intent();
130         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".ExternalService"));
131 
132         mCondition.close();
133         assertTrue(getContext().bindService(intent, mConnection,
134                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE));
135 
136         assertTrue(mCondition.block(CONDITION_TIMEOUT));
137         assertEquals(getContext().getPackageName(), mConnection.name.getPackageName());
138         assertNotSame(sServicePackage, mConnection.name.getPackageName());
139 
140         // Check the identity of the service.
141         Messenger remote = new Messenger(mConnection.service);
142         ServiceIdentity id = identifyService(remote);
143         assertNotNull(id);
144 
145         assertFalse(id.uid == 0 || id.pid == 0);
146         assertNotEquals(Process.myUid(), id.uid);
147         assertNotEquals(Process.myPid(), id.pid);
148         assertEquals(getContext().getPackageName(), id.packageName);
149     }
150 
151     /** Tests that the APK providing the externalService can bind the service itself, and that
152      * other APKs bind to a different instance of it. */
testBindExternalServiceWithRunningOwn()153     public void testBindExternalServiceWithRunningOwn() {
154         // Start the service that will create the externalService.
155         final Connection creatorConnection = new Connection();
156         Intent intent = new Intent();
157         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".ServiceCreator"));
158 
159         mCondition.close();
160         assertTrue(getContext().bindService(intent, creatorConnection, Context.BIND_AUTO_CREATE));
161         assertTrue(mCondition.block(CONDITION_TIMEOUT));
162 
163         // Get the identity of the creator.
164         Messenger remoteCreator = new Messenger(creatorConnection.service);
165         ServiceIdentity creatorId = identifyService(remoteCreator);
166         assertNotNull(creatorId);
167         assertFalse(creatorId.uid == 0 || creatorId.pid == 0);
168 
169         // Have the creator actually start its service.
170         final Message creatorMsg =
171                 Message.obtain(null, ServiceMessages.MSG_CREATE_EXTERNAL_SERVICE);
172         Handler creatorHandler = new Handler(Looper.getMainLooper()) {
173             @Override
174             public void handleMessage(Message msg) {
175                 Log.d(TAG, "Received message: " + msg);
176                 switch (msg.what) {
177                     case ServiceMessages.MSG_CREATE_EXTERNAL_SERVICE_RESPONSE:
178                         creatorMsg.copyFrom(msg);
179                         mCondition.open();
180                         break;
181                 }
182                 super.handleMessage(msg);
183             }
184         };
185         Messenger localCreator = new Messenger(creatorHandler);
186         creatorMsg.replyTo = localCreator;
187         try {
188             mCondition.close();
189             remoteCreator.send(creatorMsg);
190         } catch (RemoteException e) {
191             fail("Unexpected remote exception" + e);
192             return;
193         }
194         assertTrue(mCondition.block(CONDITION_TIMEOUT));
195 
196         // Get the connection to the creator's service.
197         assertNotNull(creatorMsg.obj);
198         Messenger remoteCreatorService = (Messenger) creatorMsg.obj;
199         ServiceIdentity creatorServiceId = identifyService(remoteCreatorService);
200         assertNotNull(creatorServiceId);
201         assertFalse(creatorServiceId.uid == 0 || creatorId.pid == 0);
202 
203         // Create an external service from this (the test) process.
204         intent = new Intent();
205         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".ExternalService"));
206 
207         mCondition.close();
208         assertTrue(getContext().bindService(intent, mConnection,
209                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE));
210         assertTrue(mCondition.block(CONDITION_TIMEOUT));
211         ServiceIdentity serviceId = identifyService(new Messenger(mConnection.service));
212         assertNotNull(serviceId);
213         assertFalse(serviceId.uid == 0 || serviceId.pid == 0);
214 
215         // Make sure that all the processes are unique.
216         int myUid = Process.myUid();
217         int myPid = Process.myPid();
218         String myPkg = getContext().getPackageName();
219 
220         assertNotEquals(myUid, creatorId.uid);
221         assertNotEquals(myUid, creatorServiceId.uid);
222         assertNotEquals(myUid, serviceId.uid);
223         assertNotEquals(myPid, creatorId.pid);
224         assertNotEquals(myPid, creatorServiceId.pid);
225         assertNotEquals(myPid, serviceId.pid);
226 
227         assertNotEquals(creatorId.uid, creatorServiceId.uid);
228         assertNotEquals(creatorId.uid, serviceId.uid);
229         assertNotEquals(creatorId.pid, creatorServiceId.pid);
230         assertNotEquals(creatorId.pid, serviceId.pid);
231 
232         assertNotEquals(creatorServiceId.uid, serviceId.uid);
233         assertNotEquals(creatorServiceId.pid, serviceId.pid);
234 
235         assertNotEquals(myPkg, creatorId.packageName);
236         assertNotEquals(myPkg, creatorServiceId.packageName);
237         assertEquals(creatorId.packageName, creatorServiceId.packageName);
238         assertEquals(myPkg, serviceId.packageName);
239 
240         getContext().unbindService(creatorConnection);
241     }
242 
243     /** Tests that the binding to an externalService can be changed. */
testBindExternalAboveClient()244     public void testBindExternalAboveClient() {
245         // Start the service and wait for connection.
246         Intent intent = new Intent();
247         intent.setComponent(new ComponentName(sServicePackage, sServicePackage+".ExternalService"));
248 
249         mCondition.close();
250         Connection initialConn = new Connection();
251         assertTrue(getContext().bindService(intent, initialConn,
252                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE));
253 
254         assertTrue(mCondition.block(CONDITION_TIMEOUT));
255 
256         ServiceIdentity idOne = identifyService(new Messenger(initialConn.service));
257         assertNotNull(idOne);
258         assertFalse(idOne.uid == 0 || idOne.pid == 0);
259 
260         // Bind the service with a different priority.
261         mCondition.close();
262         Connection prioConn = new Connection();
263         assertTrue(getContext().bindService(intent, prioConn,
264                     Context.BIND_AUTO_CREATE | Context.BIND_EXTERNAL_SERVICE |
265                             Context.BIND_ABOVE_CLIENT));
266 
267         assertTrue(mCondition.block(CONDITION_TIMEOUT));
268 
269         ServiceIdentity idTwo = identifyService(new Messenger(prioConn.service));
270         assertNotNull(idTwo);
271         assertFalse(idTwo.uid == 0 || idTwo.pid == 0);
272 
273         assertEquals(idOne.uid, idTwo.uid);
274         assertEquals(idOne.pid, idTwo.pid);
275         assertEquals(idOne.packageName, idTwo.packageName);
276         assertNotEquals(Process.myUid(), idOne.uid);
277         assertNotEquals(Process.myPid(), idOne.pid);
278         assertEquals(getContext().getPackageName(), idOne.packageName);
279 
280         getContext().unbindService(prioConn);
281         getContext().unbindService(initialConn);
282     }
283 
284     /** Contains information about the security principal of a Service. */
285     private static class ServiceIdentity {
286         int uid;
287         int pid;
288         String packageName;
289     }
290 
291     /** Given a Messenger, this will message the service to retrieve its UID, PID, and package name.
292      * On success, returns a ServiceIdentity. On failure, returns null. */
identifyService(Messenger service)293     private ServiceIdentity identifyService(Messenger service) {
294         final ServiceIdentity id = new ServiceIdentity();
295         Handler handler = new Handler(Looper.getMainLooper()) {
296             @Override
297             public void handleMessage(Message msg) {
298                 Log.d(TAG, "Received message: " + msg);
299                 switch (msg.what) {
300                     case ServiceMessages.MSG_IDENTIFY_RESPONSE:
301                         id.uid = msg.arg1;
302                         id.pid = msg.arg2;
303                         id.packageName = msg.getData().getString(ServiceMessages.IDENTIFY_PACKAGE);
304                         mCondition.open();
305                         break;
306                 }
307                 super.handleMessage(msg);
308             }
309         };
310         Messenger local = new Messenger(handler);
311 
312         Message msg = Message.obtain(null, ServiceMessages.MSG_IDENTIFY);
313         msg.replyTo = local;
314         try {
315             mCondition.close();
316             service.send(msg);
317         } catch (RemoteException e) {
318             fail("Unexpected remote exception: " + e);
319             return null;
320         }
321 
322         if (!mCondition.block(CONDITION_TIMEOUT))
323             return null;
324         return id;
325     }
326 
327     private class Connection implements ServiceConnection {
328         IBinder service = null;
329         ComponentName name = null;
330         boolean disconnected = false;
331 
332         @Override
onServiceConnected(ComponentName name, IBinder service)333         public void onServiceConnected(ComponentName name, IBinder service) {
334             Log.d(TAG, "onServiceConnected " + name);
335             this.service = service;
336             this.name = name;
337             mCondition.open();
338         }
339 
340         @Override
onServiceDisconnected(ComponentName name)341         public void onServiceDisconnected(ComponentName name) {
342             Log.d(TAG, "onServiceDisconnected " + name);
343         }
344     }
345 
assertNotEquals(T expected, T actual)346     private <T> void assertNotEquals(T expected, T actual) {
347         assertFalse("Expected <" + expected + "> should not be equal to actual <" + actual + ">",
348                 expected.equals(actual));
349     }
350 }
351