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 
17 package android.security.cts;
18 
19 import android.media.audiofx.AudioEffect;
20 import android.media.audiofx.EnvironmentalReverb;
21 import android.media.audiofx.Equalizer;
22 import android.media.audiofx.PresetReverb;
23 import android.media.MediaPlayer;
24 import android.platform.test.annotations.AsbSecurityTest;
25 import android.test.InstrumentationTestCase;
26 import android.util.Log;
27 
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.nio.charset.StandardCharsets;
31 import java.util.Arrays;
32 import java.util.UUID;
33 
34 public class EffectBundleTest extends InstrumentationTestCase {
35     private static final String TAG = "EffectBundleTest";
36     private static final int[] INVALID_BAND_ARRAY = {Integer.MIN_VALUE, -10000, -100, -2, -1};
37     private static final int mValue0 = 9999; //unlikely values. Should not change
38     private static final int mValue1 = 13877;
39     private static final int PRESET_CUSTOM = -1; //keep in sync AudioEqualizer.h
40 
41     private static final int MEDIA_SHORT = 0;
42     private static final int MEDIA_LONG = 1;
43 
44     // should match audio_effect.h (native)
45     private static final int EFFECT_CMD_SET_PARAM = 5;
46 
47     private static final int intSize = 4;
48 
49     //Testing security bug: 32436341
50     @AsbSecurityTest(cveBugId = 32436341)
testEqualizer_getParamCenterFreq()51     public void testEqualizer_getParamCenterFreq() throws Exception {
52         if (!hasEqualizer()) {
53             return;
54         }
55         testGetParam(MEDIA_SHORT, Equalizer.PARAM_CENTER_FREQ, INVALID_BAND_ARRAY, mValue0,
56                 mValue1);
57     }
58 
59     //Testing security bug: 32588352
60     @AsbSecurityTest(cveBugId = 32588352)
testEqualizer_getParamCenterFreq_long()61     public void testEqualizer_getParamCenterFreq_long() throws Exception {
62         if (!hasEqualizer()) {
63             return;
64         }
65         testGetParam(MEDIA_LONG, Equalizer.PARAM_CENTER_FREQ, INVALID_BAND_ARRAY, mValue0, mValue1);
66     }
67 
68     //Testing security bug: 32438598
69     @AsbSecurityTest(cveBugId = 32438598)
testEqualizer_getParamBandLevel()70     public void testEqualizer_getParamBandLevel() throws Exception {
71         if (!hasEqualizer()) {
72             return;
73         }
74         testGetParam(MEDIA_SHORT, Equalizer.PARAM_BAND_LEVEL, INVALID_BAND_ARRAY, mValue0, mValue1);
75     }
76 
77     //Testing security bug: 32584034
78     @AsbSecurityTest(cveBugId = 32584034)
testEqualizer_getParamBandLevel_long()79     public void testEqualizer_getParamBandLevel_long() throws Exception {
80         if (!hasEqualizer()) {
81             return;
82         }
83         testGetParam(MEDIA_LONG, Equalizer.PARAM_BAND_LEVEL, INVALID_BAND_ARRAY, mValue0, mValue1);
84     }
85 
86     //Testing security bug: 32247948
87     @AsbSecurityTest(cveBugId = 32247948)
testEqualizer_getParamFreqRange()88     public void testEqualizer_getParamFreqRange() throws Exception {
89         if (!hasEqualizer()) {
90             return;
91         }
92         testGetParam(MEDIA_SHORT, Equalizer.PARAM_BAND_FREQ_RANGE, INVALID_BAND_ARRAY, mValue0,
93                 mValue1);
94     }
95 
96     //Testing security bug: 32588756
97     @AsbSecurityTest(cveBugId = 32588756)
testEqualizer_getParamFreqRange_long()98     public void testEqualizer_getParamFreqRange_long() throws Exception {
99         if (!hasEqualizer()) {
100             return;
101         }
102         testGetParam(MEDIA_LONG, Equalizer.PARAM_BAND_FREQ_RANGE, INVALID_BAND_ARRAY, mValue0,
103                 mValue1);
104     }
105 
106     //Testing security bug: 32448258
107     @AsbSecurityTest(cveBugId = 32448258)
testEqualizer_getParamPresetName()108     public void testEqualizer_getParamPresetName() throws Exception {
109         if (!hasEqualizer()) {
110             return;
111         }
112         testParamPresetName(MEDIA_SHORT);
113     }
114 
115     //Testing security bug: 32588016
116     @AsbSecurityTest(cveBugId = 32588016)
testEqualizer_getParamPresetName_long()117     public void testEqualizer_getParamPresetName_long() throws Exception {
118         if (!hasEqualizer()) {
119             return;
120         }
121         testParamPresetName(MEDIA_LONG);
122     }
123 
testParamPresetName(int media)124     private void testParamPresetName(int media) {
125         final int command = Equalizer.PARAM_GET_PRESET_NAME;
126         for (int invalidBand : INVALID_BAND_ARRAY)
127         {
128             final byte testValue = 7;
129             byte reply[] = new byte[Equalizer.PARAM_STRING_SIZE_MAX];
130             Arrays.fill(reply, testValue);
131             if (!eqGetParam(media, command, invalidBand, reply)) {
132                 fail("getParam PARAM_GET_PRESET_NAME did not complete successfully");
133             }
134             //Compare
135             if (invalidBand == PRESET_CUSTOM) {
136                 final String expectedName = "Custom";
137                 int length = 0;
138                 while (reply[length] != 0) length++;
139                 try {
140                     final String presetName =  new String(reply, 0, length,
141                             StandardCharsets.ISO_8859_1.name());
142                     assertEquals("getPresetName custom preset name failed", expectedName,
143                             presetName);
144                 } catch (Exception e) {
145                     Log.w(TAG,"Problem creating reply string.");
146                 }
147             } else {
148                 for (int i = 0; i < reply.length; i++) {
149                     assertEquals(String.format("getParam should not change reply at byte %d", i),
150                             testValue, reply[i]);
151                 }
152             }
153         }
154     }
155 
156     //testing security bug: 32095626
157     @AsbSecurityTest(cveBugId = 32095626)
testEqualizer_setParamBandLevel()158     public void testEqualizer_setParamBandLevel() throws Exception {
159         if (!hasEqualizer()) {
160             return;
161         }
162         final int command = Equalizer.PARAM_BAND_LEVEL;
163         short[] value = { 1000 };
164         for (int invalidBand : INVALID_BAND_ARRAY)
165         {
166             if (!eqSetParam(MEDIA_SHORT, command, invalidBand, value)) {
167                 fail("setParam PARAM_BAND_LEVEL did not complete successfully");
168             }
169         }
170     }
171 
172     //testing security bug: 32585400
173     @AsbSecurityTest(cveBugId = 32585400)
testEqualizer_setParamBandLevel_long()174     public void testEqualizer_setParamBandLevel_long() throws Exception {
175         if (!hasEqualizer()) {
176             return;
177         }
178         final int command = Equalizer.PARAM_BAND_LEVEL;
179         short[] value = { 1000 };
180         for (int invalidBand : INVALID_BAND_ARRAY)
181         {
182             if (!eqSetParam(MEDIA_LONG, command, invalidBand, value)) {
183                 fail("setParam PARAM_BAND_LEVEL did not complete successfully");
184             }
185         }
186     }
187 
188     //testing security bug: 32705438
189     @AsbSecurityTest(cveBugId = 32705438)
testEqualizer_getParamFreqRangeCommand_short()190     public void testEqualizer_getParamFreqRangeCommand_short() throws Exception {
191         if (!hasEqualizer()) {
192             return;
193         }
194         assertTrue("testEqualizer_getParamFreqRangeCommand_short did not complete successfully",
195                 eqGetParamFreqRangeCommand(MEDIA_SHORT));
196     }
197 
198     //testing security bug: 32703959
199     @AsbSecurityTest(cveBugId = 32703959)
testEqualizer_getParamFreqRangeCommand_long()200     public void testEqualizer_getParamFreqRangeCommand_long() throws Exception {
201         if (!hasEqualizer()) {
202             return;
203         }
204         assertTrue("testEqualizer_getParamFreqRangeCommand_long did not complete successfully",
205                 eqGetParamFreqRangeCommand(MEDIA_LONG));
206     }
207 
208     //testing security bug: 37563371 (short media)
209     @AsbSecurityTest(cveBugId = 37563371)
testEqualizer_setParamProperties_short()210     public void testEqualizer_setParamProperties_short() throws Exception {
211         if (!hasEqualizer()) {
212             return;
213         }
214         assertTrue("testEqualizer_setParamProperties_long did not complete successfully",
215                 eqSetParamProperties(MEDIA_SHORT));
216     }
217 
218     //testing security bug: 37563371 (long media)
219     @AsbSecurityTest(cveBugId = 37563371)
testEqualizer_setParamProperties_long()220     public void testEqualizer_setParamProperties_long() throws Exception {
221         if (!hasEqualizer()) {
222             return;
223         }
224         assertTrue("testEqualizer_setParamProperties_long did not complete successfully",
225                 eqSetParamProperties(MEDIA_LONG));
226     }
227 
228     //Testing security bug: 63662938
229     @AsbSecurityTest(cveBugId = 63662938)
testDownmix_setParameter()230     public void testDownmix_setParameter() throws Exception {
231         verifyZeroPVSizeRejectedForSetParameter(
232                 EFFECT_TYPE_DOWNMIX, new int[] { DOWNMIX_PARAM_TYPE });
233     }
234 
235     /**
236      * Definitions for the downmix effect. Taken from
237      * system/media/audio/include/system/audio_effects/effect_downmix.h
238      * This effect is normally not exposed to applications.
239      */
240     private static final UUID EFFECT_TYPE_DOWNMIX = UUID
241             .fromString("381e49cc-a858-4aa2-87f6-e8388e7601b2");
242     private static final int DOWNMIX_PARAM_TYPE = 0;
243 
244     //Testing security bug: 63526567
245     @AsbSecurityTest(cveBugId = 63526567)
testEnvironmentalReverb_setParameter()246     public void testEnvironmentalReverb_setParameter() throws Exception {
247         verifyZeroPVSizeRejectedForSetParameter(
248                 AudioEffect.EFFECT_TYPE_ENV_REVERB, new int[] {
249                   EnvironmentalReverb.PARAM_ROOM_LEVEL,
250                   EnvironmentalReverb.PARAM_ROOM_HF_LEVEL,
251                   EnvironmentalReverb.PARAM_DECAY_TIME,
252                   EnvironmentalReverb.PARAM_DECAY_HF_RATIO,
253                   EnvironmentalReverb.PARAM_REFLECTIONS_LEVEL,
254                   EnvironmentalReverb.PARAM_REFLECTIONS_DELAY,
255                   EnvironmentalReverb.PARAM_REVERB_LEVEL,
256                   EnvironmentalReverb.PARAM_REVERB_DELAY,
257                   EnvironmentalReverb.PARAM_DIFFUSION,
258                   EnvironmentalReverb.PARAM_DENSITY,
259                   10 // EnvironmentalReverb.PARAM_PROPERTIES
260                 }
261         );
262     }
263 
264     //Testing security bug: 67647856
265     @AsbSecurityTest(cveBugId = 67647856)
testPresetReverb_setParameter()266     public void testPresetReverb_setParameter() throws Exception {
267         verifyZeroPVSizeRejectedForSetParameter(
268                 AudioEffect.EFFECT_TYPE_PRESET_REVERB, new int[] {
269                   PresetReverb.PARAM_PRESET
270                 }
271         );
272     }
273 
eqSetParamProperties(int media)274     private boolean eqSetParamProperties(int media) {
275         MediaPlayer mp = null;
276         Equalizer eq = null;
277         boolean status = false;
278         try {
279             mp = MediaPlayer.create(getInstrumentation().getContext(),  getMediaId(media));
280             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
281 
282             int shortSize = 2; //bytes
283 
284             int cmdCode = EFFECT_CMD_SET_PARAM;
285             byte command[] = concatArrays(/*status*/ intToByteArray(0),
286                     /*psize*/ intToByteArray(1 * intSize),
287                     /*vsize*/ intToByteArray(2 * shortSize),
288                     /*data[0]*/ intToByteArray((int) 9 /*EQ_PARAM_PROPERTIES*/),
289                     /*data[4]*/ shortToByteArray((short)-1 /*preset*/),
290                     /*data[6]*/ shortToByteArray((short)5 /*FIVEBAND_NUMBANDS*/));
291             byte reply[] = new byte[ 4 /*command.length*/];
292 
293             AudioEffect af = eq;
294             Object o = AudioEffect.class.getDeclaredMethod("command", int.class, byte[].class,
295                     byte[].class).invoke(af, cmdCode, command, reply);
296 
297             int replyValue = byteArrayToInt(reply, 0 /*offset*/);
298             if (replyValue >= 0) {
299                 Log.w(TAG, "Reply Value: " + replyValue);
300             }
301             assertTrue("Negative replyValue was expected ", replyValue < 0);
302             status = true;
303         } catch (Exception e) {
304             Log.w(TAG,"Problem setting parameter in equalizer");
305         } finally {
306             if (eq != null) {
307                 eq.release();
308             }
309             if (mp != null) {
310                 mp.release();
311             }
312         }
313         return status;
314     }
315 
eqGetParamFreqRangeCommand(int media)316     private boolean eqGetParamFreqRangeCommand(int media) {
317         MediaPlayer mp = null;
318         Equalizer eq = null;
319         boolean status = false;
320         try {
321             mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
322             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
323 
324             short band = 2;
325 
326             //baseline
327             int cmdCode = 8; // EFFECT_CMD_GET_PARAM
328             byte command[] = concatArrays(/*status*/ intToByteArray(0),
329                     /*psize*/ intToByteArray(2 * intSize),
330                     /*vsize*/ intToByteArray(2 * intSize),
331                     /*data[0]*/ intToByteArray(Equalizer.PARAM_BAND_FREQ_RANGE),
332                     /*data[1]*/ intToByteArray((int) band));
333 
334             byte reply[] = new byte[command.length];
335 
336             AudioEffect af = eq;
337             Object o = AudioEffect.class.getDeclaredMethod("command", int.class, byte[].class,
338                     byte[].class).invoke(af, cmdCode, command, reply);
339 
340             int methodStatus = AudioEffect.ERROR;
341             if (o != null) {
342                 methodStatus = Integer.valueOf(o.toString()).intValue();
343             }
344 
345             assertTrue("Command expected to fail", methodStatus <= 0);
346             int sum = 0;
347             for (int i = 0; i < reply.length; i++) {
348                 sum += Math.abs(reply[i]);
349             }
350 
351             assertEquals("reply expected to be all zeros", sum, 0);
352             status = true;
353         } catch (Exception e) {
354             Log.w(TAG,"Problem testing eqGetParamFreqRangeCommand");
355             status = false;
356         } finally {
357             if (eq != null) {
358                 eq.release();
359             }
360             if (mp != null) {
361                 mp.release();
362             }
363         }
364         return status;
365     }
366 
eqGetParam(int media, int command, int band, byte[] reply)367     private boolean eqGetParam(int media, int command, int band, byte[] reply) {
368         MediaPlayer mp = null;
369         Equalizer eq = null;
370         boolean status = false;
371         try {
372             mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
373             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
374 
375             AudioEffect af = eq;
376             int cmd[] = {command, band};
377 
378             AudioEffect.class.getDeclaredMethod("getParameter", int[].class,
379                     byte[].class).invoke(af, cmd, reply);
380             status = true;
381         } catch (Exception e) {
382             Log.w(TAG,"Problem testing equalizer");
383             status = false;
384         } finally {
385             if (eq != null) {
386                 eq.release();
387             }
388             if (mp != null) {
389                 mp.release();
390             }
391         }
392         return status;
393     }
394 
eqGetParam(int media, int command, int band, int[] reply)395     private boolean eqGetParam(int media, int command, int band, int[] reply) {
396         MediaPlayer mp = null;
397         Equalizer eq = null;
398         boolean status = false;
399         try {
400             mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
401             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
402 
403             AudioEffect af = eq;
404             int cmd[] = {command, band};
405 
406             AudioEffect.class.getDeclaredMethod("getParameter", int[].class,
407                     int[].class).invoke(af, cmd, reply);
408             status = true;
409         } catch (Exception e) {
410             Log.w(TAG,"Problem getting parameter from equalizer");
411             status = false;
412         } finally {
413             if (eq != null) {
414                 eq.release();
415             }
416             if (mp != null) {
417                 mp.release();
418             }
419         }
420         return status;
421     }
422 
testGetParam(int media, int command, int[] bandArray, int value0, int value1)423     private void testGetParam(int media, int command, int[] bandArray, int value0, int value1) {
424         int reply[] = {value0, value1};
425         for (int invalidBand : INVALID_BAND_ARRAY)
426         {
427             if (!eqGetParam(media, command, invalidBand, reply)) {
428                 fail(String.format("getParam for command %d did not complete successfully",
429                         command));
430             }
431             assertEquals("getParam should not change value0", value0, reply[0]);
432             assertEquals("getParam should not change value1", value1, reply[1]);
433         }
434     }
435 
eqSetParam(int media, int command, int band, short[] value)436     private boolean eqSetParam(int media, int command, int band, short[] value) {
437         MediaPlayer mp = null;
438         Equalizer eq = null;
439         boolean status = false;
440         try {
441             mp = MediaPlayer.create(getInstrumentation().getContext(),  getMediaId(media));
442             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
443 
444             AudioEffect af = eq;
445             int cmd[] = {command, band};
446 
447             AudioEffect.class.getDeclaredMethod("setParameter", int[].class,
448                     short[].class).invoke(af, cmd, value);
449             status = true;
450         } catch (Exception e) {
451             Log.w(TAG,"Problem setting parameter in equalizer");
452             status = false;
453         } finally {
454             if (eq != null) {
455                 eq.release();
456             }
457             if (mp != null) {
458                 mp.release();
459             }
460         }
461         return status;
462     }
463 
getMediaId(int media)464     private int getMediaId(int media) {
465         switch (media) {
466             default:
467             case MEDIA_SHORT:
468                 return R.raw.good;
469             case MEDIA_LONG:
470                 return R.raw.onekhzsine_90sec;
471         }
472     }
473 
474     // Verifies that for all the effects of the specified type
475     // an attempt to pass psize = 0 or vsize = 0 to 'set parameter' command
476     // is rejected by the effect.
verifyZeroPVSizeRejectedForSetParameter( UUID effectType, final int paramCodes[])477     private void verifyZeroPVSizeRejectedForSetParameter(
478             UUID effectType, final int paramCodes[]) throws Exception {
479 
480         boolean effectFound = false;
481         for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
482             if (descriptor.type.compareTo(effectType) != 0) continue;
483 
484             effectFound = true;
485             AudioEffect ae = null;
486             MediaPlayer mp = null;
487             try {
488                 mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
489                 java.lang.reflect.Constructor ct = AudioEffect.class.getConstructor(
490                         UUID.class, UUID.class, int.class, int.class);
491                 try {
492                     ae = (AudioEffect) ct.newInstance(descriptor.type, descriptor.uuid,
493                             /*priority*/ 0, mp.getAudioSessionId());
494                 } catch (Exception e) {
495                     // Not every effect can be instantiated by apps.
496                     Log.w(TAG, "Failed to create effect " + descriptor.uuid);
497                     continue;
498                 }
499                 java.lang.reflect.Method command = AudioEffect.class.getDeclaredMethod(
500                         "command", int.class, byte[].class, byte[].class);
501                 for (int paramCode : paramCodes) {
502                     executeSetParameter(ae, command, intSize, 0, paramCode);
503                     executeSetParameter(ae, command, 0, intSize, paramCode);
504                 }
505             } finally {
506                 if (ae != null) {
507                     ae.release();
508                 }
509                 if (mp != null) {
510                     mp.release();
511                 }
512             }
513         }
514 
515         if (!effectFound) {
516             Log.w(TAG, "No effect with type " + effectType + " was found");
517         }
518     }
519 
executeSetParameter(AudioEffect ae, java.lang.reflect.Method command, int paramSize, int valueSize, int paramCode)520     private void executeSetParameter(AudioEffect ae, java.lang.reflect.Method command,
521             int paramSize, int valueSize, int paramCode) throws Exception {
522         byte cmdBuf[] = concatArrays(/*status*/ intToByteArray(0),
523                 /*psize*/ intToByteArray(paramSize),
524                 /*vsize*/ intToByteArray(valueSize),
525                 /*data[0]*/ intToByteArray(paramCode));
526         byte reply[] = new byte[intSize];
527         Integer ret = (Integer)command.invoke(ae, EFFECT_CMD_SET_PARAM, cmdBuf, reply);
528         if (ret >= 0) {
529             int val = byteArrayToInt(reply, 0 /*offset*/);
530             assertTrue("Negative reply value expected, effect " + ae.getDescriptor().uuid +
531                     ", parameter " + paramCode + ", reply value " + val,
532                     val < 0);
533         } else {
534             // Some effect implementations detect this condition at the command dispatch level,
535             // and reject command execution. That's also OK, but log a message so the test
536             // author can double check if 'paramCode' is correct.
537             Log.w(TAG, "\"Set parameter\" command rejected for effect " + ae.getDescriptor().uuid +
538                     ", parameter " + paramCode + ", return code " + ret);
539         }
540     }
541 
hasEqualizer()542     private boolean hasEqualizer() {
543         boolean result = false;
544         try {
545             MediaPlayer mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
546             new Equalizer(0 /*priority*/, mp.getAudioSessionId());
547             result = true;
548         } catch (Exception e) {
549             Log.d(TAG, "Cannot create equalizer");
550         }
551         return result;
552     }
553 
intToByteArray(int value)554     private static byte[] intToByteArray(int value) {
555         ByteBuffer converter = ByteBuffer.allocate(4);
556         converter.order(ByteOrder.nativeOrder());
557         converter.putInt(value);
558         return converter.array();
559     }
560 
byteArrayToInt(byte[] valueBuf, int offset)561     public static int byteArrayToInt(byte[] valueBuf, int offset) {
562         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
563         converter.order(ByteOrder.nativeOrder());
564         return converter.getInt(offset);
565     }
566 
shortToByteArray(short value)567     private static byte[] shortToByteArray(short value) {
568         ByteBuffer converter = ByteBuffer.allocate(2);
569         converter.order(ByteOrder.nativeOrder());
570         short sValue = (short) value;
571         converter.putShort(sValue);
572         return converter.array();
573     }
574 
concatArrays(byte[]... arrays)575     private static  byte[] concatArrays(byte[]... arrays) {
576         int len = 0;
577         for (byte[] a : arrays) {
578             len += a.length;
579         }
580         byte[] b = new byte[len];
581 
582         int offs = 0;
583         for (byte[] a : arrays) {
584             System.arraycopy(a, 0, b, offs, a.length);
585             offs += a.length;
586         }
587         return b;
588     }
589 }
590