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

Commit 33e0101c authored by Taran Singh's avatar Taran Singh
Browse files

DO NOT MERGE: Validate IME metadata before parsing

Malicious IMEs can load an extremely large metaData for inputmethod xml
which can lead to IMMS running out of memory while contructing
InputMethodInfo.
this change limits the size of metadata xml to 200KBs and would throw
XmlPullParserException for xmls larger than that.

Bug: 416259832
Test: Manually using steps in the bug
Flag: EXEMPT bug_fix
Change-Id: Ic90e1bd6615f5954577ed29c3daf7b44596fb137
(cherry picked from commit 5e31d9c0)
parent eab68f80
Loading
Loading
Loading
Loading
+71 −2
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.icu.util.ULocale;
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -51,6 +52,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

@@ -102,6 +104,24 @@ public final class InputMethodInfo implements Parcelable {

    static final String TAG = "InputMethodInfo";

    /**
     * The maximum allowed size in bytes for an input method's metadata XML file
     * (typically {@code inputmethod.xml} referenced by
     * {@link android.inputmethodservice.InputMethod#SERVICE_META_DATA}).
     * This limit is enforced to prevent {@link OutOfMemoryError OutOfMemoryErrors}
     * when the Android system parses input method manifests.
     * <p>
     * An excessively large metadata file, whether due to misconfiguration or
     * malicious intent, could consume an unreasonable amount of memory in the
     * system process, potentially leading to instability or denial of service.
     * </p>
     * <p>
     * The current recommended value is 200 KB (200 * 1024 bytes), which is
     * significantly larger than typical input method metadata files.
     * </p>
     */
    private static final int MAX_METADATA_SIZE_BYTES = 200 * 1024; // 200 KB

    /**
     * The Service that implements this input method component.
     */
@@ -244,14 +264,14 @@ public final class InputMethodInfo implements Parcelable {
        XmlResourceParser parser = null;
        final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
        try {
            Resources res = pm.getResourcesForApplication(si.applicationInfo);
            validateXmlMetaData(si, res);
            parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
            if (parser == null) {
                throw new XmlPullParserException("No "
                        + InputMethod.SERVICE_META_DATA + " meta-data");
            }

            Resources res = pm.getResourcesForApplication(si.applicationInfo);

            AttributeSet attrs = Xml.asAttributeSet(parser);

            int type;
@@ -384,6 +404,55 @@ public final class InputMethodInfo implements Parcelable {
        mIsVrOnly = isVrOnly;
    }

    /**
     * Validates the XML metadata for an input method service to prevent OOM errors
     * from excessively large XML files.
     *
     * @param si The ServiceInfo of the input method.
     * @param res The input method app resources.
     * @throws IOException if there's an issue reading the resource.
     * @throws NameNotFoundException if the application's resources cannot be found.
     * @throws XmlPullParserException if the metadata file exceeds the size limit.
     */
    private static void validateXmlMetaData(@NonNull ServiceInfo si, @NonNull Resources res)
            throws IOException, NameNotFoundException, XmlPullParserException {
        final Bundle metaData = si.metaData;
        if (metaData == null) {
            // No metadata, skip validation.
            return;
        }
        final int resourceId = metaData.getInt(InputMethod.SERVICE_META_DATA);
        if (resourceId == 0) {
            // No metadata, skip validation.
            return;
        }

        // Validate file size using InputStream.skip()
        long totalBytesSkipped = 0;
        // Loop to ensure we skip the required number of bytes, as a single
        // call to skip() is not guaranteed to skip the full amount.
        try (InputStream is = res.openRawResource(resourceId)) {
            while (totalBytesSkipped < MAX_METADATA_SIZE_BYTES) {
                long bytesSkipped = is.skip(MAX_METADATA_SIZE_BYTES - totalBytesSkipped);
                if (bytesSkipped <= 0) {
                    // end of the stream.
                    break;
                }
                totalBytesSkipped += bytesSkipped;
            }

            // If we successfully skipped exactly MAX_METADATA_SIZE_BYTES
            if (totalBytesSkipped == MAX_METADATA_SIZE_BYTES) {
                // try to read one more byte.
                if (is.read() != -1) {
                    throw new XmlPullParserException(
                            "Input method metadata exceeds maximum allowed limit of 200KB for "
                                    + si.packageName + ". InputMethod will not be loaded. ");
                }
            }
        }
    }

    /**
     * @hide
     */