| /* |
| * Copyright 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.security.cts; |
| |
| import android.app.ActivityManager; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.DeadObjectException; |
| import android.os.IBinder; |
| import android.security.cts.activity.ISecureRandomService; |
| import android.security.cts.activity.SecureRandomService; |
| import android.test.AndroidTestCase; |
| |
| import com.android.cts.util.TimeoutReq; |
| |
| import java.io.BufferedReader; |
| import java.io.EOFException; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| public class ClonedSecureRandomTest extends AndroidTestCase { |
| private static final int MAX_SHUTDOWN_TRIES = 50; |
| |
| private static final int ANSWER_TIMEOUT_SECONDS = 180; |
| |
| private static final String SEPARATE_PROCESS_NAME = ":secureRandom"; |
| |
| private static final int MAX_PID = 32768; |
| |
| /** |
| * Attempt to burn through PIDs faster after this many iterations to reach a |
| * wrap-around point faster. |
| */ |
| private static final int PRIMING_ITERATIONS = 128; |
| |
| private static final int RANDOM_BYTES_PER_PID = 8; |
| |
| private static final int MAX_PIDS_WASTED = 1024; |
| |
| private static final int PID_WASTING_SKIP_LOWER = 64; |
| |
| private static final int PID_WASTING_SKIP_UPPER = 2048; |
| |
| private volatile CountDownLatch mLatch; |
| |
| private Intent mSeparateIntent; |
| |
| private ISecureRandomService mSecureRandomService; |
| |
| private ServiceConnection mServiceConnection = new ServiceConnection() { |
| public void onServiceConnected(ComponentName className, IBinder service) { |
| mSecureRandomService = ISecureRandomService.Stub.asInterface(service); |
| mLatch.countDown(); |
| } |
| |
| public void onServiceDisconnected(ComponentName className) { |
| } |
| }; |
| |
| private boolean mHasDisconnected; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| mSeparateIntent = new Intent(getContext(), SecureRandomService.class); |
| } |
| |
| /** |
| * This test spawns a Service in a new process to check the initial state of |
| * SecureRandom. It then attempts to make the PID number wrap around so it |
| * sees a new process with the same PID twice. The test completes when it |
| * sees two newly started processes with the same PID and compares their |
| * output. |
| */ |
| @TimeoutReq(minutes=15) |
| public void testCheckForDuplicateOutput() throws Exception { |
| assertEquals("Only supports up to " + MAX_PID + " because of memory requirements", |
| Integer.toString(MAX_PID), getFirstLineFromFile("/proc/sys/kernel/pid_max")); |
| |
| final String packageName = getContext().getPackageName(); |
| String separateProcessName = packageName + SEPARATE_PROCESS_NAME; |
| |
| /* |
| * Using a byte[][] and BitSet gives us a fixed upper bound for the |
| * memory cost of this test. One could possibly use a SparseArray if the |
| * upper bound becomes too large (for instance, if PID_MAX is large), |
| * only keep track of a smaller number of outputs, and just cause a |
| * wrap-around of PIDs to keep the test working. |
| */ |
| byte[][] outputs = new byte[MAX_PID][RANDOM_BYTES_PER_PID]; |
| BitSet seenPids = new BitSet(MAX_PID); |
| |
| ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); |
| |
| int myPid = android.os.Process.myPid(); |
| |
| /* |
| * We're guaranteed to see at least one duplicate if we iterate MAX_PID |
| * number of times because of the pigeonhole principle. In an attempt to |
| * hit a collision faster, first get a closely-spaced sampling of PIDs |
| * then spin up a bunch of threads locally to get us closer to wrapping |
| * around to the first PID. |
| */ |
| int firstPid = -1; |
| int previousPid = -1; |
| int lastPid = -1; |
| for (int i = 0; i < MAX_PID; i++) { |
| byte[] output = new byte[RANDOM_BYTES_PER_PID]; |
| int pid; |
| |
| mLatch = new CountDownLatch(1); |
| getContext().startService(mSeparateIntent); |
| getContext().bindService(mSeparateIntent, mServiceConnection, 0); |
| if (!mLatch.await(ANSWER_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { |
| fail("Timeout waiting for answer from SecureRandomService; cannot complete test"); |
| } |
| |
| // Create another latch we'll use to ensure the service has stopped. |
| final CountDownLatch serviceStopLatch = new CountDownLatch(1); |
| mSecureRandomService.asBinder().linkToDeath(new IBinder.DeathRecipient() { |
| @Override |
| public void binderDied() { |
| serviceStopLatch.countDown(); |
| } |
| }, 0); |
| |
| pid = mSecureRandomService.getRandomBytesAndPid(output); |
| getContext().unbindService(mServiceConnection); |
| |
| /* |
| * Ensure the background process has stopped by waiting for the |
| * latch to fire. |
| */ |
| int tries = 0; |
| do { |
| /* |
| * If this has looped more than once, try to yield to |
| * system_server. |
| */ |
| if (tries > 0) { |
| Thread.yield(); |
| } |
| getContext().stopService(mSeparateIntent); |
| am.killBackgroundProcesses(packageName); |
| } while (!serviceStopLatch.await(100, TimeUnit.MILLISECONDS) && tries++ < MAX_SHUTDOWN_TRIES); |
| assertTrue("Background process should have stopped already", tries < MAX_SHUTDOWN_TRIES); |
| |
| /* |
| * Make sure the AndroidManifest.xml wasn't altered in a way that |
| * breaks the test. |
| */ |
| assertFalse("SecureRandomService must run in a different process. Check " |
| + "AndroidManifest.xml to ensure it has a unique android:process=\"...\"", |
| myPid == pid); |
| |
| // We didn't get a new process for some reason. Try again. |
| if (previousPid == pid) { |
| i--; |
| continue; |
| } else if (previousPid == -1 && firstPid == -1) { |
| /* |
| * The first time around, we'll discard the output. This is |
| * needed because we don't know if the SecureRandomService instance |
| * has been running before or not. To be consistent, we only |
| * want the first outputs from SecureRandom for this test. |
| */ |
| i--; |
| previousPid = pid; |
| continue; |
| } else { |
| previousPid = pid; |
| } |
| |
| if (seenPids.get(pid)) { |
| assertFalse("SecureRandom should not output the same value twice (pid=" + pid |
| + ", output=" + Arrays.toString(output) + ", outputs[pid]=" |
| + Arrays.toString(outputs[pid]) + ")", |
| Arrays.equals(output, outputs[pid])); |
| return; |
| } |
| |
| seenPids.set(pid); |
| System.arraycopy(output, 0, outputs[pid], 0, output.length); |
| |
| if (firstPid == -1) { |
| firstPid = pid; |
| } |
| |
| if (i <= PRIMING_ITERATIONS) { |
| lastPid = pid; |
| } else if (pid > lastPid && (lastPid > firstPid || pid < firstPid)) { |
| wastePids(firstPid, previousPid); |
| } |
| } |
| |
| /* |
| * This should never be reached unless the test was altered to break it. |
| * Since we're looping until we see PID_MAX unique answers, we must have |
| * seen a duplicate by the pigeonhole principle. |
| */ |
| fail("Must see a duplicate PID"); |
| } |
| |
| /** |
| * This is an attempt to get the PIDs to roll over faster. Threads use up |
| * PIDs on Android and spawning a new thread is much faster than having |
| * another service spawned as we are doing in this test. |
| */ |
| private static void wastePids(int firstPid, int previousPid) { |
| int distance = (firstPid - previousPid + MAX_PID) % MAX_PID; |
| |
| // Don't waste PIDs if we're close to wrap-around to improve odds of |
| // collision. |
| if ((distance < PID_WASTING_SKIP_LOWER) || (MAX_PID - distance < PID_WASTING_SKIP_UPPER)) { |
| return; |
| } |
| |
| for (int i = 0; i < distance; i++) { |
| Thread t = new Thread(); |
| t.start(); |
| } |
| } |
| |
| private static String getFirstLineFromFile(String filename) throws IOException { |
| BufferedReader in = null; |
| try { |
| in = new BufferedReader(new FileReader(filename)); |
| final String line = in.readLine(); |
| if (line == null) { |
| throw new EOFException("EOF encountered before reading first line of " + filename); |
| } |
| return line.trim(); |
| } finally { |
| if (in != null) { |
| in.close(); |
| } |
| } |
| } |
| } |