Backport MediaCodec-based test to StagefrighTest
am: 576904fc52
Change-Id: I29708ddb4b37d1ca7c9ec56318663a883328b849
diff --git a/tests/tests/security/res/raw/cve_2016_2507.mp4 b/tests/tests/security/res/raw/cve_2016_2507.mp4
new file mode 100644
index 0000000..ca248e1
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2016_2507.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 b45a697..b011306 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -24,12 +24,25 @@
import android.content.Context;
import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
import android.media.MediaPlayer;
+import android.opengl.GLES20;
+import android.opengl.GLES11Ext;
import android.os.Looper;
+import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.util.Log;
+import android.view.Surface;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -48,6 +61,15 @@
public StagefrightTest() {
}
+ /***********************************************************
+ to prevent merge conflicts, add K tests below this comment,
+ before any existing test methods
+ ***********************************************************/
+
+ public void testStagefright_cve_2016_2507() throws Exception {
+ doStagefrightTest(R.raw.cve_2016_2507);
+ }
+
public void testStagefright_cve_2015_1538_1() throws Exception {
doStagefrightTest(R.raw.cve_2015_1538_1);
}
@@ -125,58 +147,123 @@
}
private void doStagefrightTest(final int rid) throws Exception {
- class MediaPlayerCrashListener
- implements MediaPlayer.OnErrorListener,
- MediaPlayer.OnPreparedListener,
- MediaPlayer.OnCompletionListener {
+ doStagefrightTestMediaPlayer(rid);
+ doStagefrightTestMediaCodec(rid);
+ }
+
+ private Surface getDummySurface() {
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_MIN_FILTER,
+ GLES20.GL_NEAREST);
+ GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_MAG_FILTER,
+ GLES20.GL_LINEAR);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_WRAP_S,
+ GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+ GLES20.GL_TEXTURE_WRAP_T,
+ GLES20.GL_CLAMP_TO_EDGE);
+ SurfaceTexture surfaceTex = new SurfaceTexture(textures[0]);
+ surfaceTex.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
- public boolean onError(MediaPlayer mp, int newWhat, int extra) {
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ Log.i(TAG, "new frame available");
+ }
+ });
+ return new Surface(surfaceTex);
+ }
+
+ class MediaPlayerCrashListener
+ implements MediaPlayer.OnErrorListener,
+ MediaPlayer.OnPreparedListener,
+ MediaPlayer.OnCompletionListener {
+ @Override
+ public boolean onError(MediaPlayer mp, int newWhat, int extra) {
+ Log.i(TAG, "error: " + newWhat + "/" + extra);
+ // don't overwrite a more severe error with a less severe one
+ if (what != MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
what = newWhat;
- lock.lock();
- condition.signal();
- lock.unlock();
-
- return true; // don't call oncompletion
}
+ lock.lock();
+ condition.signal();
+ lock.unlock();
- @Override
- public void onPrepared(MediaPlayer mp) {
- mp.start();
- }
-
- @Override
- public void onCompletion(MediaPlayer mp) {
- what = 0;
- lock.lock();
- condition.signal();
- lock.unlock();
- }
-
- public int waitForError() throws InterruptedException {
- lock.lock();
- if (condition.awaitNanos(TIMEOUT_NS) <= 0) {
- Log.d(TAG, "timed out on waiting for error");
- }
- lock.unlock();
- return what;
- }
-
- ReentrantLock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
- int what;
+ return true; // don't call oncompletion
}
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ mp.start();
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ // preserve error condition, if any
+ lock.lock();
+ condition.signal();
+ lock.unlock();
+ }
+
+ public int waitForError() throws InterruptedException {
+ lock.lock();
+ if (condition.awaitNanos(TIMEOUT_NS) <= 0) {
+ Log.d(TAG, "timed out on waiting for error");
+ }
+ lock.unlock();
+ if (what != 0) {
+ // Sometimes mediaserver signals a decoding error first, and *then* crashes
+ // due to additional in-flight buffers being processed, so wait a little
+ // and see if more errors show up.
+ SystemClock.sleep(1000);
+ }
+ return what;
+ }
+
+ ReentrantLock lock = new ReentrantLock();
+ Condition condition = lock.newCondition();
+ int what;
+ }
+
+ class LooperThread extends Thread {
+ private Looper mLooper;
+
+ LooperThread(Runnable runner) {
+ super(runner);
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ super.run();
+ }
+
+ public void stopLooper() {
+ mLooper.quitSafely();
+ }
+ }
+
+ private void doStagefrightTestMediaPlayer(final int rid) throws Exception {
+
+ String name = getInstrumentation().getContext().getResources().getResourceEntryName(rid);
+ Log.i(TAG, "start mediaplayer test for: " + name);
+
final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
- Thread t = new Thread(new Runnable() {
+ LooperThread t = new LooperThread(new Runnable() {
@Override
public void run() {
- Looper.prepare();
MediaPlayer mp = new MediaPlayer();
mp.setOnErrorListener(mpcl);
mp.setOnPreparedListener(mpcl);
mp.setOnCompletionListener(mpcl);
+ Surface surface = getDummySurface();
+ mp.setSurface(surface);
try {
AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
.openRawResourceFd(rid);
@@ -195,10 +282,148 @@
});
t.start();
- String name = getInstrumentation().getContext().getResources().getResourceEntryName(rid);
String cve = name.replace("_", "-").toUpperCase();
assertFalse("Device *IS* vulnerable to " + cve,
mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
- t.interrupt();
+ t.stopLooper();
+ t.join(); // wait for thread to exit so we're sure the player was released
+ }
+
+ private void doStagefrightTestMediaCodec(final int rid) throws Exception {
+
+ final MediaPlayerCrashListener mpcl = new MediaPlayerCrashListener();
+
+ LooperThread thr = new LooperThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MediaPlayer mp = new MediaPlayer();
+ mp.setOnErrorListener(mpcl);
+ try {
+ AssetFileDescriptor fd = getInstrumentation().getContext().getResources()
+ .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());
+ } 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();
+ }
+
+ Resources resources = getInstrumentation().getContext().getResources();
+ AssetFileDescriptor fd = resources.openRawResourceFd(rid);
+ MediaExtractor ex = new MediaExtractor();
+ try {
+ ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+ } catch (IOException e) {
+ // ignore
+ }
+ int numtracks = ex.getTrackCount();
+ String rname = resources.getResourceEntryName(rid);
+ Log.i(TAG, "start mediacodec test for: " + rname + ", which has " + numtracks + " tracks");
+ for (int t = 0; t < numtracks; t++) {
+ // find all the available decoders for this format
+ ArrayList<String> matchingCodecs = new ArrayList<String>();
+ MediaFormat format = null;
+ try {
+ format = ex.getTrackFormat(t);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "could not get track format for track " + t);
+ continue;
+ }
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ 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 track " + t + ", type " + mime);
+ }
+ // decode this track once with each matching codec
+ ex.selectTrack(t);
+ for (String codecName: matchingCodecs) {
+ Log.i(TAG, "Decoding track " + t + " using codec " + codecName);
+ ex.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+ MediaCodec codec = MediaCodec.createByCodecName(codecName);
+ Surface surface = null;
+ if (mime.startsWith("video/")) {
+ surface = getDummySurface();
+ }
+ try {
+ codec.configure(format, surface, null, 0);
+ codec.start();
+ } catch (Exception e) {
+ Log.i(TAG, "Failed to start/configure:", e);
+ }
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ try {
+ ByteBuffer [] inputBuffers = codec.getInputBuffers();
+ while (true) {
+ int flags = ex.getSampleFlags();
+ long time = ex.getSampleTime();
+ int bufidx = codec.dequeueInputBuffer(5000);
+ if (bufidx >= 0) {
+ int n = ex.readSampleData(inputBuffers[bufidx], 0);
+ if (n < 0) {
+ flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ time = 0;
+ n = 0;
+ }
+ codec.queueInputBuffer(bufidx, 0, n, time, flags);
+ ex.advance();
+ }
+ int status = codec.dequeueOutputBuffer(info, 5000);
+ if (status >= 0) {
+ if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ break;
+ }
+ if (info.presentationTimeUs > TIMEOUT_NS / 1000) {
+ Log.d(TAG, "stopping after 10 seconds worth of data");
+ break;
+ }
+ codec.releaseOutputBuffer(status, true);
+ }
+ }
+ } catch (Exception e) {
+ // local exceptions ignored, not security issues
+ } finally {
+ codec.release();
+ }
+ }
+ }
+ ex.release();
+ String cve = rname.replace("_", "-").toUpperCase();
+ assertFalse("Device *IS* vulnerable to " + cve,
+ mpcl.waitForError() == MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+ thr.stopLooper();
+ thr.join();
}
}