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