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

Commit 35f10e76 authored by Nan Wu's avatar Nan Wu
Browse files

Add ExtraKeyValueFilter to Intent Firewall

Allows filter intent's extra bundles

Bug: 428733109
Flag: android.security.enable_intent_firewall_extra_key_value_filter
Test: ExtraKeyValueFilterTest
Change-Id: Id6e4b759c81c095c25e6298cc37786000f323b92
parent 262d119c
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -166,6 +166,13 @@ flag {
    bug: "295238578"
}

flag {
    name: "enable_intent_firewall_extra_key_value_filter"
    namespace: "responsible_apis"
    description: "Flag for enable ExtraKeyValueFilter in IntentFirewall"
    bug: "428733109"
}

flag {
    name: "fail_on_parcel_size_mismatch"
    namespace: "responsible_apis"
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 com.android.server.firewall;
import static android.security.Flags.enableIntentFirewallExtraKeyValueFilter;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;

import com.android.internal.util.XmlUtils;

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

import java.io.IOException;

class ExtraKeyValueFilter implements Filter {
    private static final String ATTR_NAME = "name";
    private final String mKeyFilter;
    private final StringFilter mValueFilter;

    private ExtraKeyValueFilter(String keyFilter, StringFilter valueFilter) {
        this.mKeyFilter = keyFilter;
        this.mValueFilter = valueFilter;
    }

    @Override
    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
            int callerUid, int callerPid, String resolvedType, int receivingUid) {
        if (!enableIntentFirewallExtraKeyValueFilter()) {
            return false;
        }
        Bundle extras = intent.getExtras();
        if (extras == null) return false;
        return mValueFilter.matchesValue(extras.getString(mKeyFilter));
    }

    public static final FilterFactory FACTORY = new FilterFactory("extra") {
        @Override
        public Filter newFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
            String keyFilter = null;
            StringFilter valueFilter = null;

            final int depth = parser.getDepth();
            while (XmlUtils.nextElementWithin(parser, depth)) {
                String elementName = parser.getName();
                switch (elementName) {
                    case "key" -> {
                        if (keyFilter != null) {
                            throw new XmlPullParserException(
                                    "Multiple key elements found in an extra element");
                        }
                        keyFilter = parser.getAttributeValue(null, ATTR_NAME);
                    }
                    case "value" -> {
                        if (valueFilter != null) {
                            throw new XmlPullParserException(
                                    "Multiple value elements found in an extra element");
                        }
                        valueFilter = StringFilter.readFromXml(null, parser);
                    }
                    default -> throw new XmlPullParserException(
                                "Unknown element in extra rule: " + elementName);
                }
            }

            if (keyFilter == null || valueFilter == null) {
                throw new XmlPullParserException("<extra> must contain both <key> and <value>");
            }

            return new ExtraKeyValueFilter(keyFilter, valueFilter);
        }
    };

}
+51 −22
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.firewall;

import static android.security.Flags.enableIntentFirewallExtraKeyValueFilter;

