Add new test type to StagefrightTest

This test decodes raw blobs of encoded data, not wrapped in a container file.

Bug: 36215950
Bug: 36816007
Bug: 36895511
Change-Id: I68aea5a9e78d8600c72b2cc280d63cf90fff4214
(cherry picked from commit 6724527ab528c8abbd0a4dd9fb560005fc200ee9)
diff --git a/tests/tests/security/res/raw/bug_36215950.mp4 b/tests/tests/security/res/raw/bug_36215950.mp4
new file mode 100644
index 0000000..a58f49e
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_36215950.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_36816007.mp4 b/tests/tests/security/res/raw/bug_36816007.mp4
new file mode 100644
index 0000000..70fa650
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_36816007.mp4
Binary files differ
diff --git a/tests/tests/security/res/raw/bug_36895511.mp4 b/tests/tests/security/res/raw/bug_36895511.mp4
new file mode 100644
index 0000000..298494b
--- /dev/null
+++ b/tests/tests/security/res/raw/bug_36895511.mp4
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 67600ec..08e2078 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -45,6 +45,7 @@
 import android.view.Surface;
 import android.webkit.cts.CtsTestServer;
 
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -789,4 +790,152 @@
         thr.stopLooper();
         thr.join();
     }
+
+    public void testBug36215950() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_36215950, "video/hevc", 320, 240);
+    }
+
+    public void testBug36816007() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_36816007, "video/avc", 320, 240);
+    }
+
+    public void testBug36895511() throws Exception {
+        doStagefrightTestRawBlob(R.raw.bug_36895511, "video/hevc", 320, 240);
+    }
+
+    private void runWithTimeout(Runnable runner, int timeout) {
+        Thread t = new Thread(runner);
+        t.start();
+        try {
+            t.join(timeout);
+        } catch (InterruptedException e) {
+            fail("operation was interrupted");
+        }
+        if (t.isAlive()) {
+            fail("operation not completed within timeout of " + timeout + "ms");
+        }
+    }
+
+    private void releaseCodec(final MediaCodec codec) {
+        runWithTimeout(new Runnable() {
+            @Override
+            public void run() {
+                codec.release();
+            }
+        }, 5000);
+    }
+
+    private void doStagefrightTestRawBlob(int rid, String mime, int initWidth, int initHeight) throws Exception {
+
+        final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
+        final Context context = getInstrumentation().getContext();
+        final Resources resources =  context.getResources();
+
+        LooperThread thr = new LooperThread(new Runnable() {
+            @Override
+            public void run() {
+
+                MediaPlayer mp = new MediaPlayer();
+                mp.setOnErrorListener(mpcl);
+                AssetFileDescriptor fd = null;
+                try {
+                    fd = resources.openRawResourceFd(R.raw.good);
+
+                    // the onErrorListener won't receive MEDIA_ERROR_SERVER_DIED until
+                    // setDataSource has been called
+                    mp.setDataSource(fd.getFileDescriptor(),
+                                     fd.getStartOffset(),
+                                     fd.getLength());
+                    fd.close();
+                } catch (Exception e) {
+                    // this is a known-good file, so no failure should occur
+                    fail("setDataSource of known-good file failed");
+                }
+
+                synchronized(mpcl) {
+                    mpcl.notify();
+                }
+                Looper.loop();
+                mp.release();
+            }
+        });
+        thr.start();
+        // wait until the thread has initialized the MediaPlayer
+        synchronized(mpcl) {
+            mpcl.wait();
+        }
+
+        AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+        byte [] blob = new byte[(int)fd.getLength()];
+        FileInputStream fis = fd.createInputStream();
+        int numRead = fis.read(blob);
+        fis.close();
+        //Log.i("@@@@", "read " + numRead + " bytes");
+
+        // find all the available decoders for this format
+        ArrayList<String> matchingCodecs = new ArrayList<String>();
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+            if (info.isEncoder()) {
+                continue;
+            }
+            try {
+                MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                if (caps != null) {
+                    matchingCodecs.add(info.getName());
+                }
+            } catch (IllegalArgumentException e) {
+                // type is not supported
+            }
+        }
+
+        if (matchingCodecs.size() == 0) {
+            Log.w(TAG, "no codecs for mime type " + mime);
+        }
+        String rname = resources.getResourceEntryName(rid);
+        // decode this blob once with each matching codec
+        for (String codecName: matchingCodecs) {
+            Log.i(TAG, "Decoding blob " + rname + " using codec " + codecName);
+            MediaCodec codec = MediaCodec.createByCodecName(codecName);
+            MediaFormat format = MediaFormat.createVideoFormat(mime, initWidth, initHeight);
+            codec.configure(format, null, null, 0);
+            codec.start();
+
+            try {
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                ByteBuffer [] inputBuffers = codec.getInputBuffers();
+                // enqueue the bad data a number of times, in case
+                // the codec needs multiple buffers to fail.
+                for(int i = 0; i < 64; i++) {
+                    int bufidx = codec.dequeueInputBuffer(5000);
+                    if (bufidx >= 0) {
+                        Log.i(TAG, "got input buffer of size " + inputBuffers[bufidx].capacity());
+                        inputBuffers[bufidx].rewind();
+                        inputBuffers[bufidx].put(blob, 0, numRead);
+                        codec.queueInputBuffer(bufidx, 0, numRead, 0, 0);
+                    } else {
+                        Log.i(TAG, "no input buffer");
+                    }
+                    bufidx = codec.dequeueOutputBuffer(info, 5000);
+                    if (bufidx >= 0) {
+                        Log.i(TAG, "got output buffer");
+                        codec.releaseOutputBuffer(bufidx, false);
+                    } else {
+                        Log.i(TAG, "no output buffer");
+                    }
+                }
+            } catch (Exception e) {
+                // ignore, not a security issue
+            } finally {
+                releaseCodec(codec);
+            }
+        }
+
+        String cve = rname.replace("_", "-").toUpperCase();
+        assertFalse("Device *IS* vulnerable to " + cve,
+                    mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+        thr.stopLooper();
+        thr.join();
+    }
 }