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

Commit babe5d73 authored by Benedict Wong's avatar Benedict Wong
Browse files

[ipsec-qtaguid] Tag sockets upon creation of encap sockets

Added calls to tag encap sockets to that of the UID for which the encap
socket is being created on behalf of. This ensures that all data
accounting generated for the UDP-encap-ESP socket is correctly billed to
the right UID.

Bug: 62994731
Test: New tests added to IpSecServiceTest.java, passing
Change-Id: I15365ea9c982fd7b4e3cdeff314ddfba2289c86e
parent a4239cf7
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -253,6 +253,13 @@ package android.net {
    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
  }

  public class TrafficStats {
    method public static long getLoopbackRxBytes();
    method public static long getLoopbackRxPackets();
    method public static long getLoopbackTxBytes();
    method public static long getLoopbackTxPackets();
  }

}

package android.os {
+27 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.net;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.DownloadManager;
import android.app.backup.BackupManager;
import android.app.usage.NetworkStatsManager;
@@ -154,6 +155,8 @@ public class TrafficStats {

    private static Object sProfilingLock = new Object();

    private static final String LOOPBACK_IFACE = "lo";

    /**
     * Set active tag to use when accounting {@link Socket} traffic originating
     * from the current thread. Only one active tag per thread is supported.
@@ -542,6 +545,30 @@ public class TrafficStats {
        return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
    }

    /** {@hide} */
    @TestApi
    public static long getLoopbackTxPackets() {
        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_PACKETS);
    }

    /** {@hide} */
    @TestApi
    public static long getLoopbackRxPackets() {
        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_PACKETS);
    }

    /** {@hide} */
    @TestApi
    public static long getLoopbackTxBytes() {
        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_BYTES);
    }

    /** {@hide} */
    @TestApi
    public static long getLoopbackRxBytes() {
        return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_BYTES);
    }

    /**
     * Return number of packets transmitted since device boot. Counts packets
     * across all network interfaces, and always increases monotonically since
+40 −1
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.IBinder;
@@ -120,6 +121,7 @@ public class IpSecService extends IIpSecService.Stub {
    }

    private final IpSecServiceConfiguration mSrvConfig;
    final UidFdTagger mUidFdTagger;

    /**
     * Interface for user-reference and kernel-resource cleanup.
@@ -762,8 +764,23 @@ public class IpSecService extends IIpSecService.Stub {
    /** @hide */
    @VisibleForTesting
    public IpSecService(Context context, IpSecServiceConfiguration config) {
        this(context, config, (fd, uid) ->  {
            try{
                TrafficStats.setThreadStatsUid(uid);
                TrafficStats.tagFileDescriptor(fd);
            } finally {
                TrafficStats.clearThreadStatsUid();
            }
        });
    }

    /** @hide */
    @VisibleForTesting
    public IpSecService(
            Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
        mContext = context;
        mSrvConfig = config;
        mUidFdTagger = uidFdTagger;
    }

    public void systemReady() {
@@ -924,6 +941,26 @@ public class IpSecService extends IIpSecService.Stub {
        throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
    }

    /**
     * Functional interface to do traffic tagging of given sockets to UIDs.
     *
     * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
     * sockets are billed to the UID that the UDP encap socket was created on behalf of.
     *
     * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
     * methods that cannot be easily mocked/tested.
     */
    @VisibleForTesting
    public interface UidFdTagger {
        /**
         * Sets socket tag to assign all traffic to the provided UID.
         *
         * <p>Since the socket is created on behalf of an unprivileged application, all traffic
         * should be accounted to the UID of the unprivileged application.
         */
        public void tag(FileDescriptor fd, int uid) throws IOException;
    }

    /**
     * Open a socket via the system server and bind it to the specified port (random if port=0).
     * This will return a PFD to the user that represent a bound UDP socket. The system server will
@@ -939,7 +976,8 @@ public class IpSecService extends IIpSecService.Stub {
        }
        checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");

        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
        int callingUid = Binder.getCallingUid();
        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
        int resourceId = mNextResourceId.getAndIncrement();
        FileDescriptor sockFd = null;
        try {
@@ -948,6 +986,7 @@ public class IpSecService extends IIpSecService.Stub {
            }

            sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            mUidFdTagger.tag(sockFd, callingUid);

            if (port != 0) {
                Log.v(TAG, "Binding to port " + port);
+64 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -40,10 +41,14 @@ import android.net.IpSecTransform;
import android.net.IpSecUdpEncapResponse;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;

import dalvik.system.SocketTagger;

import java.io.FileDescriptor;
import java.net.InetAddress;
@@ -56,6 +61,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;

/** Unit tests for {@link IpSecService}. */
@SmallTest
@@ -411,4 +417,62 @@ public class IpSecServiceTest {
            mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
        }
    }

    @Test
    public void testUidFdtagger() throws Exception {
        SocketTagger actualSocketTagger = SocketTagger.get();

        try {
            FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

            // Has to be done after socket creation because BlockGuardOS calls tag on new sockets
            SocketTagger mockSocketTagger = mock(SocketTagger.class);
            SocketTagger.set(mockSocketTagger);

            mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID);
            verify(mockSocketTagger).tag(eq(sockFd));
        } finally {
            SocketTagger.set(actualSocketTagger);
        }
    }

    /**
     * Checks if two file descriptors point to the same file.
     *
     * <p>According to stat.h documentation, the correct way to check for equivalent or duplicated
     * file descriptors is to check their inode and device. These two entries uniquely identify any
     * file.
     */
    private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) {
        try {
            StructStat fd1Stat = Os.fstat(fd1);
            StructStat fd2Stat = Os.fstat(fd2);

            return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev;
        } catch (ErrnoException e) {
            return false;
        }
    }

    @Test
    public void testOpenUdpEncapSocketTagsSocket() throws Exception {
        IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class);
        IpSecService testIpSecService =
                new IpSecService(mMockContext, mMockIpSecSrvConfig, mockTagger);

        IpSecUdpEncapResponse udpEncapResp =
                testIpSecService.openUdpEncapsulationSocket(0, new Binder());
        assertNotNull(udpEncapResp);
        assertEquals(IpSecManager.Status.OK, udpEncapResp.status);

        FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
        ArgumentMatcher<FileDescriptor> fdMatcher =
                (argFd) -> {
                    return fileDescriptorsEqual(sockFd, argFd);
                };
        verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid()));

        testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
        udpEncapResp.fileDescriptor.close();
    }
}