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