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

Commit cdf3d2ed authored by Nan Wu's avatar Nan Wu Committed by Android (Google) Code Review
Browse files

Merge "Add ExtraKeyValueFilter to Intent Firewall" into main

parents b59fb077 35f10e76
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);
    }
}