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

Commit 21c90e0c authored by William Loh's avatar William Loh
Browse files

Limit max meta data string values per component.

Impose a max size limit of 262,144 characters for all meta data strings
combined for each component defined in the manifest. In combination with
meta-data tag count and value attribute length limits, this limits the
bundle size to ~0.5MB and prevents binder transactions failures when
querying components with GET_META_DATA.

Bug: 309027507
Test: atest AndroidPackageParsingValidationTest
Test: manualy test POC malicious app fails to install
Change-Id: I4ae91edfa388e337144a7b768b90a42626443a7a
parent 1897f8b2
Loading
Loading
Loading
Loading
+13 −3
Original line number Diff line number Diff line
@@ -38,9 +38,11 @@ public class Element {
    private static final int MAX_ATTR_LEN_PERMISSION_GROUP = 256;
    private static final int MAX_ATTR_LEN_PACKAGE = 256;
    private static final int MAX_ATTR_LEN_MIMETYPE = 512;
    public static final int MAX_ATTR_LEN_NAME = 1024;
    public static final int MAX_ATTR_LEN_PATH = 4000;
    public static final int MAX_ATTR_LEN_VALUE = 32_768;
    private static final int MAX_ATTR_LEN_NAME = 1024;
    private static final int MAX_ATTR_LEN_PATH = 4000;
    private static final int MAX_ATTR_LEN_VALUE = 32_768;

    private static final int MAX_TOTAL_META_DATA_SIZE = 262_144;

    private static final String BAD_COMPONENT_NAME_CHARS = ";,[](){}:?%^*|/\\";

@@ -157,6 +159,7 @@ public class Element {
    }

    private long mChildTagMask = 0;
    private int mTotalComponentMetadataSize = 0;

    private static int getCounterIdx(String tag) {
        switch(tag) {
@@ -283,6 +286,7 @@ public class Element {
    private void init(String tag) {
        this.mTag = tag;
        mChildTagMask = 0;
        mTotalComponentMetadataSize = 0;
        switch (tag) {
            case TAG_ACTIVITY:
                initializeCounter(TAG_LAYOUT, 1000);
@@ -820,6 +824,12 @@ public class Element {
        }
    }

    void validateComponentMetadata(String value) {
        mTotalComponentMetadataSize += value.length();
        if (mTotalComponentMetadataSize > MAX_TOTAL_META_DATA_SIZE) {
            throw new SecurityException("Max total meta data size limit exceeded for " + mTag);
        }
    }

    void seen(@NonNull Element element) {
        TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
+20 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package android.content.res;
import android.annotation.NonNull;
import android.annotation.StyleableRes;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

@@ -84,6 +86,9 @@ public class Validator {
            return;
        }
        mElements.peek().validateResStrAttr(index, stringValue);
        if (index == R.styleable.AndroidManifestMetaData_value) {
            validateComponentMetadata(stringValue.toString());
        }
    }

    /**
@@ -94,5 +99,20 @@ public class Validator {
            return;
        }
        mElements.peek().validateStrAttr(attrName, attrValue);
        if (attrName.equals(Element.TAG_ATTR_VALUE)) {
            validateComponentMetadata(attrValue);
        }
    }

    private void validateComponentMetadata(String attrValue) {
        Element element = mElements.peek();
        // Meta-data values are evaluated on the parent element which is the next element in the
        // mElements stack after the meta-data element. The top of the stack is always the current
        // element being validated so check that the top element is meta-data.
        if (element.mTag.equals(Element.TAG_META_DATA) && mElements.size() > 1) {
            element = mElements.pop();
            mElements.peek().validateComponentMetadata(attrValue);
            mElements.push(element);
        }
    }
}
+66 −0
Original line number Diff line number Diff line
@@ -492,6 +492,40 @@ class AndroidPackageParsingValidationTest {
        validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesPermission_name, 1024)
    }

    @Test
    fun totalMetadataValuesExceedMax_shouldFail() {
        val value = "x".repeat(8192)
        var tags = ""
        repeat(32) { index ->
            tags += "<meta-data name=\"name$index\" value=\"$value\" />"
        }
        var xml = "<application>$tags</application>"
        try {
            parseXmlForMetadata(xml)
        } catch (e: SecurityException) {
            fail(
                "Failed to parse component meta-data when values have not exceeded max allowed"
            )
        }
        try {
            parseXmlForMetadataRes(xml)
        } catch (e: SecurityException) {
            fail(
                "Failed to parse component meta-data when values have not exceeded max allowed"
            )
        }
        tags += "<meta-data name=\"last\" value=\"x\" />"
        xml = "<application>$tags</application>"
        var e = assertThrows(SecurityException::class.java) {
            parseXmlForMetadata(xml)
        }
        assertEquals("Max total meta data size limit exceeded for application", e.message)
        e = assertThrows(SecurityException::class.java) {
            parseXmlForMetadataRes(xml)
        }
        assertEquals("Max total meta data size limit exceeded for application", e.message)
    }

    private fun validateTagAttrComponentName(tag: String, attr: String, index: Int) {
        val passNames = arrayOf("com.android.TestClass", "TestClass", "_", "$", ".TestClass", "上")
        for (name in passNames) {
@@ -664,6 +698,38 @@ class AndroidPackageParsingValidationTest {
        } while (type != XmlPullParser.END_DOCUMENT)
    }

    fun parseXmlForMetadata(manifestStr: String) {
        pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null)
        val validator = Validator()
        do {
            val type = pullParser.next()
            validator.validate(pullParser)
            if (type == XmlPullParser.START_TAG && pullParser.getName().equals("meta-data")) {
                val name = "value"
                val value = pullParser.getAttributeValue("", name)
                validator.validateStrAttr(pullParser, "value", value)
            }
        } while (type != XmlPullParser.END_DOCUMENT)
    }

    fun parseXmlForMetadataRes(manifestStr: String) {
        pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null)
        val validator = Validator()
        do {
            val type = pullParser.next()
            validator.validate(pullParser)
            if (type == XmlPullParser.START_TAG && pullParser.getName().equals("meta-data")) {
                val name = "value"
                val value = pullParser.getAttributeValue("", name)
                validator.validateResStrAttr(
                    pullParser,
                    R.styleable.AndroidManifestMetaData_value,
                    value
                )
            }
        } while (type != XmlPullParser.END_DOCUMENT)
    }

    fun expectedCountErrorMsg(tag: String, parentTag: String) =
            "The number of child $tag elements exceeded the max allowed in $parentTag"