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();
+ }
}