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

Commit 012739e9 authored by Benedict Wong's avatar Benedict Wong Committed by android-build-merger
Browse files

Merge "Add reference counted resources to IpSecService" am: e11366f3

am: adb6437f

Change-Id: I927ac534570b3836510e0fb4feda8cf544f5a9e4
parents ff6fcaec adb6437f
Loading
Loading
Loading
Loading
+199 −20
Original line number Original line Diff line number Diff line
@@ -57,11 +57,23 @@ import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;


import libcore.io.IoUtils;
import libcore.io.IoUtils;


/** @hide */
/**
 * A service to manage multiple clients that want to access the IpSec API. The service is
 * responsible for maintaining a list of clients and managing the resources (and related quotas)
 * that each of them own.
 *
 * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
 * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
 * thread is ever running at a time.
 *
 * @hide
 */
public class IpSecService extends IIpSecService.Stub {
public class IpSecService extends IIpSecService.Stub {
    private static final String TAG = "IpSecService";
    private static final String TAG = "IpSecService";
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -92,15 +104,15 @@ public class IpSecService extends IIpSecService.Stub {
    private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
    private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);


    @GuardedBy("this")
    @GuardedBy("this")
    private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
    private final KernelResourceArray<SpiRecord> mSpiRecords = new KernelResourceArray<>();


    @GuardedBy("this")
    @GuardedBy("this")
    private final ManagedResourceArray<TransformRecord> mTransformRecords =
    private final KernelResourceArray<TransformRecord> mTransformRecords =
            new ManagedResourceArray<>();
            new KernelResourceArray<>();


    @GuardedBy("this")
    @GuardedBy("this")
    private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
    private final KernelResourceArray<UdpSocketRecord> mUdpSocketRecords =
            new ManagedResourceArray<>();
            new KernelResourceArray<>();


    interface IpSecServiceConfiguration {
    interface IpSecServiceConfiguration {
        INetd getNetdInstance() throws RemoteException;
        INetd getNetdInstance() throws RemoteException;
@@ -120,6 +132,173 @@ public class IpSecService extends IIpSecService.Stub {


    private final IpSecServiceConfiguration mSrvConfig;
    private final IpSecServiceConfiguration mSrvConfig;


    /**
     * Interface for user-reference and kernel-resource cleanup.
     *
     * <p>This interface must be implemented for a resource to be reference counted.
     */
    @VisibleForTesting
    public interface IResource {
        /**
         * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
         * objects dependent on it.
         *
         * <p>Implementations of this method are expected to remove references to the IResource
         * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
         * the resource is considered invalid for user access or allocation or use in other
         * resources.
         *
         * <p>References to the IResource object may be held by other RefcountedResource objects,
         * and as such, the kernel resources and quota may not be cleaned up.
         */
        void invalidate() throws RemoteException;

        /**
         * Releases underlying resources and related quotas.
         *
         * <p>Implementations of this method are expected to remove all system resources that are
         * tracked by the IResource object. Due to other RefcountedResource objects potentially
         * having references to the IResource object, releaseKernelResources may not always be
         * called from releaseIfUnreferencedRecursively().
         */
        void freeUnderlyingResources() throws RemoteException;
    }

    /**
     * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
     *
     * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
     * RefcountedResource object creates an explicit reference that must be freed by calling
     * userRelease(). Additionally, adding this object as a child of another RefcountedResource
     * object will add an implicit reference.
     *
     * <p>Resources are cleaned up when all references, both implicit and explicit, are released
     * (ie, when userRelease() is called and when all parents have called releaseReference() on this
     * object.)
     */
    @VisibleForTesting
    public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
        private final T mResource;
        private final List<RefcountedResource> mChildren;
        int mRefCount = 1; // starts at 1 for user's reference.
        IBinder mBinder;

        RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
            synchronized (IpSecService.this) {
                this.mResource = resource;
                this.mChildren = new ArrayList<>(children.length);
                this.mBinder = binder;

                for (RefcountedResource child : children) {
                    mChildren.add(child);
                    child.mRefCount++;
                }

                try {
                    mBinder.linkToDeath(this, 0);
                } catch (RemoteException e) {
                    binderDied();
                }
            }
        }

        /**
         * If the Binder object dies, this function is called to free the system resources that are
         * being managed by this record and to subsequently release this record for garbage
         * collection
         */
        @Override
        public void binderDied() {
            synchronized (IpSecService.this) {
                try {
                    userRelease();
                } catch (Exception e) {
                    Log.e(TAG, "Failed to release resource: " + e);
                }
            }
        }

