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