Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7407a3f authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

Bass: Fix parsing BASE structure

Each time we get the field length from the remote, make sure we actually
received the proper amount of data. This eliminates the fatal errors when
the BASE data is truncated or badly formatted.

Bug: 328181940
Test: atest BluetoothInstrumentationTests
Flag: EXEMPT; Regression tests added
Change-Id: I5ba26f8bda55c94424024b1cd80e8e95dfebc610
parent c2e51a6e
Loading
Loading
Loading
Loading
+74 −11
Original line number Diff line number Diff line
@@ -141,26 +141,36 @@ class BaseData {
        levelOne.print();
        log("levelOne subgroups" + levelOne.numSubGroups);
        for (int i = 0; i < (int) levelOne.numSubGroups; i++) {
            if (offset >= serviceData.length) {
                Log.e(TAG, "Error: parsing Level 2");
                return null;
            }

            Pair<BaseInformation, Integer> pair1 = parseLevelTwo(serviceData, i, offset);
            BaseInformation node2 = pair1.first;
            if (node2 == null) {
            if (pair1 == null) {
                Log.e(TAG, "Error: parsing Level 2");
                return null;
            }
            BaseInformation node2 = pair1.first;
            numOfBISIndices += node2.numSubGroups;
            levelTwo.add(node2);
            node2.print();
            offset = pair1.second;
            for (int k = 0; k < node2.numSubGroups; k++) {
                if (offset >= serviceData.length) {
                    Log.e(TAG, "Error: parsing Level 3");
                    return null;
                }

                Pair<BaseInformation, Integer> pair2 = parseLevelThree(serviceData, offset);
                BaseInformation node3 = pair2.first;
                offset = pair2.second;
                if (node3 == null) {
                if (pair2 == null) {
                    Log.e(TAG, "Error: parsing Level 3");
                    return null;
                }
                BaseInformation node3 = pair2.first;
                levelThree.add(node3);
                node3.print();
                offset = pair2.second;
            }
        }
        consolidateBaseofLevelTwo(levelTwo, levelThree);
@@ -173,17 +183,52 @@ class BaseData {
        BaseInformation node = new BaseInformation();
        node.level = METADATA_LEVEL2;
        node.subGroupId = groupIndex;
        int bufferLengthLeft = (serviceData.length - offset);

        // Min. length expected is: codecID (5) + numBis (1) + codecSpecCfgLen (1) + metadataLen (1)
        final int minNodeBufferLen = METADATA_CODEC_LENGTH + 3;
        if (bufferLengthLeft < minNodeBufferLen) {
            Log.e(TAG, "Error: Invalid Lvl2 buffer length.");
            return null;
        }

        node.numSubGroups = serviceData[offset++]; // NumBis
        System.arraycopy(serviceData, offset, node.codecId, 0, METADATA_CODEC_LENGTH);
        offset += METADATA_CODEC_LENGTH;
        node.codecConfigLength = serviceData[offset++] & 0xff;
        if (node.codecConfigLength != 0) {

        // Declared codec specific data length
        int declaredLength = serviceData[offset++] & 0xff;

        bufferLengthLeft = (serviceData.length - offset);
        if (declaredLength < 0 || declaredLength > bufferLengthLeft) {
            Log.e(TAG, "Error: Invalid codec config length or codec config truncated.");
            return null;
        }

        if (declaredLength != 0) {
            node.codecConfigLength = declaredLength;
            node.codecConfigInfo = new byte[node.codecConfigLength];
            System.arraycopy(serviceData, offset, node.codecConfigInfo, 0, node.codecConfigLength);
            offset += node.codecConfigLength;
        }
        node.metaDataLength = serviceData[offset++] & 0xff;
        if (node.metaDataLength != 0) {

        // Verify the buffer size left
        bufferLengthLeft = (serviceData.length - offset);
        if (bufferLengthLeft < 1) {
            Log.e(TAG, "Error: Invalid Lvl2 buffer length.");
            return null;
        }

        // Declared metadata length
        declaredLength = serviceData[offset++] & 0xff;
        --bufferLengthLeft;
        if (declaredLength < 0 || declaredLength > bufferLengthLeft) {
            Log.e(TAG, "Error: Invalid metadata length or metadata truncated.");
            return null;
        }

        if (declaredLength != 0) {
            node.metaDataLength = declaredLength;
            node.metaData = new byte[node.metaDataLength];
            System.arraycopy(serviceData, offset, node.metaData, 0, node.metaDataLength);
            offset += node.metaDataLength;
@@ -195,9 +240,27 @@ class BaseData {
        log("Parsing Level 3");
        BaseInformation node = new BaseInformation();
        node.level = METADATA_LEVEL3;
        int bufferLengthLeft = (serviceData.length - offset);

        // Min. length expected is: bisIdx (1) + codecSpecCfgLen (1)
        final int minNodeBufferLen = 2;
        if (bufferLengthLeft < minNodeBufferLen) {
            Log.e(TAG, "Error: Invalid Lvl2 buffer length.");
            return null;
        }
        node.index = serviceData[offset++];
        node.codecConfigLength = serviceData[offset++] & 0xff;
        if (node.codecConfigLength != 0) {

        // Verify the buffer size left
        int declaredLength = serviceData[offset++] & 0xff;

        bufferLengthLeft = (serviceData.length - offset);
        if (declaredLength < 0 || declaredLength > bufferLengthLeft) {
            Log.e(TAG, "Error: Invalid metadata length or metadata truncated.");
            return null;
        }

        if (declaredLength != 0) {
            node.codecConfigLength = declaredLength;
            node.codecConfigInfo = new byte[node.codecConfigLength];
            System.arraycopy(serviceData, offset, node.codecConfigInfo, 0, node.codecConfigLength);
            offset += node.codecConfigLength;
+277 −0
Original line number Diff line number Diff line
@@ -119,6 +119,283 @@ public class BaseDataTest {
        assertThat(level.codecConfigLength).isEqualTo(3);
    }

    @Test
    public void parseBaseDataLvl2TruncatedConfig() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x01, // numBIS
                    (byte) 0x06,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00, // Lc3
                    (byte) 0x03, // codecConfigLength
                    (byte) 0x01,
                    (byte) 'A', // codecConfigInfo
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        assertThat(data).isEqualTo(null);
    }

    @Test
    public void parseBaseDataLvl2TruncatedMetadata() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x01, // numBIS
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00, // UNKNOWN_CODEC
                    (byte) 0x02, // codecConfigLength
                    (byte) 0x01,
                    (byte) 'A', // codecConfigInfo
                    (byte) 0x04, // metaDataLength
                    (byte) 0x06,
                    (byte) 0x07,
                    (byte) 0x08, // metaData
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        assertThat(data).isEqualTo(null);
    }

    @Test
    public void parseBaseDataLvl3TruncatedConfig() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x01, // numBIS
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00, // UNKNOWN_CODEC
                    (byte) 0x02, // codecConfigLength
                    (byte) 0x01,
                    (byte) 'A', // codecConfigInfo
                    (byte) 0x03, // metaDataLength
                    (byte) 0x06,
                    (byte) 0x07,
                    (byte) 0x08, // metaData
                    // LEVEL 3
                    (byte) 0x04, // index
                    (byte) 0x04, // codecConfigLength
                    (byte) 0x02,
                    (byte) 'B',
                    (byte) 'C' // codecConfigInfo
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        assertThat(data).isEqualTo(null);
    }

    @Test
    public void parseBaseDataInvalidLtv() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x01, // numBIS
                    (byte) 0x06,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00,
                    (byte) 0x00, // LC3
                    (byte) 0x02, // codecConfigLength
                    (byte) 0x04,
                    (byte) 'A', // codecConfigInfo
                    (byte) 0x03, // metaDataLength
                    (byte) 0x06,
                    (byte) 0x07,
                    (byte) 0x08, // metaData
                    // LEVEL 3
                    (byte) 0x04, // index
                    (byte) 0x03, // codecConfigLength
                    (byte) 0x03,
                    (byte) 'B',
                    (byte) 'C' // codecConfigInfo
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        BaseData.BaseInformation level = data.getLevelOne();
        assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03});
        assertThat(level.numSubGroups).isEqualTo(1);

        assertThat(data.getLevelTwo().size()).isEqualTo(1);
        level = data.getLevelTwo().get(0);

        assertThat(level.numSubGroups).isEqualTo(1);
        assertThat(level.codecId).isEqualTo(new byte[] {0x06, 0x00, 0x00, 0x00, 0x00});
        assertThat(level.codecConfigLength).isEqualTo(2);
        assertThat(level.metaDataLength).isEqualTo(3);

        assertThat(data.getLevelThree().size()).isEqualTo(1);
        level = data.getLevelThree().get(0);
        assertThat(level.index).isEqualTo(4);

        // Got the whole config, without interpreting it as LTV
        assertThat(level.codecConfigLength).isEqualTo(3);
    }

    @Test
    public void parseBaseVendorCodecBaseData() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x01, // numBIS
                    (byte) 0xFF, // VENDOR_CODEC
                    (byte) 0x0A,
                    (byte) 0xAB,
                    (byte) 0xBC,
                    (byte) 0xCD,
                    (byte) 0x04, // codecConfigLength
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03,
                    (byte) 0x04, // opaque vendor data
                    (byte) 0x03, // metaDataLength
                    (byte) 0x06,
                    (byte) 0x07,
                    (byte) 0x08, // metaData
                    // LEVEL 3
                    (byte) 0x04, // index
                    (byte) 0x03, // codecConfigLength
                    (byte) 0x03,
                    (byte) 0x02,
                    (byte) 0x01 // opaque vendor data
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        BaseData.BaseInformation level = data.getLevelOne();
        assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03});
        assertThat(level.numSubGroups).isEqualTo(1);

        assertThat(data.getLevelTwo().size()).isEqualTo(1);
        level = data.getLevelTwo().get(0);

        assertThat(level.numSubGroups).isEqualTo(1);
        assertThat(level.codecId)
                .isEqualTo(
                        new byte[] {
                            (byte) 0xFF, (byte) 0x0A, (byte) 0xAB, (byte) 0xBC, (byte) 0xCD
                        });
        assertThat(level.codecConfigLength).isEqualTo(4);
        assertThat(level.metaDataLength).isEqualTo(3);

        assertThat(data.getLevelThree().size()).isEqualTo(1);
        level = data.getLevelThree().get(0);
        assertThat(level.index).isEqualTo(4);
        assertThat(level.codecConfigLength).isEqualTo(3);
    }

    @Test
    public void parseBaseVendorCodecBaseDataMinimal() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x01, // numBIS
                    (byte) 0xFF, // VENDOR_CODEC
                    (byte) 0x0A,
                    (byte) 0xAB,
                    (byte) 0xBC,
                    (byte) 0xCD,
                    (byte) 0x00, // codecConfigLength
                    (byte) 0x00, // metaDataLength
                    // LEVEL 3
                    (byte) 0x04, // index
                    (byte) 0x00, // codecConfigLength
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        BaseData.BaseInformation level = data.getLevelOne();
        assertThat(level.presentationDelay).isEqualTo(new byte[] {0x01, 0x02, 0x03});
        assertThat(level.numSubGroups).isEqualTo(1);

        assertThat(data.getLevelTwo().size()).isEqualTo(1);
        level = data.getLevelTwo().get(0);

        assertThat(level.numSubGroups).isEqualTo(1);
        assertThat(level.codecId)
                .isEqualTo(
                        new byte[] {
                            (byte) 0xFF, (byte) 0x0A, (byte) 0xAB, (byte) 0xBC, (byte) 0xCD
                        });
        assertThat(level.codecConfigLength).isEqualTo(0);
        assertThat(level.metaDataLength).isEqualTo(0);

        assertThat(data.getLevelThree().size()).isEqualTo(1);
        level = data.getLevelThree().get(0);
        assertThat(level.index).isEqualTo(4);
        assertThat(level.codecConfigLength).isEqualTo(0);
    }

    @Test
    public void parseBaseVendorCodecBaseDataInvalid() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));

        byte[] serviceData =
                new byte[] {
                    // LEVEL 1
                    (byte) 0x01,
                    (byte) 0x02,
                    (byte) 0x03, // presentationDelay
                    (byte) 0x01, // numSubGroups
                    // LEVEL 2
                    (byte) 0x00, // numBIS invalid value
                    (byte) 0xFE, // UNKNOWN CODEC
                    (byte) 0x00, // codecConfigLength
                    (byte) 0x00, // metaDataLength
                };

        BaseData data = BaseData.parseBaseData(serviceData);
        assertThat(data).isEqualTo(null);
    }

    @Test
    public void parseBaseData_longMetaData() {
        assertThrows(IllegalArgumentException.class, () -> BaseData.parseBaseData(null));