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

Commit 7f5a2fe7 authored by Benedict Wong's avatar Benedict Wong
Browse files

Add internal support for IPsec forward policies

This change adds support for IPsec forward policies, which are necessary
for packets to be allowed to be forwarded to another interface, as is
the case with tethering. This is necessary and useful only within the
system server, and as such is not exposed as a public API.

This change is safe, since the addition of a FWD policy on IPsec tunnel
interfaces will by default block forwarded traffic (as would be the case
without this patch). In the event that the (system) owner of the tunnel
requires support for forwarded packets (eg tethering), this patch allows
application of transforms in the FWD direction as well.

This will be used to ensure that the VCN can be used as the underlying
network for the purposes of tethering.

Bug: 185495453
Test: atest IpSecServiceTest
Test: atest IpSecServiceParameterizedTest
Test: manual testing with tethering over VCN
Change-Id: I74ecea71f1954029f6fbdbe34598c82e0aac386b
parent 61f27d7d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -78,6 +78,16 @@ public final class IpSecManager {
     */
    public static final int DIRECTION_OUT = 1;

    /**
     * Used when applying a transform to direct traffic through an {@link IpSecTransform} for
     * forwarding between interfaces.
     *
     * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
     *
     * @hide
     */
    public static final int DIRECTION_FWD = 2;

    /**
     * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
     *
+19 −4
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import android.net.util.NetdService;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
@@ -65,6 +66,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.PermissionUtils;

import libcore.io.IoUtils;

@@ -466,8 +468,7 @@ public class IpSecService extends IIpSecService.Stub {

        /** Safety method; guards against access of other user's UserRecords */
        private void checkCallerUid(int uid) {
            if (uid != Binder.getCallingUid()
                    && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
            if (uid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid()) {
                throw new SecurityException("Attempted access of unowned resources");
            }
        }
@@ -1105,11 +1106,15 @@ public class IpSecService extends IIpSecService.Stub {
     * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
     * DIRECTION_IN or DIRECTION_OUT
     */
    private static void checkDirection(int direction) {
    private void checkDirection(int direction) {
        switch (direction) {
            case IpSecManager.DIRECTION_OUT:
            case IpSecManager.DIRECTION_IN:
                return;
            case IpSecManager.DIRECTION_FWD:
                // Only NETWORK_STACK or PERMISSION_NETWORK_STACK allowed to use forward policies
                PermissionUtils.enforceNetworkStackPermission(mContext);
                return;
        }
        throw new IllegalArgumentException("Invalid Direction: " + direction);
    }
@@ -1353,6 +1358,16 @@ public class IpSecService extends IIpSecService.Stub {
                        ikey,
                        0xffffffff,
                        resourceId);
                netd.ipSecAddSecurityPolicy(
                        callerUid,
                        selAddrFamily,
                        IpSecManager.DIRECTION_FWD,
                        remoteAddr,
                        localAddr,
                        0,
                        ikey,
                        0xffffffff,
                        resourceId);
            }

            userRecord.mTunnelInterfaceRecords.put(
@@ -1820,7 +1835,7 @@ public class IpSecService extends IIpSecService.Stub {
        int mark =
                (direction == IpSecManager.DIRECTION_OUT)
                        ? tunnelInterfaceInfo.getOkey()
                        : tunnelInterfaceInfo.getIkey();
                        : tunnelInterfaceInfo.getIkey(); // Ikey also used for FWD policies

        try {
            // Default to using the invalid SPI of 0 for inbound SAs. This allows policies to skip
+85 −15
Original line number Diff line number Diff line
@@ -16,9 +16,14 @@

package com.android.server;

import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import static android.net.IpSecManager.DIRECTION_FWD;
import static android.net.IpSecManager.DIRECTION_IN;
import static android.net.IpSecManager.DIRECTION_OUT;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;

@@ -56,6 +61,7 @@ import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.system.Os;
import android.test.mock.MockContext;
import android.util.ArraySet;

import androidx.test.filters.SmallTest;

@@ -71,6 +77,7 @@ import java.net.Inet4Address;
import java.net.Socket;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;

/** Unit tests for {@link IpSecService}. */
@SmallTest
@@ -119,7 +126,18 @@ public class IpSecServiceParameterizedTest {
    AppOpsManager mMockAppOps = mock(AppOpsManager.class);
    ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);

    MockContext mMockContext = new MockContext() {
    TestContext mTestContext = new TestContext();

    private class TestContext extends MockContext {
        private Set<String> mAllowedPermissions = new ArraySet<>(Arrays.asList(
                android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
                android.Manifest.permission.NETWORK_STACK,
                PERMISSION_MAINLINE_NETWORK_STACK));

        private void setAllowedPermissions(String... permissions) {
            mAllowedPermissions = new ArraySet<>(permissions);
        }

        @Override
        public Object getSystemService(String name) {
            switch(name) {
@@ -147,20 +165,22 @@ public class IpSecServiceParameterizedTest {

        @Override
        public void enforceCallingOrSelfPermission(String permission, String message) {
            if (permission == android.Manifest.permission.MANAGE_IPSEC_TUNNELS) {
            if (mAllowedPermissions.contains(permission)) {
                return;
            }
            } else {
                throw new SecurityException("Unavailable permission requested");
            }
        }

        @Override
        public int checkCallingOrSelfPermission(String permission) {
            if (android.Manifest.permission.NETWORK_STACK.equals(permission)) {
            if (mAllowedPermissions.contains(permission)) {
                return PERMISSION_GRANTED;
            } else {
                return PERMISSION_DENIED;
            }
        }
            throw new UnsupportedOperationException();
    }
    };

    INetd mMockNetd;
    PackageManager mMockPkgMgr;
@@ -194,7 +214,7 @@ public class IpSecServiceParameterizedTest {
        mMockNetd = mock(INetd.class);
        mMockPkgMgr = mock(PackageManager.class);
        mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
        mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
        mIpSecService = new IpSecService(mTestContext, mMockIpSecSrvConfig);

        // Injecting mock netd
        when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
@@ -664,6 +684,21 @@ public class IpSecServiceParameterizedTest {

        assertNotNull(createTunnelResp);
        assertEquals(IpSecManager.Status.OK, createTunnelResp.status);
        for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT, DIRECTION_FWD}) {
            for (int selAddrFamily : ADDRESS_FAMILIES) {
                verify(mMockNetd).ipSecAddSecurityPolicy(
                        eq(mUid),
                        eq(selAddrFamily),
                        eq(direction),
                        anyString(),
                        anyString(),
                        eq(0),
                        anyInt(), // iKey/oKey
                        anyInt(), // mask
                        eq(createTunnelResp.resourceId));
            }
        }

        return createTunnelResp;
    }

@@ -798,16 +833,51 @@ public class IpSecServiceParameterizedTest {
    }

    @Test
    public void testApplyTunnelModeTransform() throws Exception {
        verifyApplyTunnelModeTransformCommon(false);
    public void testApplyTunnelModeTransformOutbound() throws Exception {
        verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT);
    }

    @Test
    public void testApplyTunnelModeTransformReleasedSpi() throws Exception {
        verifyApplyTunnelModeTransformCommon(true);
    public void testApplyTunnelModeTransformOutboundNonNetworkStack() throws Exception {
        mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
        verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT);
    }

    public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply) throws Exception {
    @Test
    public void testApplyTunnelModeTransformOutboundReleasedSpi() throws Exception {
        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_OUT);
    }

    @Test
    public void testApplyTunnelModeTransformInbound() throws Exception {
        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN);
    }

    @Test
    public void testApplyTunnelModeTransformInboundNonNetworkStack() throws Exception {
        mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);
        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN);
    }

    @Test
    public void testApplyTunnelModeTransformForward() throws Exception {
        verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD);
    }

    @Test
    public void testApplyTunnelModeTransformForwardNonNetworkStack() throws Exception {
        mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS);

        try {
            verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD);
            fail("Expected security exception due to use of forward policies without NETWORK_STACK"
                     + " or MAINLINE_NETWORK_STACK permission");
        } catch (SecurityException expected) {
        }
    }

    public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction)
            throws Exception {
        IpSecConfig ipSecConfig = new IpSecConfig();
        ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
@@ -825,17 +895,17 @@ public class IpSecServiceParameterizedTest {
        int transformResourceId = createTransformResp.resourceId;
        int tunnelResourceId = createTunnelResp.resourceId;
        mIpSecService.applyTunnelModeTransform(
                tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE);
                tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE);

        for (int selAddrFamily : ADDRESS_FAMILIES) {
            verify(mMockNetd)
                    .ipSecUpdateSecurityPolicy(
                            eq(mUid),
                            eq(selAddrFamily),
                            eq(IpSecManager.DIRECTION_OUT),
                            eq(direction),
                            anyString(),
                            anyString(),
                            eq(TEST_SPI),
                            eq(direction == DIRECTION_OUT ? TEST_SPI : 0),
                            anyInt(), // iKey/oKey
                            anyInt(), // mask
                            eq(tunnelResourceId));