import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.ComponentName;
@@ -87,7 +89,33 @@ public class IntentFirewall {
    private FirewallIntentResolver mServiceResolver = new FirewallIntentResolver();

    static {
        FilterFactory[] factories = new FilterFactory[] {
        FilterFactory[] factories;
        if (enableIntentFirewallExtraKeyValueFilter()) {
            factories = new FilterFactory[]{
                    AndFilter.FACTORY,
                    OrFilter.FACTORY,
                    NotFilter.FACTORY,

                    StringFilter.ACTION,
                    StringFilter.COMPONENT,
                    StringFilter.COMPONENT_NAME,
                    StringFilter.COMPONENT_PACKAGE,
                    StringFilter.DATA,
                    StringFilter.HOST,
                    StringFilter.MIME_TYPE,
                    StringFilter.SCHEME,
                    StringFilter.PATH,
                    StringFilter.SSP,

                    CategoryFilter.FACTORY,
                    SenderFilter.FACTORY,
                    SenderPackageFilter.FACTORY,
                    SenderPermissionFilter.FACTORY,
                    PortFilter.FACTORY,
                    ExtraKeyValueFilter.FACTORY
            };
        } else {
            factories = new FilterFactory[]{
                    AndFilter.FACTORY,
                    OrFilter.FACTORY,
                    NotFilter.FACTORY,
@@ -109,6 +137,7 @@ public class IntentFirewall {
                    SenderPermissionFilter.FACTORY,
                    PortFilter.FACTORY
            };
        }

        // load factor ~= .75
        factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3);
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 com.android.server.firewall;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.content.Intent;

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.StringReader;

@RunWith(AndroidJUnit4.class)
public class ExtraKeyValueFilterTest {

    private static final String EQUALS_KEY_FILTER_RULE = """
                <extra>
                  <key name="user_id"/>
                  <value startsWith="admin"/>
                </extra>
            """;

    private ExtraKeyValueFilter parseExtraFilter(String xml) throws Exception {
        XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
        parser.setInput(new StringReader(xml));
        parser.nextTag(); // <extra>
        return (ExtraKeyValueFilter) ExtraKeyValueFilter.FACTORY.newFilter(parser);
    }

    @Test
    public void testKeyFilterMatches_valueFilterMatches_success() throws Exception {
        ExtraKeyValueFilter filter = parseExtraFilter(EQUALS_KEY_FILTER_RULE);

        Intent intent = new Intent();
        intent.putExtra("user_id", "admin123");

        boolean result = filter.matches(null, null, intent, 0, 0, null, 0);
        assertTrue("Parsed ExtraKeyValueFilter should match the intent", result);
    }

    @Test
    public void testKeyFilterMatches_valueFilterDoesNotMatch_fail() throws Exception {
        ExtraKeyValueFilter filter = parseExtraFilter(EQUALS_KEY_FILTER_RULE);
        Intent intent = new Intent();
        intent.putExtra("user_id", "guest");

        boolean result = filter.matches(null, null, intent, 0, 0, null, 0);
        assertFalse("Parsed ExtraKeyValueFilter should not match the intent", result);
    }

    @Test
    public void testKeyFilterDoesNotMatch_valueFilterMatches_fail() throws Exception {
        ExtraKeyValueFilter filter = parseExtraFilter(EQUALS_KEY_FILTER_RULE);

        Intent intent = new Intent();
        intent.putExtra("user_id1", "admin2");

        boolean result = filter.matches(null, null, intent, 0, 0, null, 0);
        assertFalse("Parsed ExtraKeyValueFilter should not match the intent", result);
    }

    @Test
    public void testKeyValueFilterMixMatch_fail() throws Exception {
        ExtraKeyValueFilter filter = parseExtraFilter(EQUALS_KEY_FILTER_RULE);

        Intent intent = new Intent();
        intent.putExtra("user_id", "guest"); // matches key, but not value
        intent.putExtra("user_id_1", "admin123"); // matches value, but not key

        boolean result = filter.matches(null, null, intent, 0, 0, null, 0);
        assertFalse("Parsed ExtraKeyValueFilter should not match the intent", result);
    }

    @Test
    public void testKeyFilterMatches_valueFilterDoesNotMatchNonStringValue_fail()
            throws Exception {
        ExtraKeyValueFilter filter = parseExtraFilter(EQUALS_KEY_FILTER_RULE);
        Intent intent = new Intent();
        intent.putExtra("user_id", 1);

        boolean result = filter.matches(null, null, intent, 0, 0, null, 0);
        assertFalse("Parsed ExtraKeyValueFilter should not match the intent", result);
    }

    @Test(expected = XmlPullParserException.class)
    public void testWrongElementInExtraFilter_parseExtraFilter_fail() throws Exception {
        String xml = """
                    <extra>
                      <key name="user_id"/>
                      <value startsWith="admin"/>
                      <foo name="user_id"/>
                    </extra>
                """;
        parseExtraFilter(xml);
    }

    @Test(expected = XmlPullParserException.class)
    public void testDuplicateKeyElementInExtraFilter_parseExtraFilter_fail() throws Exception {
        String xml = """
                    <extra>
                      <key name="user_id"/>
                      <value startsWith="admin"/>
                      <key name="user_id2"/>
                    </extra>
                """;
        parseExtraFilter(xml);
    }

    @Test(expected = XmlPullParserException.class)
    public void testMissingKeyElementInExtraFilter_parseExtraFilter_fail() throws Exception {
        String xml = """
                    <extra>
                      <value startsWith="admin"/>
                    </extra>
                """;
        parseExtraFilter(xml);
    }
}