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

Commit d4b5212e authored by Yuri Lin's avatar Yuri Lin
Browse files

Trim any long string inputs that come in to AutomaticZenRule

This change both prevents any rules from being unable to be written to disk and also avoids risk of running out of memory while handling all the zen rules.

Bug: 242703460
Bug: 242703505
Bug: 242703780
Bug: 242704043
Bug: 243794204
Test: cts AutomaticZenRuleTest; atest android.app.AutomaticZenRuleTest; manually confirmed each exploit example either saves the rule successfully with a truncated string (in the case of name & conditionId) or may fail to save the rule at all (if the owner/configactivity is invalid). Additionally ran the memory-exhausting PoC without device crashes.

Change-Id: I110172a43f28528dd274b3b346eb29c3796ff2c6
Merged-In: I110172a43f28528dd274b3b346eb29c3796ff2c6
(cherry picked from commit de172ba0)
parent 91e0c359
Loading
Loading
Loading
Loading
+51 −11
Original line number Original line Diff line number Diff line
@@ -47,6 +47,13 @@ public final class AutomaticZenRule implements Parcelable {
    private boolean mModified = false;
    private boolean mModified = false;
    private String mPkg;
    private String mPkg;


    /**
     * The maximum string length for any string contained in this automatic zen rule. This pertains
     * both to fields in the rule itself (such as its name) and items with sub-fields.
     * @hide
     */
    public static final int MAX_STRING_LENGTH = 1000;

    /**
    /**
     * Creates an automatic zen rule.
     * Creates an automatic zen rule.
     *
     *
@@ -93,10 +100,10 @@ public final class AutomaticZenRule implements Parcelable {
    public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
    public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
            @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
            @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
            @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
            @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
        this.name = name;
        this.name = getTrimmedString(name);
        this.owner = owner;
        this.owner = getTrimmedComponentName(owner);
        this.configurationActivity = configurationActivity;
        this.configurationActivity = getTrimmedComponentName(configurationActivity);
        this.conditionId = conditionId;
        this.conditionId = getTrimmedUri(conditionId);
        this.interruptionFilter = interruptionFilter;
        this.interruptionFilter = interruptionFilter;
        this.enabled = enabled;
        this.enabled = enabled;
        this.mZenPolicy = policy;
        this.mZenPolicy = policy;
@@ -115,12 +122,14 @@ public final class AutomaticZenRule implements Parcelable {
    public AutomaticZenRule(Parcel source) {
    public AutomaticZenRule(Parcel source) {
        enabled = source.readInt() == ENABLED;
        enabled = source.readInt() == ENABLED;
        if (source.readInt() == ENABLED) {
        if (source.readInt() == ENABLED) {
            name = source.readString();
            name = getTrimmedString(source.readString());
        }
        }
        interruptionFilter = source.readInt();
        interruptionFilter = source.readInt();
        conditionId = source.readParcelable(null, android.net.Uri.class);
        conditionId = getTrimmedUri(source.readParcelable(null, android.net.Uri.class));
        owner = source.readParcelable(null, android.content.ComponentName.class);
        owner = getTrimmedComponentName(
        configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
                source.readParcelable(null, android.content.ComponentName.class));
        configurationActivity = getTrimmedComponentName(
                source.readParcelable(null, android.content.ComponentName.class));
        creationTime = source.readLong();
        creationTime = source.readLong();
        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
        mModified = source.readInt() == ENABLED;
        mModified = source.readInt() == ENABLED;
@@ -196,7 +205,7 @@ public final class AutomaticZenRule implements Parcelable {
     * Sets the representation of the state that causes this rule to become active.
     * Sets the representation of the state that causes this rule to become active.
     */
     */
    public void setConditionId(Uri conditionId) {
    public void setConditionId(Uri conditionId) {
        this.conditionId = conditionId;
        this.conditionId = getTrimmedUri(conditionId);
    }
    }


    /**
    /**
@@ -211,7 +220,7 @@ public final class AutomaticZenRule implements Parcelable {
     * Sets the name of this rule.
     * Sets the name of this rule.
     */
     */
    public void setName(String name) {
    public void setName(String name) {
        this.name = name;
        this.name = getTrimmedString(name);
    }
    }


    /**
    /**
@@ -243,7 +252,7 @@ public final class AutomaticZenRule implements Parcelable {
     * that are not backed by {@link android.service.notification.ConditionProviderService}.
     * that are not backed by {@link android.service.notification.ConditionProviderService}.
     */
     */
    public void setConfigurationActivity(@Nullable ComponentName componentName) {
    public void setConfigurationActivity(@Nullable ComponentName componentName) {
        this.configurationActivity = componentName;
        this.configurationActivity = getTrimmedComponentName(componentName);
    }
    }


    /**
    /**
@@ -333,4 +342,35 @@ public final class AutomaticZenRule implements Parcelable {
            return new AutomaticZenRule[size];
            return new AutomaticZenRule[size];
        }
        }
    };
    };

    /**
     * If the package or class name of the provided ComponentName are longer than MAX_STRING_LENGTH,
     * return a trimmed version that truncates each of the package and class name at the max length.
     */
    private static ComponentName getTrimmedComponentName(ComponentName cn) {
        if (cn == null) return null;
        return new ComponentName(getTrimmedString(cn.getPackageName()),
                getTrimmedString(cn.getClassName()));
    }

    /**
     * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH.
     */
    private static String getTrimmedString(String input) {
        if (input != null && input.length() > MAX_STRING_LENGTH) {
            return input.substring(0, MAX_STRING_LENGTH);
        }
        return input;
    }

    /**
     * Returns a truncated copy of the Uri by trimming the string representation to the maximum
     * string length.
     */
    private static Uri getTrimmedUri(Uri input) {
        if (input != null && input.toString().length() > MAX_STRING_LENGTH) {
            return Uri.parse(getTrimmedString(input.toString()));
        }
        return input;
    }
}
}
+153 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;