        public T getResource() {
            return mResource;
        }

        /**
         * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
         * arrays)
         *
         * <p>If this method has been previously called, the RefcountedResource's binder field will
         * be null, and the method will return without performing the cleanup a second time.
         *
         * <p>Note that calling this function does not imply that kernel resources will be freed at
         * this time, or that the related quota will be returned. Such actions will only be
         * performed upon the reference count reaching zero.
         */
        @GuardedBy("IpSecService.this")
        public void userRelease() throws RemoteException {
            // Prevent users from putting reference counts into a bad state by calling
            // userRelease() multiple times.
            if (mBinder == null) {
                return;
            }

            mBinder.unlinkToDeath(this, 0);
            mBinder = null;

            mResource.invalidate();

            releaseReference();
        }

        /**
         * Removes a reference to this resource. If the resultant reference count is zero, the
         * underlying resources are freed, and references to all child resources are also dropped
         * recursively (resulting in them freeing their resources and children, etcetera)
         *
         * <p>This method also sets the reference count to an invalid value (-1) to signify that it
         * has been fully released. Any subsequent calls to this method will result in an
         * IllegalStateException being thrown due to resource already having been previously
         * released
         */
        @VisibleForTesting
        @GuardedBy("IpSecService.this")
        public void releaseReference() throws RemoteException {
            mRefCount--;

            if (mRefCount > 0) {
                return;
            } else if (mRefCount < 0) {
                throw new IllegalStateException(
                        "Invalid operation - resource has already been released.");
            }

            // Cleanup own resources
            mResource.freeUnderlyingResources();

            // Cleanup child resources as needed
            for (RefcountedResource<? extends IResource> child : mChildren) {
                child.releaseReference();
            }

            // Enforce that resource cleanup can only be called once
            // By decrementing the refcount (from 0 to -1), the next call will throw an
            // IllegalStateException - it has already been released fully.
            mRefCount--;
        }

        @Override
        public String toString() {
            return new StringBuilder()
                    .append("{mResource=")
                    .append(mResource)
                    .append(", mRefCount=")
                    .append(mRefCount)
                    .append(", mChildren=")
                    .append(mChildren)
                    .append("}")
                    .toString();
        }
    }

