Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2016 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 package android.security.cts;
     17 
     18 import android.media.AudioFormat;
     19 import android.media.AudioManager;
     20 import android.media.AudioTrack;
     21 import android.media.audiofx.AudioEffect;
     22 import android.media.audiofx.Equalizer;
     23 import android.platform.test.annotations.SecurityTest;
     24 import android.util.Log;
     25 
     26 import com.android.compatibility.common.util.CtsAndroidTestCase;
     27 
     28 import java.nio.ByteBuffer;
     29 import java.nio.ByteOrder;
     30 import java.util.Arrays;
     31 import java.util.UUID;
     32 
     33 @SecurityTest
     34 public class AudioSecurityTest extends CtsAndroidTestCase {
     35     private static final String TAG = "AudioSecurityTest";
     36 
     37     private static final int ERROR_DEAD_OBJECT = -7; // AudioEffect.ERROR_DEAD_OBJECT
     38 
     39     // should match audio_effect.h (native)
     40     private static final int EFFECT_CMD_SET_PARAM = 5;
     41     private static final int EFFECT_CMD_GET_PARAM = 8;
     42     private static final int EFFECT_CMD_OFFLOAD   = 20;
     43     private static final int SIZEOF_EFFECT_PARAM_T = 12;
     44 
     45     private static void verifyZeroReply(byte[] reply) throws Exception {
     46         int count = 0;
     47         for (byte b : reply) {
     48             if (b != 0) {
     49                 count++;
     50             }
     51         }
     52         assertEquals("reply has " + count + " nonzero values", 0 /* expected */, count);
     53     }
     54 
     55     // @FunctionalInterface
     56     private interface TestEffect {
     57         void test(AudioEffect audioEffect) throws Exception;
     58     }
     59 
     60     private static void testAllEffects(String testName, TestEffect testEffect) throws Exception {
     61         int failures = 0;
     62         for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
     63             final AudioEffect audioEffect;
     64             try {
     65                 audioEffect = (AudioEffect)AudioEffect.class.getConstructor(
     66                         UUID.class, UUID.class, int.class, int.class).newInstance(
     67                                 descriptor.type,
     68                                 descriptor.uuid, // uuid overrides type
     69                                 0 /* priority */, 0 /* audioSession */);
     70             } catch (Exception e) {
     71                 Log.w(TAG, "effect " + testName + " " + descriptor.name
     72                         + " cannot be created (ignoring)");
     73                 continue; // OK;
     74             }
     75             try {
     76                 testEffect.test(audioEffect);
     77                 Log.d(TAG, "effect " + testName + " " + descriptor.name + " success");
     78             } catch (Exception e) {
     79                 Log.e(TAG, "effect " + testName + " " + descriptor.name + " exception failed!",
     80                         e);
     81                 ++failures;
     82             } catch (AssertionError e) {
     83                 Log.e(TAG, "effect " + testName + " " + descriptor.name + " assert failed!",
     84                         e);
     85                 ++failures;
     86             }
     87         }
     88         assertEquals("found " + testName + " " + failures + " failures",
     89                 0 /* expected */, failures);
     90     }
     91 
     92     // b/28173666
     93     public void testAllEffectsGetParameterAttemptOffload_CVE_2016_3745() throws Exception {
     94         testAllEffects("get parameter attempt offload",
     95                 new TestEffect() {
     96             @Override
     97             public void test(AudioEffect audioEffect) throws Exception {
     98                 testAudioEffectGetParameter(audioEffect, true /* offload */);
     99             }
    100         });
    101     }
    102 
    103     // b/32438594
    104     // b/32624850
    105     // b/32635664
    106     public void testAllEffectsGetParameter2AttemptOffload_CVE_2017_0398() throws Exception {
    107         testAllEffects("get parameter2 attempt offload",
    108                 new TestEffect() {
    109             @Override
    110             public void test(AudioEffect audioEffect) throws Exception {
    111                 testAudioEffectGetParameter2(audioEffect, true /* offload */);
    112             }
    113         });
    114     }
    115 
    116     // b/30204301
    117     public void testAllEffectsSetParameterAttemptOffload_CVE_2016_3924() throws Exception {
    118         testAllEffects("set parameter attempt offload",
    119                 new TestEffect() {
    120             @Override
    121             public void test(AudioEffect audioEffect) throws Exception {
    122                 testAudioEffectSetParameter(audioEffect, true /* offload */);
    123             }
    124         });
    125     }
    126 
    127     // b/37536407
    128     public void testAllEffectsEqualizer_CVE_2017_0401() throws Exception {
    129         testAllEffects("equalizer get parameter name",
    130                 new TestEffect() {
    131             @Override
    132             public void test(AudioEffect audioEffect) throws Exception {
    133                 testAudioEffectEqualizerGetParameterName(audioEffect);
    134             }
    135         });
    136     }
    137 
    138     private static void testAudioEffectGetParameter(
    139             AudioEffect audioEffect, boolean offload) throws Exception {
    140         if (audioEffect == null) {
    141             return;
    142         }
    143         try {
    144             // 1) set offload_enabled
    145             if (offload) {
    146                 byte command[] = new byte[8];
    147                 Arrays.fill(command, (byte)1);
    148                 byte reply[] = new byte[4]; // ignored
    149 
    150                 /* ignored */ AudioEffect.class.getDeclaredMethod(
    151                         "command", int.class, byte[].class, byte[].class).invoke(
    152                                 audioEffect, EFFECT_CMD_OFFLOAD, command, reply);
    153             }
    154 
    155             // 2) get parameter with invalid psize
    156             {
    157                 byte command[] = new byte[30];
    158                 Arrays.fill(command, (byte)0xDD);
    159                 byte reply[] = new byte[30];
    160 
    161                 Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    162                         "command", int.class, byte[].class, byte[].class).invoke(
    163                                 audioEffect, EFFECT_CMD_GET_PARAM, command, reply);
    164 
    165                 assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    166                 verifyZeroReply(reply);
    167             }
    168 
    169             // NOTE: an alternative way of checking crash:
    170             //
    171             // Thread.sleep(1000 /* millis */);
    172             // assertTrue("Audio server might have crashed",
    173             //        audioEffect.setEnabled(false) != AudioEffect.ERROR_DEAD_OBJECT);
    174         } catch (NoSuchMethodException e) {
    175             Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
    176         } finally {
    177             audioEffect.release();
    178         }
    179     }
    180 
    181     private static void testAudioEffectGetParameter2(
    182             AudioEffect audioEffect, boolean offload) throws Exception {
    183         if (audioEffect == null) {
    184             return;
    185         }
    186         try {
    187             // 1) set offload_enabled
    188             if (offload) {
    189                 byte command[] = new byte[8];
    190                 Arrays.fill(command, (byte)1);
    191                 byte reply[] = new byte[4]; // ignored
    192 
    193                 /* ignored */ AudioEffect.class.getDeclaredMethod(
    194                         "command", int.class, byte[].class, byte[].class).invoke(
    195                                 audioEffect, EFFECT_CMD_OFFLOAD, command, reply);
    196             }
    197 
    198             // 2) get parameter with small command size but large psize
    199             {
    200                 final int parameterSize = 0x100000;
    201 
    202                 byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
    203                         .order(ByteOrder.nativeOrder())
    204                         .putInt(0)             // status (unused)
    205                         .putInt(parameterSize) // psize (very large)
    206                         .putInt(0)             // vsize
    207                         .putInt(0x04030201)    // data[0] (param too small for psize)
    208                         .putInt(0x08070605)    // data[4]
    209                         .array();
    210                 byte reply[] = new byte[parameterSize + SIZEOF_EFFECT_PARAM_T];
    211 
    212                 Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    213                         "command", int.class, byte[].class, byte[].class).invoke(
    214                                 audioEffect, EFFECT_CMD_GET_PARAM, command, reply);
    215 
    216                 verifyZeroReply(reply);
    217                 assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    218             }
    219         } catch (NoSuchMethodException e) {
    220             Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
    221         } finally {
    222             audioEffect.release();
    223         }
    224     }
    225 
    226     private static void testAudioEffectGetParameter3(AudioEffect audioEffect) throws Exception {
    227         if (audioEffect == null) {
    228             return;
    229         }
    230         try {
    231             // 1) get parameter with zero command size
    232             {
    233                 final int parameterSize = 0x10;
    234 
    235                 Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    236                         "command", int.class, byte[].class, byte[].class).invoke(
    237                                 audioEffect,
    238                                 EFFECT_CMD_GET_PARAM,
    239                                 new byte[0] /* command */,
    240                                 new byte[parameterSize + SIZEOF_EFFECT_PARAM_T] /* reply */);
    241 
    242                 assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    243             }
    244         } catch (NoSuchMethodException e) {
    245             Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
    246         } finally {
    247             audioEffect.release();
    248         }
    249     }
    250 
    251     private static void testAudioEffectSetParameter(
    252             AudioEffect audioEffect, boolean offload) throws Exception {
    253         if (audioEffect == null) {
    254             return;
    255         }
    256         try {
    257             // 1) set offload_enabled
    258             if (offload) {
    259                 byte command[] = new byte[8];
    260                 Arrays.fill(command, (byte)1);
    261                 byte reply[] = new byte[4]; // ignored
    262 
    263                 /* ignored */ AudioEffect.class.getDeclaredMethod(
    264                         "command", int.class, byte[].class, byte[].class).invoke(
    265                                 audioEffect, EFFECT_CMD_OFFLOAD, command, reply);
    266             }
    267 
    268             // 2) set parameter with invalid psize
    269             {
    270                 byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
    271                         .order(ByteOrder.nativeOrder())
    272                         .putInt(0)          // status (unused)
    273                         .putInt(0xdddddddd) // psize (very large)
    274                         .putInt(4)          // vsize
    275                         .putInt(1)          // data[0] (param too small for psize)
    276                         .putInt(0)          // data[4]
    277                         .array();
    278                 byte reply[] = new byte[4]; // returns status code (ignored)
    279 
    280                 Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    281                         "command", int.class, byte[].class, byte[].class).invoke(
    282                                 audioEffect, EFFECT_CMD_SET_PARAM, command, reply);
    283 
    284                 assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    285                 // on failure reply may contain the status code.
    286             }
    287         } catch (NoSuchMethodException e) {
    288             Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
    289         } finally {
    290             audioEffect.release();
    291         }
    292     }
    293 
    294     private static void testAudioEffectSetOffload(AudioEffect audioEffect) throws Exception {
    295         if (audioEffect == null) {
    296             return;
    297         }
    298         try {
    299             // 1) set offload_enabled with zero command and reply size
    300             {
    301                 Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    302                         "command", int.class, byte[].class, byte[].class).invoke(
    303                                 audioEffect,
    304                                 EFFECT_CMD_OFFLOAD,
    305                                 new byte[0] /* command */,
    306                                 new byte[0] /* reply */);
    307 
    308                 assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    309             }
    310          } catch (NoSuchMethodException e) {
    311             Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
    312         } finally {
    313             audioEffect.release();
    314         }
    315     }
    316 
    317     private static void testAudioEffectEqualizerGetParameterName(
    318             AudioEffect audioEffect) throws Exception {
    319         if (audioEffect == null) {
    320             return;
    321         }
    322         try {
    323             // get parameter name with zero vsize
    324             {
    325                 final int param = Equalizer.PARAM_GET_PRESET_NAME;
    326                 final int band = 0;
    327                 byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
    328                         .order(ByteOrder.nativeOrder())
    329                         .putInt(0)          // status (unused)
    330                         .putInt(8)          // psize (param, band)
    331                         .putInt(0)          // vsize
    332                         .putInt(param)      // equalizer param
    333                         .putInt(band)       // equalizer band
    334                         .array();
    335                 Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    336                         "command", int.class, byte[].class, byte[].class).invoke(
    337                                 audioEffect, EFFECT_CMD_GET_PARAM, command,
    338                                 new byte[5 * 4] /* reply - ignored */);
    339                 assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    340             }
    341         } catch (NoSuchMethodException e) {
    342             Log.w(TAG, "AudioEffect.command() does not exist (ignoring)"); // OK
    343         } finally {
    344             audioEffect.release();
    345         }
    346     }
    347 
    348     // should match effect_visualizer.h (native)
    349     private static final String VISUALIZER_TYPE = "e46b26a0-dddd-11db-8afd-0002a5d5c51b";
    350     private static final int VISUALIZER_CMD_CAPTURE = 0x10000;
    351     private static final int VISUALIZER_PARAM_CAPTURE_SIZE = 0;
    352 
    353     // b/31781965
    354     public void testVisualizerCapture_CVE_2017_0396() throws Exception {
    355         // Capture params
    356         final int CAPTURE_SIZE = 1 << 24; // 16MB seems to be large enough to cause a SEGV.
    357         final byte[] captureBuf = new byte[CAPTURE_SIZE];
    358 
    359         // Track params
    360         final int sampleRate = 48000;
    361         final int format = AudioFormat.ENCODING_PCM_16BIT;
    362         final int loops = 1;
    363         final int seconds = 1;
    364         final int channelCount = 2;
    365         final int bufferFrames = seconds * sampleRate;
    366         final int bufferSamples = bufferFrames * channelCount;
    367         final int bufferSize = bufferSamples * 2; // bytes per sample for 16 bits
    368         final short data[] = new short[bufferSamples]; // zero data
    369 
    370         for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
    371             if (descriptor.type.compareTo(UUID.fromString(VISUALIZER_TYPE)) != 0) {
    372                 continue;
    373             }
    374 
    375             AudioEffect audioEffect = null;
    376             AudioTrack audioTrack = null;
    377 
    378             try {
    379                 // create track and play
    380                 {
    381                     audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
    382                             AudioFormat.CHANNEL_OUT_STEREO, format, bufferSize,
    383                             AudioTrack.MODE_STATIC);
    384                     assertEquals("Cannot write to audio track",
    385                             bufferSamples,
    386                             audioTrack.write(data, 0 /* offsetInBytes */, data.length));
    387                     assertEquals("AudioTrack not initialized",
    388                             AudioTrack.STATE_INITIALIZED,
    389                             audioTrack.getState());
    390                     assertEquals("Cannot set loop points",
    391                             android.media.AudioTrack.SUCCESS,
    392                             audioTrack.setLoopPoints(0 /* startInFrames */, bufferFrames, loops));
    393                     audioTrack.play();
    394                 }
    395 
    396                 // wait for track to really begin playing
    397                 Thread.sleep(200 /* millis */);
    398 
    399                 // create effect
    400                 {
    401                     audioEffect = (AudioEffect) AudioEffect.class.getConstructor(
    402                             UUID.class, UUID.class, int.class, int.class).newInstance(
    403                                     descriptor.type, descriptor.uuid, 0 /* priority */,
    404                                     audioTrack.getAudioSessionId());
    405                 }
    406 
    407                 // set capture size
    408                 {
    409                     byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
    410                             .order(ByteOrder.nativeOrder())
    411                             .putInt(0)                             // status (unused)
    412                             .putInt(4)                             // psize (sizeof(param))
    413                             .putInt(4)                             // vsize (sizeof(value))
    414                             .putInt(VISUALIZER_PARAM_CAPTURE_SIZE) // data[0] (param)
    415                             .putInt(CAPTURE_SIZE)                  // data[4] (value)
    416                             .array();
    417 
    418                     Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    419                             "command", int.class, byte[].class, byte[].class).invoke(
    420                                     audioEffect,
    421                                     EFFECT_CMD_SET_PARAM,
    422                                     command, new byte[4] /* reply */);
    423                     Log.d(TAG, "setparam returns " + ret);
    424                     assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    425                 }
    426 
    427                 // enable effect
    428                 {
    429                     final int ret = audioEffect.setEnabled(true);
    430                     assertEquals("Cannot enable audio effect", 0 /* expected */, ret);
    431                 }
    432 
    433                 // wait for track audio data to be processed, otherwise capture
    434                 // will not really return audio data.
    435                 Thread.sleep(200 /* millis */);
    436 
    437                 // capture data
    438                 {
    439                     Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
    440                             "command", int.class, byte[].class, byte[].class).invoke(
    441                                     audioEffect,
    442                                     VISUALIZER_CMD_CAPTURE,
    443                                     new byte[0] /* command */, captureBuf /* reply */);
    444                     Log.d(TAG, "capture returns " + ret);
    445                     assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
    446                 }
    447             } finally {
    448                 if (audioEffect != null) {
    449                     audioEffect.release();
    450                 }
    451                 if (audioTrack != null) {
    452                     audioTrack.release();
    453                 }
    454             }
    455         }
    456     }
    457 }
    458