import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.google.common.base.Strings;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.lang.reflect.Field;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class AutomaticZenRuleTest {
    private static final String CLASS = "android.app.AutomaticZenRule";

    @Test
    public void testLongFields_inConstructor() {
        String longString = Strings.repeat("A", 65536);
        Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));

        // test both variants where there's an owner, and where there's a configuration activity
        AutomaticZenRule rule1 = new AutomaticZenRule(
                longString, // name
                new ComponentName("pkg", longString), // owner
                null,  // configuration activity
                longUri, // conditionId
                null, // zen policy
                0, // interruption filter
                true); // enabled

        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule1.getName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                rule1.getConditionId().toString().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule1.getOwner().getClassName().length());

        AutomaticZenRule rule2 = new AutomaticZenRule(
                longString, // name
                null, // owner
                new ComponentName(longString, "SomeClassName"), // configuration activity
                longUri, // conditionId
                null, // zen policy
                0, // interruption filter
                false); // enabled

        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule2.getName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                rule2.getConditionId().toString().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                rule2.getConfigurationActivity().getPackageName().length());
    }

    @Test
    public void testLongFields_inSetters() {
        String longString = Strings.repeat("A", 65536);
        Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));

        AutomaticZenRule rule = new AutomaticZenRule(
                "sensible name",
                new ComponentName("pkg", "ShortClass"),
                null,
                Uri.parse("uri://short"),
                null, 0, true);

        rule.setName(longString);
        rule.setConditionId(longUri);
        rule.setConfigurationActivity(new ComponentName(longString, longString));

        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, rule.getName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                rule.getConditionId().toString().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                rule.getConfigurationActivity().getPackageName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                rule.getConfigurationActivity().getClassName().length());
    }

    @Test
    public void testLongInputsFromParcel() {
        // Create a rule with long fields, set directly via reflection so that we can confirm that
        // a rule with too-long fields that comes in via a parcel has its fields truncated directly.
        AutomaticZenRule rule = new AutomaticZenRule(
                "placeholder",
                new ComponentName("place", "holder"),
                null,
                Uri.parse("uri://placeholder"),
                null, 0, true);

        try {
            String longString = Strings.repeat("A", 65536);
            Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
            Field name = Class.forName(CLASS).getDeclaredField("name");
            name.setAccessible(true);
            name.set(rule, longString);
            Field conditionId = Class.forName(CLASS).getDeclaredField("conditionId");
            conditionId.setAccessible(true);
            conditionId.set(rule, longUri);
            Field owner = Class.forName(CLASS).getDeclaredField("owner");
            owner.setAccessible(true);
            owner.set(rule, new ComponentName(longString, longString));
            Field configActivity = Class.forName(CLASS).getDeclaredField("configurationActivity");
            configActivity.setAccessible(true);
            configActivity.set(rule, new ComponentName(longString, longString));
        } catch (NoSuchFieldException e) {
            fail(e.toString());
        } catch (ClassNotFoundException e) {
            fail(e.toString());
        } catch (IllegalAccessException e) {
            fail(e.toString());
        }

        Parcel parcel = Parcel.obtain();
        rule.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);

        AutomaticZenRule fromParcel = new AutomaticZenRule(parcel);
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, fromParcel.getName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                fromParcel.getConditionId().toString().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                fromParcel.getConfigurationActivity().getPackageName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                fromParcel.getConfigurationActivity().getClassName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                fromParcel.getOwner().getPackageName().length());
        assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                fromParcel.getOwner().getClassName().length());
    }
}