1 /*
2  * Copyright 2013 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.security.cts;
18 
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.ServiceConnection;
24 import android.os.DeadObjectException;
25 import android.os.IBinder;
26 import android.security.cts.activity.ISecureRandomService;
27 import android.security.cts.activity.SecureRandomService;
28 import android.test.AndroidTestCase;
29 import android.test.suitebuilder.annotation.LargeTest;
30 
31 import java.io.BufferedReader;
32 import java.io.EOFException;
33 import java.io.FileReader;
34 import java.io.IOException;
35 import java.util.Arrays;
36 import java.util.BitSet;
37 import java.util.concurrent.CountDownLatch;
38 import java.util.concurrent.TimeUnit;
39 
40 @LargeTest
41 public class ClonedSecureRandomTest extends AndroidTestCase {
42     private static final int MAX_SHUTDOWN_TRIES = 50;
43 
44     private static final int ANSWER_TIMEOUT_SECONDS = 180;
45 
46     private static final String SEPARATE_PROCESS_NAME = ":secureRandom";
47 
48     private static final int MAX_PID = 32768;
49 
50     /**
51      * Attempt to burn through PIDs faster after this many iterations to reach a
52      * wrap-around point faster.
53      */
54     private static final int PRIMING_ITERATIONS = 128;
55 
56     private static final int RANDOM_BYTES_PER_PID = 8;
57 
58     private static final int MAX_PIDS_WASTED = 1024;
59 
60     private static final int PID_WASTING_SKIP_LOWER = 64;
61 
62     private static final int PID_WASTING_SKIP_UPPER = 2048;
63 
64     private volatile CountDownLatch mLatch;
65 
66     private Intent mSeparateIntent;
67 
68     private ISecureRandomService mSecureRandomService;
69 
70     private ServiceConnection mServiceConnection = new ServiceConnection() {
71         public void onServiceConnected(ComponentName className, IBinder service) {
72             mSecureRandomService = ISecureRandomService.Stub.asInterface(service);
73             mLatch.countDown();
74         }
75 
76         public void onServiceDisconnected(ComponentName className) {
77         }
78     };
79 
80     private boolean mHasDisconnected;
81 
82     @Override
setUp()83     protected void setUp() throws Exception {
84         super.setUp();
85 
86         mSeparateIntent = new Intent(getContext(), SecureRandomService.class);
87     }
88 
89     /**
90      * This test spawns a Service in a new process to check the initial state of
91      * SecureRandom. It then attempts to make the PID number wrap around so it
92      * sees a new process with the same PID twice. The test completes when it
93      * sees two newly started processes with the same PID and compares their
94      * output.
95      */
testCheckForDuplicateOutput()96     public void testCheckForDuplicateOutput() throws Exception {
97         assertEquals("Only supports up to " + MAX_PID + " because of memory requirements",
98                 Integer.toString(MAX_PID), getFirstLineFromFile("/proc/sys/kernel/pid_max"));
99 
100         final String packageName = getContext().getPackageName();
101         String separateProcessName = packageName + SEPARATE_PROCESS_NAME;
102 
103         /*
104          * Using a byte[][] and BitSet gives us a fixed upper bound for the
105          * memory cost of this test. One could possibly use a SparseArray if the
106          * upper bound becomes too large (for instance, if PID_MAX is large),
107          * only keep track of a smaller number of outputs, and just cause a
108          * wrap-around of PIDs to keep the test working.
109          */
110         byte[][] outputs = new byte[MAX_PID][RANDOM_BYTES_PER_PID];
111         BitSet seenPids = new BitSet(MAX_PID);
112 
113         ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
114 
115         int myPid = android.os.Process.myPid();
116 
117         /*
118          * We're guaranteed to see at least one duplicate if we iterate MAX_PID
119          * number of times because of the pigeonhole principle. In an attempt to
120          * hit a collision faster, first get a closely-spaced sampling of PIDs
121          * then spin up a bunch of threads locally to get us closer to wrapping
122          * around to the first PID.
123          */
124         int firstPid = -1;
125         int previousPid = -1;
126         int lastPid = -1;
127         for (int i = 0; i < MAX_PID; i++) {
128             byte[] output = new byte[RANDOM_BYTES_PER_PID];
129             int pid;
130 
131             mLatch = new CountDownLatch(1);
132             getContext().startService(mSeparateIntent);
133             getContext().bindService(mSeparateIntent, mServiceConnection, 0);
134             if (!mLatch.await(ANSWER_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
135                 fail("Timeout waiting for answer from SecureRandomService; cannot complete test");
136             }
137 
138             // Create another latch we'll use to ensure the service has stopped.
139             final CountDownLatch serviceStopLatch = new CountDownLatch(1);
140             mSecureRandomService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
141                 @Override
142                 public void binderDied() {
143                     serviceStopLatch.countDown();
144                 }
145             }, 0);
146 
147             pid = mSecureRandomService.getRandomBytesAndPid(output);
148             getContext().unbindService(mServiceConnection);
149 
150             /*
151              * Ensure the background process has stopped by waiting for the
152              * latch to fire.
153              */
154             int tries = 0;
155             do {
156                 /*
157                  * If this has looped more than once, try to yield to
158                  * system_server.
159                  */
160                 if (tries > 0) {
161                     Thread.yield();
162                 }
163                 getContext().stopService(mSeparateIntent);
164                 am.killBackgroundProcesses(packageName);
165             } while (!serviceStopLatch.await(100, TimeUnit.MILLISECONDS) && tries++ < MAX_SHUTDOWN_TRIES);
166             assertTrue("Background process should have stopped already", tries < MAX_SHUTDOWN_TRIES);
167 
168             /*
169              * Make sure the AndroidManifest.xml wasn't altered in a way that
170              * breaks the test.
171              */
172             assertFalse("SecureRandomService must run in a different process. Check "
173                     + "AndroidManifest.xml to ensure it has a unique android:process=\"...\"",
174                     myPid == pid);
175 
176             // We didn't get a new process for some reason. Try again.
177             if (previousPid == pid) {
178                 i--;
179                 continue;
180             } else if (previousPid == -1 && firstPid == -1) {
181                 /*
182                  * The first time around, we'll discard the output. This is
183                  * needed because we don't know if the SecureRandomService instance
184                  * has been running before or not. To be consistent, we only
185                  * want the first outputs from SecureRandom for this test.
186                  */
187                 i--;
188                 previousPid = pid;
189                 continue;
190             } else {
191                 previousPid = pid;
192             }
193 
194             if (seenPids.get(pid)) {
195                 assertFalse("SecureRandom should not output the same value twice (pid=" + pid
196                                 + ", output=" + Arrays.toString(output) + ", outputs[pid]="
197                                 + Arrays.toString(outputs[pid]) + ")",
198                         Arrays.equals(output, outputs[pid]));
199                 return;
200             }
201 
202             seenPids.set(pid);
203             System.arraycopy(output, 0, outputs[pid], 0, output.length);
204 
205             if (firstPid == -1) {
206                 firstPid = pid;
207             }
208 
209             if (i <= PRIMING_ITERATIONS) {
210                 lastPid = pid;
211             } else if (pid > lastPid && (lastPid > firstPid || pid < firstPid)) {
212                 wastePids(firstPid, previousPid);
213             }
214         }
215 
216         /*
217          * This should never be reached unless the test was altered to break it.
218          * Since we're looping until we see PID_MAX unique answers, we must have
219          * seen a duplicate by the pigeonhole principle.
220          */
221         fail("Must see a duplicate PID");
222     }
223 
224     /**
225      * This is an attempt to get the PIDs to roll over faster. Threads use up
226      * PIDs on Android and spawning a new thread is much faster than having
227      * another service spawned as we are doing in this test.
228      */
wastePids(int firstPid, int previousPid)229     private static void wastePids(int firstPid, int previousPid) {
230         int distance = (firstPid - previousPid + MAX_PID) % MAX_PID;
231 
232         // Don't waste PIDs if we're close to wrap-around to improve odds of
233         // collision.
234         if ((distance < PID_WASTING_SKIP_LOWER) || (MAX_PID - distance < PID_WASTING_SKIP_UPPER)) {
235             return;
236         }
237 
238         for (int i = 0; i < distance; i++) {
239             Thread t = new Thread();
240             t.start();
241         }
242     }
243 
getFirstLineFromFile(String filename)244     private static String getFirstLineFromFile(String filename) throws IOException {
245         BufferedReader in = null;
246         try {
247             in = new BufferedReader(new FileReader(filename));
248             final String line = in.readLine();
249             if (line == null) {
250                 throw new EOFException("EOF encountered before reading first line of " + filename);
251             }
252             return line.trim();
253         } finally {
254             if (in != null) {
255                 in.close();
256             }
257         }
258     }
259 }
260