Skip to content

Commit d1e4a63

Browse files
ojw28icbaker
authored andcommitted
Zero out trailing bytes in CryptoInfo.iv
CryptoInfo.iv length is always 16. When the actual initialization vector is shorter, zero out the trailing bytes. Issue: google#6982 PiperOrigin-RevId: 295575845
1 parent 27bd129 commit d1e4a63

File tree

5 files changed

+114
-74
lines changed

5 files changed

+114
-74
lines changed

RELEASENOTES.md

+6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@
7171
show how to render video to a `GLSurfaceView` while applying a GL shader.
7272
([#6920](https://github.com/google/ExoPlayer/issues/6920)).
7373

74+
### 2.11.3 (2020-02-19) ###
75+
76+
* DRM: Fix issue switching from protected content that uses a 16-byte
77+
initialization vector to one that uses an 8-byte initialization vector
78+
([#6982](https://github.com/google/ExoPlayer/issues/6982)).
79+
7480
### 2.11.2 (2020-02-13) ###
7581

7682
* Add Java FLAC extractor

library/common/src/main/java/com/google/android/exoplayer2/decoder/CryptoInfo.java

+23-21
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,41 @@
2727
public final class CryptoInfo {
2828

2929
/**
30+
* The 16 byte initialization vector. If the initialization vector of the content is shorter than
31+
* 16 bytes, 0 byte padding is appended to extend the vector to the required 16 byte length.
32+
*
3033
* @see android.media.MediaCodec.CryptoInfo#iv
3134
*/
3235
public byte[] iv;
3336
/**
37+
* The 16 byte key id.
38+
*
3439
* @see android.media.MediaCodec.CryptoInfo#key
3540
*/
3641
public byte[] key;
3742
/**
43+
* The type of encryption that has been applied. Must be one of the {@link C.CryptoMode} values.
44+
*
3845
* @see android.media.MediaCodec.CryptoInfo#mode
3946
*/
40-
@C.CryptoMode
41-
public int mode;
47+
@C.CryptoMode public int mode;
4248
/**
49+
* The number of leading unencrypted bytes in each sub-sample. If null, all bytes are treated as
50+
* encrypted and {@link #numBytesOfEncryptedData} must be specified.
51+
*
4352
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
4453
*/
4554
public int[] numBytesOfClearData;
4655
/**
56+
* The number of trailing encrypted bytes in each sub-sample. If null, all bytes are treated as
57+
* clear and {@link #numBytesOfClearData} must be specified.
58+
*
4759
* @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData
4860
*/
4961
public int[] numBytesOfEncryptedData;
5062
/**
63+
* The number of subSamples that make up the buffer's contents.
64+
*
5165
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
5266
*/
5367
public int numSubSamples;
@@ -112,10 +126,10 @@ public void copyTo(android.media.MediaCodec.CryptoInfo cryptoInfo) {
112126
// Update cryptoInfo fields directly because CryptoInfo.set performs an unnecessary
113127
// object allocation on Android N.
114128
cryptoInfo.numSubSamples = numSubSamples;
115-
cryptoInfo.numBytesOfClearData = copyOrNull(frameworkCryptoInfo.numBytesOfClearData);
116-
cryptoInfo.numBytesOfEncryptedData = copyOrNull(frameworkCryptoInfo.numBytesOfEncryptedData);
117-
cryptoInfo.key = copyOrNull(frameworkCryptoInfo.key);
118-
cryptoInfo.iv = copyOrNull(frameworkCryptoInfo.iv);
129+
cryptoInfo.numBytesOfClearData = copyOrNull(numBytesOfClearData);
130+
cryptoInfo.numBytesOfEncryptedData = copyOrNull(numBytesOfEncryptedData);
131+
cryptoInfo.key = copyOrNull(key);
132+
cryptoInfo.iv = copyOrNull(iv);
119133
cryptoInfo.mode = mode;
120134
if (Util.SDK_INT >= 24) {
121135
android.media.MediaCodec.CryptoInfo.Pattern pattern = patternHolder.pattern;
@@ -148,31 +162,19 @@ public void increaseClearDataFirstSubSampleBy(int count) {
148162
if (count == 0) {
149163
return;
150164
}
151-
152165
if (numBytesOfClearData == null) {
153166
numBytesOfClearData = new int[1];
154-
}
155-
numBytesOfClearData[0] += count;
156-
157-
// It is OK to have numBytesOfClearData and frameworkCryptoInfo.numBytesOfClearData point to
158-
// the same array, see set().
159-
if (frameworkCryptoInfo.numBytesOfClearData == null) {
160167
frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData;
161168
}
162-
163-
// Update frameworkCryptoInfo.numBytesOfClearData only if it points to a different array than
164-
// numBytesOfClearData (all fields are public and non-final, therefore they can set be set
165-
// directly without calling set()). Otherwise, the array has been updated already in the steps
166-
// above.
167-
if (frameworkCryptoInfo.numBytesOfClearData != numBytesOfClearData) {
168-
frameworkCryptoInfo.numBytesOfClearData[0] += count;
169-
}
169+
numBytesOfClearData[0] += count;
170170
}
171171

172+
@Nullable
172173
private static int[] copyOrNull(@Nullable int[] array) {
173174
return array != null ? Arrays.copyOf(array, array.length) : null;
174175
}
175176

177+
@Nullable
176178
private static byte[] copyOrNull(@Nullable byte[] array) {
177179
return array != null ? Arrays.copyOf(array, array.length) : null;
178180
}

library/common/src/test/java/com/google/android/exoplayer2/decoder/CryptoInfoTest.java

-31
Original file line numberDiff line numberDiff line change
@@ -65,35 +65,4 @@ public void increaseClearDataFirstSubSampleBy_withSharedClearDataPointer_setsVal
6565
assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(6);
6666
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(6);
6767
}
68-
69-
@Test
70-
public void increaseClearDataFirstSubSampleBy_withDifferentClearDataArrays_setsValue() {
71-
cryptoInfo.numBytesOfClearData = new int[] {1, 1, 1, 1};
72-
cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData = new int[] {5, 5, 5, 5};
73-
74-
cryptoInfo.increaseClearDataFirstSubSampleBy(5);
75-
76-
assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(6);
77-
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(10);
78-
}
79-
80-
@Test
81-
public void increaseClearDataFirstSubSampleBy_withInternalClearDataArraysNull_setsValue() {
82-
cryptoInfo.numBytesOfClearData = new int[] {10, 10, 10, 10};
83-
84-
cryptoInfo.increaseClearDataFirstSubSampleBy(5);
85-
86-
assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(15);
87-
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(15);
88-
}
89-
90-
@Test
91-
public void increaseClearDataFirstSubSampleBy_internalClearDataIsNotNull_setsValue() {
92-
cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData = new int[] {5, 5, 5, 5};
93-
94-
cryptoInfo.increaseClearDataFirstSubSampleBy(5);
95-
96-
assertThat(cryptoInfo.numBytesOfClearData[0]).isEqualTo(5);
97-
assertThat(cryptoInfo.getFrameworkCryptoInfo().numBytesOfClearData[0]).isEqualTo(10);
98-
}
9968
}

library/core/src/main/java/com/google/android/exoplayer2/source/SampleDataQueue.java

+13-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import androidx.annotation.Nullable;
1919
import com.google.android.exoplayer2.C;
20+
import com.google.android.exoplayer2.decoder.CryptoInfo;
2021
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
2122
import com.google.android.exoplayer2.extractor.SampleDataReader;
2223
import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData;
@@ -28,6 +29,7 @@
2829
import java.io.EOFException;
2930
import java.io.IOException;
3031
import java.nio.ByteBuffer;
32+
import java.util.Arrays;
3133

3234
/** A queue of media sample data. */
3335
/* package */ class SampleDataQueue {
@@ -229,10 +231,14 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex
229231
int ivSize = signalByte & 0x7F;
230232

231233
// Read the initialization vector.
232-
if (buffer.cryptoInfo.iv == null) {
233-
buffer.cryptoInfo.iv = new byte[16];
234+
CryptoInfo cryptoInfo = buffer.cryptoInfo;
235+
if (cryptoInfo.iv == null) {
236+
cryptoInfo.iv = new byte[16];
237+
} else {
238+
// Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
239+
Arrays.fill(cryptoInfo.iv, (byte) 0);
234240
}
235-
readData(offset, buffer.cryptoInfo.iv, ivSize);
241+
readData(offset, cryptoInfo.iv, ivSize);
236242
offset += ivSize;
237243

238244
// Read the subsample count, if present.
@@ -247,11 +253,11 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex
247253
}
248254

249255
// Write the clear and encrypted subsample sizes.
250-
@Nullable int[] clearDataSizes = buffer.cryptoInfo.numBytesOfClearData;
256+
@Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
251257
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
252258
clearDataSizes = new int[subsampleCount];
253259
}
254-
@Nullable int[] encryptedDataSizes = buffer.cryptoInfo.numBytesOfEncryptedData;
260+
@Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
255261
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
256262
encryptedDataSizes = new int[subsampleCount];
257263
}
@@ -272,12 +278,12 @@ private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder ex
272278

273279
// Populate the cryptoInfo.
274280
CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
275-
buffer.cryptoInfo.set(
281+
cryptoInfo.set(
276282
subsampleCount,
277283
clearDataSizes,
278284
encryptedDataSizes,
279285
cryptoData.encryptionKey,
280-
buffer.cryptoInfo.iv,
286+
cryptoInfo.iv,
281287
cryptoData.cryptoMode,
282288
cryptoData.encryptedBlocks,
283289
cryptoData.clearBlocks);

library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java

+72-15
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
*/
1616
package com.google.android.exoplayer2.source;
1717

18+
import static com.google.android.exoplayer2.C.BUFFER_FLAG_ENCRYPTED;
1819
import static com.google.android.exoplayer2.C.BUFFER_FLAG_KEY_FRAME;
1920
import static com.google.android.exoplayer2.C.RESULT_BUFFER_READ;
2021
import static com.google.android.exoplayer2.C.RESULT_FORMAT_READ;
2122
import static com.google.android.exoplayer2.C.RESULT_NOTHING_READ;
2223
import static com.google.common.truth.Truth.assertThat;
2324
import static java.lang.Long.MIN_VALUE;
2425
import static java.util.Arrays.copyOfRange;
26+
import static org.junit.Assert.assertArrayEquals;
2527
import static org.mockito.Mockito.when;
2628

2729
import androidx.annotation.Nullable;
@@ -114,17 +116,13 @@ public final class SampleQueueTest {
114116
C.BUFFER_FLAG_KEY_FRAME, C.BUFFER_FLAG_ENCRYPTED, 0, C.BUFFER_FLAG_ENCRYPTED,
115117
};
116118
private static final long[] ENCRYPTED_SAMPLE_TIMESTAMPS = new long[] {0, 1000, 2000, 3000};
117-
private static final Format[] ENCRYPTED_SAMPLES_FORMATS =
119+
private static final Format[] ENCRYPTED_SAMPLE_FORMATS =
118120
new Format[] {FORMAT_ENCRYPTED, FORMAT_ENCRYPTED, FORMAT_1, FORMAT_ENCRYPTED};
119121
/** Encrypted samples require the encryption preamble. */
120-
private static final int[] ENCRYPTED_SAMPLES_SIZES = new int[] {1, 3, 1, 3};
122+
private static final int[] ENCRYPTED_SAMPLE_SIZES = new int[] {1, 3, 1, 3};
121123

122-
private static final int[] ENCRYPTED_SAMPLES_OFFSETS = new int[] {7, 4, 3, 0};
123-
private static final byte[] ENCRYPTED_SAMPLES_DATA = new byte[8];
124-
125-
static {
126-
Arrays.fill(ENCRYPTED_SAMPLES_DATA, (byte) 1);
127-
}
124+
private static final int[] ENCRYPTED_SAMPLE_OFFSETS = new int[] {7, 4, 3, 0};
125+
private static final byte[] ENCRYPTED_SAMPLE_DATA = new byte[] {1, 1, 1, 1, 1, 1, 1, 1};
128126

129127
private static final TrackOutput.CryptoData DUMMY_CRYPTO_DATA =
130128
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
@@ -461,6 +459,60 @@ public void testAllowPlaceholderSessionPopulatesDrmSession() {
461459
/* decodeOnlyUntilUs= */ 0);
462460
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
463461
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
462+
assertReadEncryptedSample(/* sampleIndex= */ 3);
463+
}
464+
465+
@Test
466+
@SuppressWarnings("unchecked")
467+
public void testTrailingCryptoInfoInitializationVectorBytesZeroed() {
468+
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
469+
DrmSession<ExoMediaCrypto> mockPlaceholderDrmSession =
470+
(DrmSession<ExoMediaCrypto>) Mockito.mock(DrmSession.class);
471+
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
472+
when(mockDrmSessionManager.acquirePlaceholderSession(
473+
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
474+
.thenReturn(mockPlaceholderDrmSession);
475+
476+
writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
477+
byte[] sampleData = new byte[] {0, 1, 2};
478+
byte[] initializationVector = new byte[] {7, 6, 5, 4, 3, 2, 1, 0};
479+
byte[] encryptedSampleData =
480+
TestUtil.joinByteArrays(
481+
new byte[] {
482+
0x08, // subsampleEncryption = false (1 bit), ivSize = 8 (7 bits).
483+
},
484+
initializationVector,
485+
sampleData);
486+
writeSample(
487+
encryptedSampleData, /* timestampUs= */ 0, BUFFER_FLAG_KEY_FRAME | BUFFER_FLAG_ENCRYPTED);
488+
489+
int result =
490+
sampleQueue.read(
491+
formatHolder,
492+
inputBuffer,
493+
/* formatRequired= */ false,
494+
/* loadingFinished= */ false,
495+
/* decodeOnlyUntilUs= */ 0);
496+
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
497+
498+
// Fill cryptoInfo.iv with non-zero data. When the 8 byte initialization vector is written into
499+
// it, we expect the trailing 8 bytes to be zeroed.
500+
inputBuffer.cryptoInfo.iv = new byte[16];
501+
Arrays.fill(inputBuffer.cryptoInfo.iv, (byte) 1);
502+
503+
result =
504+
sampleQueue.read(
505+
formatHolder,
506+
inputBuffer,
507+
/* formatRequired= */ false,
508+
/* loadingFinished= */ false,
509+
/* decodeOnlyUntilUs= */ 0);
510+
assertThat(result).isEqualTo(RESULT_BUFFER_READ);
511+
512+
// Assert cryptoInfo.iv contains the 8-byte initialization vector and that the trailing 8 bytes
513+
// have been zeroed.
514+
byte[] expectedInitializationVector = Arrays.copyOf(initializationVector, 16);
515+
assertArrayEquals(expectedInitializationVector, inputBuffer.cryptoInfo.iv);
464516
}
465517

466518
@Test
@@ -995,11 +1047,11 @@ private void writeTestData() {
9951047

9961048
private void writeTestDataWithEncryptedSections() {
9971049
writeTestData(
998-
ENCRYPTED_SAMPLES_DATA,
999-
ENCRYPTED_SAMPLES_SIZES,
1000-
ENCRYPTED_SAMPLES_OFFSETS,
1050+
ENCRYPTED_SAMPLE_DATA,
1051+
ENCRYPTED_SAMPLE_SIZES,
1052+
ENCRYPTED_SAMPLE_OFFSETS,
10011053
ENCRYPTED_SAMPLE_TIMESTAMPS,
1002-
ENCRYPTED_SAMPLES_FORMATS,
1054+
ENCRYPTED_SAMPLE_FORMATS,
10031055
ENCRYPTED_SAMPLES_FLAGS);
10041056
}
10051057

@@ -1033,7 +1085,12 @@ private void writeFormat(Format format) {
10331085
/** Writes a single sample to {@code sampleQueue}. */
10341086
private void writeSample(byte[] data, long timestampUs, int sampleFlags) {
10351087
sampleQueue.sampleData(new ParsableByteArray(data), data.length);
1036-
sampleQueue.sampleMetadata(timestampUs, sampleFlags, data.length, 0, null);
1088+
sampleQueue.sampleMetadata(
1089+
timestampUs,
1090+
sampleFlags,
1091+
data.length,
1092+
/* offset= */ 0,
1093+
(sampleFlags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? DUMMY_CRYPTO_DATA : null);
10371094
}
10381095

10391096
/**
@@ -1206,7 +1263,7 @@ private void assertReadFormat(boolean formatRequired, Format format) {
12061263
}
12071264

12081265
private void assertReadEncryptedSample(int sampleIndex) {
1209-
byte[] sampleData = new byte[ENCRYPTED_SAMPLES_SIZES[sampleIndex]];
1266+
byte[] sampleData = new byte[ENCRYPTED_SAMPLE_SIZES[sampleIndex]];
12101267
Arrays.fill(sampleData, (byte) 1);
12111268
boolean isKeyFrame = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0;
12121269
boolean isEncrypted = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0;
@@ -1216,7 +1273,7 @@ private void assertReadEncryptedSample(int sampleIndex) {
12161273
isEncrypted,
12171274
sampleData,
12181275
/* offset= */ 0,
1219-
ENCRYPTED_SAMPLES_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
1276+
ENCRYPTED_SAMPLE_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
12201277
}
12211278

12221279
/**

0 commit comments

Comments
 (0)