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