    /* Very simple counting class that looks much like a counting semaphore */
    /* Very simple counting class that looks much like a counting semaphore */
    public static class ResourceTracker {
    public static class ResourceTracker {
        private final int mMax;
        private final int mMax;
@@ -211,13 +390,13 @@ public class IpSecService extends IIpSecService.Stub {
    private final UserQuotaTracker mUserQuotaTracker = new UserQuotaTracker();
    private final UserQuotaTracker mUserQuotaTracker = new UserQuotaTracker();


    /**
    /**
     * The ManagedResource class provides a facility to cleanly and reliably release system
     * The KernelResource class provides a facility to cleanly and reliably release system
     * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
     * resources. It relies on two things: an IBinder that allows KernelResource to automatically
     * clean up in the event that the Binder dies and a user-provided resourceId that should
     * clean up in the event that the Binder dies and a user-provided resourceId that should
     * uniquely identify the managed resource. To use this class, the user should implement the
     * uniquely identify the managed resource. To use this class, the user should implement the
     * releaseResources() method that is responsible for releasing system resources when invoked.
     * releaseResources() method that is responsible for releasing system resources when invoked.
     */
     */
    private abstract class ManagedResource implements IBinder.DeathRecipient {
    private abstract class KernelResource implements IBinder.DeathRecipient {
        final int pid;
        final int pid;
        final int uid;
        final int uid;
        private IBinder mBinder;
        private IBinder mBinder;
@@ -225,7 +404,7 @@ public class IpSecService extends IIpSecService.Stub {


        private AtomicInteger mReferenceCount = new AtomicInteger(0);
        private AtomicInteger mReferenceCount = new AtomicInteger(0);


        ManagedResource(int resourceId, IBinder binder) {
        KernelResource(int resourceId, IBinder binder) {
            super();
            super();
            if (resourceId == INVALID_RESOURCE_ID) {
            if (resourceId == INVALID_RESOURCE_ID) {
                throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
                throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
@@ -341,7 +520,7 @@ public class IpSecService extends IIpSecService.Stub {
    /**
    /**
     * Minimal wrapper around SparseArray that performs ownership validation on element accesses.
     * Minimal wrapper around SparseArray that performs ownership validation on element accesses.
     */
     */
    private class ManagedResourceArray<T extends ManagedResource> {
    private class KernelResourceArray<T extends KernelResource> {
        SparseArray<T> mArray = new SparseArray<>();
        SparseArray<T> mArray = new SparseArray<>();


        T getAndCheckOwner(int key) {
        T getAndCheckOwner(int key) {
@@ -369,7 +548,7 @@ public class IpSecService extends IIpSecService.Stub {
        }
        }
    }
    }


    private final class TransformRecord extends ManagedResource {
    private final class TransformRecord extends KernelResource {
        private final IpSecConfig mConfig;
        private final IpSecConfig mConfig;
        private final SpiRecord[] mSpis;
        private final SpiRecord[] mSpis;
        private final UdpSocketRecord mSocket;
        private final UdpSocketRecord mSocket;
@@ -456,7 +635,7 @@ public class IpSecService extends IIpSecService.Stub {
        }
        }
    }
    }


    private final class SpiRecord extends ManagedResource {
    private final class SpiRecord extends KernelResource {
        private final int mDirection;
        private final int mDirection;
        private final String mLocalAddress;
        private final String mLocalAddress;
        private final String mRemoteAddress;
        private final String mRemoteAddress;
@@ -544,7 +723,7 @@ public class IpSecService extends IIpSecService.Stub {
        }
        }
    }
    }


    private final class UdpSocketRecord extends ManagedResource {
    private final class UdpSocketRecord extends KernelResource {
        private FileDescriptor mSocket;
        private FileDescriptor mSocket;
        private final int mPort;
        private final int mPort;


@@ -718,8 +897,8 @@ public class IpSecService extends IIpSecService.Stub {
    /* This method should only be called from Binder threads. Do not call this from
    /* This method should only be called from Binder threads. Do not call this from
     * within the system server as it will crash the system on failure.
     * within the system server as it will crash the system on failure.
     */
     */
    private synchronized <T extends ManagedResource> void releaseManagedResource(
    private synchronized <T extends KernelResource> void releaseKernelResource(
            ManagedResourceArray<T> resArray, int resourceId, String typeName)
            KernelResourceArray<T> resArray, int resourceId, String typeName)
            throws RemoteException {
            throws RemoteException {
        // We want to non-destructively get so that we can check credentials before removing
        // We want to non-destructively get so that we can check credentials before removing
        // this from the records.
        // this from the records.
@@ -737,7 +916,7 @@ public class IpSecService extends IIpSecService.Stub {
    /** Release a previously allocated SPI that has been registered with the system server */
    /** Release a previously allocated SPI that has been registered with the system server */
    @Override
    @Override
    public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
    public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
        releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
        releaseKernelResource(mSpiRecords, resourceId, "SecurityParameterIndex");
    }
    }


    /**
    /**
@@ -827,7 +1006,7 @@ public class IpSecService extends IIpSecService.Stub {
    @Override
    @Override
    public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
    public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {


        releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
        releaseKernelResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
    }
    }


    /**
    /**
@@ -974,7 +1153,7 @@ public class IpSecService extends IIpSecService.Stub {
     */
     */
    @Override
    @Override
    public void deleteTransportModeTransform(int resourceId) throws RemoteException {
    public void deleteTransportModeTransform(int resourceId) throws RemoteException {
        releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
        releaseKernelResource(mTransformRecords, resourceId, "IpSecTransform");
    }
    }


    /**
    /**
@@ -984,7 +1163,7 @@ public class IpSecService extends IIpSecService.Stub {
    @Override
    @Override
    public synchronized void applyTransportModeTransform(
    public synchronized void applyTransportModeTransform(
            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
            ParcelFileDescriptor socket, int resourceId) throws RemoteException {
        // Synchronize liberally here because we are using ManagedResources in this block
        // Synchronize liberally here because we are using KernelResources in this block
        TransformRecord info;
        TransformRecord info;
        // FIXME: this code should be factored out into a security check + getter
        // FIXME: this code should be factored out into a security check + getter
        info = mTransformRecords.getAndCheckOwner(resourceId);
        info = mTransformRecords.getAndCheckOwner(resourceId);
+356 −0

File added.

Preview size limit exceeded, changes collapsed.