Convert IpSecService resources to use refcounting

This is part 2 of 2 of the refcounting refactor for IpSecService
resources.

Switched ManagedResources to use RefcountedResource structure for
managing reference counts and eventual cleanup. Further, resource arrays
and quota management have been aggregated into a UserRecord for better
isolation. UID access checking has been similarly moved into the
UserRecordTracker, and resourceId checking has been rolled into
RefcountedResourceArray's accessor methods.

Bug: 63409385
Test: CTS, all unit tests run on aosp_marlin-eng, new tests added
Change-Id: Iee52dd1c9d2583bb6bfaf65be87569e9d50a5b63
This commit is contained in:
Benedict Wong
2017-11-16 15:27:22 -08:00
parent 20f80d2083
commit 6855aeeea2

View File

@@ -103,17 +103,6 @@ public class IpSecService extends IIpSecService.Stub {
/** Should be a never-repeating global ID for resources */ /** Should be a never-repeating global ID for resources */
private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
@GuardedBy("this")
private final KernelResourceArray<SpiRecord> mSpiRecords = new KernelResourceArray<>();
@GuardedBy("this")
private final KernelResourceArray<TransformRecord> mTransformRecords =
new KernelResourceArray<>();
@GuardedBy("this")
private final KernelResourceArray<UdpSocketRecord> mUdpSocketRecords =
new KernelResourceArray<>();
interface IpSecServiceConfiguration { interface IpSecServiceConfiguration {
INetd getNetdInstance() throws RemoteException; INetd getNetdInstance() throws RemoteException;
@@ -158,7 +147,7 @@ public class IpSecService extends IIpSecService.Stub {
* *
* <p>Implementations of this method are expected to remove all system resources that are * <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 * tracked by the IResource object. Due to other RefcountedResource objects potentially
* having references to the IResource object, releaseKernelResources may not always be * having references to the IResource object, freeUnderlyingResources may not always be
* called from releaseIfUnreferencedRecursively(). * called from releaseIfUnreferencedRecursively().
*/ */
void freeUnderlyingResources() throws RemoteException; void freeUnderlyingResources() throws RemoteException;
@@ -204,7 +193,7 @@ public class IpSecService extends IIpSecService.Stub {
/** /**
* If the Binder object dies, this function is called to free the system resources that are * 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 * being tracked by this record and to subsequently release this record for garbage
* collection * collection
*/ */
@Override @Override
@@ -300,7 +289,8 @@ public class IpSecService extends IIpSecService.Stub {
} }
/* 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 { @VisibleForTesting
static class ResourceTracker {
private final int mMax; private final int mMax;
int mCurrent; int mCurrent;
@@ -309,18 +299,18 @@ public class IpSecService extends IIpSecService.Stub {
mCurrent = 0; mCurrent = 0;
} }
synchronized boolean isAvailable() { boolean isAvailable() {
return (mCurrent < mMax); return (mCurrent < mMax);
} }
synchronized void take() { void take() {
if (!isAvailable()) { if (!isAvailable()) {
Log.wtf(TAG, "Too many resources allocated!"); Log.wtf(TAG, "Too many resources allocated!");
} }
mCurrent++; mCurrent++;
} }
synchronized void give() { void give() {
if (mCurrent <= 0) { if (mCurrent <= 0) {
Log.wtf(TAG, "We've released this resource too many times"); Log.wtf(TAG, "We've released this resource too many times");
} }
@@ -339,40 +329,70 @@ public class IpSecService extends IIpSecService.Stub {
} }
} }
private static final class UserQuotaTracker { @VisibleForTesting
/* Maximum number of UDP Encap Sockets that a single UID may possess */ static final class UserRecord {
/* Type names */
public static final String TYPENAME_SPI = "SecurityParameterIndex";
public static final String TYPENAME_TRANSFORM = "IpSecTransform";
public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket";
/* Maximum number of each type of resource that a single UID may possess */
public static final int MAX_NUM_ENCAP_SOCKETS = 2; public static final int MAX_NUM_ENCAP_SOCKETS = 2;
/* Maximum number of IPsec Transforms that a single UID may possess */
public static final int MAX_NUM_TRANSFORMS = 4; public static final int MAX_NUM_TRANSFORMS = 4;
/* Maximum number of IPsec Transforms that a single UID may possess */
public static final int MAX_NUM_SPIS = 8; public static final int MAX_NUM_SPIS = 8;
/* Record for one users's IpSecService-managed objects */ final RefcountedResourceArray<SpiRecord> mSpiRecords =
public static class UserRecord { new RefcountedResourceArray<>(TYPENAME_SPI);
public final ResourceTracker socket = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS); final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
public final ResourceTracker transform = new ResourceTracker(MAX_NUM_TRANSFORMS);
public final ResourceTracker spi = new ResourceTracker(MAX_NUM_SPIS);
@Override final RefcountedResourceArray<TransformRecord> mTransformRecords =
public String toString() { new RefcountedResourceArray<>(TYPENAME_TRANSFORM);
return new StringBuilder() final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
.append("{socket=")
.append(socket) final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
.append(", transform=") new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET);
.append(transform) final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
.append(", spi=")
.append(spi) void removeSpiRecord(int resourceId) {
.append("}") mSpiRecords.remove(resourceId);
.toString();
}
} }
void removeTransformRecord(int resourceId) {
mTransformRecords.remove(resourceId);
}
void removeEncapSocketRecord(int resourceId) {
mEncapSocketRecords.remove(resourceId);
}
@Override
public String toString() {
return new StringBuilder()
.append("{mSpiQuotaTracker=")
.append(mSpiQuotaTracker)
.append(", mTransformQuotaTracker=")
.append(mTransformQuotaTracker)
.append(", mSocketQuotaTracker=")
.append(mSocketQuotaTracker)
.append(", mSpiRecords=")
.append(mSpiRecords)
.append(", mTransformRecords=")
.append(mTransformRecords)
.append(", mEncapSocketRecords=")
.append(mEncapSocketRecords)
.append("}")
.toString();
}
}
@VisibleForTesting
static final class UserResourceTracker {
private final SparseArray<UserRecord> mUserRecords = new SparseArray<>(); private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
/* a never-fail getter so that we can populate the list of UIDs as-needed */ /** Never-fail getter that populates the list of UIDs as-needed */
public synchronized UserRecord getUserRecord(int uid) { public UserRecord getUserRecord(int uid) {
checkCallerUid(uid);
UserRecord r = mUserRecords.get(uid); UserRecord r = mUserRecords.get(uid);
if (r == null) { if (r == null) {
r = new UserRecord(); r = new UserRecord();
@@ -381,122 +401,56 @@ public class IpSecService extends IIpSecService.Stub {
return r; return r;
} }
/** 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()) {
throw new SecurityException("Attempted access of unowned resources");
}
}
@Override @Override
public String toString() { public String toString() {
return mUserRecords.toString(); return mUserRecords.toString();
} }
} }
private final UserQuotaTracker mUserQuotaTracker = new UserQuotaTracker(); @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
/** /**
* The KernelResource class provides a facility to cleanly and reliably release system * The KernelResourceRecord class provides a facility to cleanly and reliably track system
* resources. It relies on two things: an IBinder that allows KernelResource to automatically * resources. It relies on a provided resourceId that should uniquely identify the kernel
* clean up in the event that the Binder dies and a user-provided resourceId that should * resource. To use this class, the user should implement the invalidate() and
* uniquely identify the managed resource. To use this class, the user should implement the * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
* releaseResources() method that is responsible for releasing system resources when invoked. * tracking arrays and kernel resources, respectively
*/ */
private abstract class KernelResource implements IBinder.DeathRecipient { private abstract class KernelResourceRecord implements IResource {
final int pid; final int pid;
final int uid; final int uid;
private IBinder mBinder; protected final int mResourceId;
protected int mResourceId;
private AtomicInteger mReferenceCount = new AtomicInteger(0); KernelResourceRecord(int resourceId) {
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");
} }
mBinder = binder;
mResourceId = resourceId; mResourceId = resourceId;
pid = Binder.getCallingPid(); pid = Binder.getCallingPid();
uid = Binder.getCallingUid(); uid = Binder.getCallingUid();
getResourceTracker().take(); getResourceTracker().take();
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
} }
public void addReference() { @Override
mReferenceCount.incrementAndGet(); public abstract void invalidate() throws RemoteException;
/** Convenience method; retrieves the user resource record for the stored UID. */
protected UserRecord getUserRecord() {
return mUserResourceTracker.getUserRecord(uid);
} }
public void removeReference() { @Override
if (mReferenceCount.decrementAndGet() < 0) { public abstract void freeUnderlyingResources() throws RemoteException;
Log.wtf(TAG, "Programming error: negative reference count");
}
}
public boolean isReferenced() {
return (mReferenceCount.get() > 0);
}
/**
* Ensures that the caller is either the owner of this resource or has the system UID and
* throws a SecurityException otherwise.
*/
public void checkOwnerOrSystem() {
if (uid != Binder.getCallingUid()
&& android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
throw new SecurityException("Only the owner may access managed resources!");
}
}
/**
* When this record is no longer needed for managing system resources this function should
* clean up all system resources and nullify the record. This function shall perform all
* necessary cleanup of the resources managed by this record.
*
* <p>NOTE: this function verifies ownership before allowing resources to be freed.
*/
public final void release() throws RemoteException {
synchronized (IpSecService.this) {
if (isReferenced()) {
throw new IllegalStateException(
"Cannot release a resource that has active references!");
}
if (mResourceId == INVALID_RESOURCE_ID) {
return;
}
releaseResources();
getResourceTracker().give();
if (mBinder != null) {
mBinder.unlinkToDeath(this, 0);
}
mBinder = null;
mResourceId = INVALID_RESOURCE_ID;
}
}
/**
* 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
*/
public final void binderDied() {
try {
release();
} catch (Exception e) {
Log.e(TAG, "Failed to release resource: " + e);
}
}
/**
* Implement this method to release all system resources that are being protected by this
* record. Once the resources are released, the record should be invalidated and no longer
* used by calling release(). This should NEVER be called directly.
*
* <p>Calls to this are always guarded by IpSecService#this
*/
protected abstract void releaseResources() throws RemoteException;
/** Get the resource tracker for this resource */ /** Get the resource tracker for this resource */
protected abstract ResourceTracker getResourceTracker(); protected abstract ResourceTracker getResourceTracker();
@@ -510,30 +464,52 @@ public class IpSecService extends IIpSecService.Stub {
.append(pid) .append(pid)
.append(", uid=") .append(", uid=")
.append(uid) .append(uid)
.append(", mReferenceCount=")
.append(mReferenceCount.get())
.append("}") .append("}")
.toString(); .toString();
} }
}; };
// TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many
// more things as changed.
/** /**
* Minimal wrapper around SparseArray that performs ownership validation on element accesses. * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
*
* <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
* if a key is not found during a retrieval process.
*/ */
private class KernelResourceArray<T extends KernelResource> { static class RefcountedResourceArray<T extends IResource> {
SparseArray<T> mArray = new SparseArray<>(); SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
private final String mTypeName;
T getAndCheckOwner(int key) { public RefcountedResourceArray(String typeName) {
T val = mArray.get(key); this.mTypeName = typeName;
// The value should never be null unless the resource doesn't exist
// (since we do not allow null resources to be added).
if (val != null) {
val.checkOwnerOrSystem();
}
return val;
} }
void put(int key, T obj) { /**
* Accessor method to get inner resource object.
*
* @throws IllegalArgumentException if no resource with provided key is found.
*/
T getResourceOrThrow(int key) {
return getRefcountedResourceOrThrow(key).getResource();
}
/**
* Accessor method to get reference counting wrapper.
*
* @throws IllegalArgumentException if no resource with provided key is found.
*/
RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
RefcountedResource<T> resource = mArray.get(key);
if (resource == null) {
throw new IllegalArgumentException(
String.format("No such %s found for given id: %d", mTypeName, key));
}
return resource;
}
void put(int key, RefcountedResource<T> obj) {
checkNotNull(obj, "Null resources cannot be added"); checkNotNull(obj, "Null resources cannot be added");
mArray.put(key, obj); mArray.put(key, obj);
} }
@@ -548,30 +524,17 @@ public class IpSecService extends IIpSecService.Stub {
} }
} }
private final class TransformRecord extends KernelResource { private final class TransformRecord extends KernelResourceRecord {
private final IpSecConfig mConfig; private final IpSecConfig mConfig;
private final SpiRecord[] mSpis; private final SpiRecord[] mSpis;
private final UdpSocketRecord mSocket; private final EncapSocketRecord mSocket;
TransformRecord( TransformRecord(
int resourceId, int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
IBinder binder, super(resourceId);
IpSecConfig config,
SpiRecord[] spis,
UdpSocketRecord socket) {
super(resourceId, binder);
mConfig = config; mConfig = config;
mSpis = spis; mSpis = spis;
mSocket = socket; mSocket = socket;
for (int direction : DIRECTIONS) {
mSpis[direction].addReference();
mSpis[direction].setOwnedByTransform();
}
if (mSocket != null) {
mSocket.addReference();
}
} }
public IpSecConfig getConfig() { public IpSecConfig getConfig() {
@@ -584,7 +547,7 @@ public class IpSecService extends IIpSecService.Stub {
/** always guarded by IpSecService#this */ /** always guarded by IpSecService#this */
@Override @Override
protected void releaseResources() { public void freeUnderlyingResources() {
for (int direction : DIRECTIONS) { for (int direction : DIRECTIONS) {
int spi = mSpis[direction].getSpi(); int spi = mSpis[direction].getSpi();
try { try {
@@ -603,17 +566,17 @@ public class IpSecService extends IIpSecService.Stub {
} }
} }
for (int direction : DIRECTIONS) { getResourceTracker().give();
mSpis[direction].removeReference();
}
if (mSocket != null) {
mSocket.removeReference();
}
} }
@Override
public void invalidate() throws RemoteException {
getUserRecord().removeTransformRecord(mResourceId);
}
@Override
protected ResourceTracker getResourceTracker() { protected ResourceTracker getResourceTracker() {
return mUserQuotaTracker.getUserRecord(this.uid).transform; return getUserRecord().mTransformQuotaTracker;
} }
@Override @Override
@@ -635,7 +598,7 @@ public class IpSecService extends IIpSecService.Stub {
} }
} }
private final class SpiRecord extends KernelResource { private final class SpiRecord extends KernelResourceRecord {
private final int mDirection; private final int mDirection;
private final String mLocalAddress; private final String mLocalAddress;
private final String mRemoteAddress; private final String mRemoteAddress;
@@ -645,12 +608,11 @@ public class IpSecService extends IIpSecService.Stub {
SpiRecord( SpiRecord(
int resourceId, int resourceId,
IBinder binder,
int direction, int direction,
String localAddress, String localAddress,
String remoteAddress, String remoteAddress,
int spi) { int spi) {
super(resourceId, binder); super(resourceId);
mDirection = direction; mDirection = direction;
mLocalAddress = localAddress; mLocalAddress = localAddress;
mRemoteAddress = remoteAddress; mRemoteAddress = remoteAddress;
@@ -659,7 +621,7 @@ public class IpSecService extends IIpSecService.Stub {
/** always guarded by IpSecService#this */ /** always guarded by IpSecService#this */
@Override @Override
protected void releaseResources() { public void freeUnderlyingResources() {
if (mOwnedByTransform) { if (mOwnedByTransform) {
Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform"); Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
// Because SPIs are "handed off" to transform, objects, they should never be // Because SPIs are "handed off" to transform, objects, they should never be
@@ -682,11 +644,8 @@ public class IpSecService extends IIpSecService.Stub {
} }
mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
}
@Override getResourceTracker().give();
protected ResourceTracker getResourceTracker() {
return mUserQuotaTracker.getUserRecord(this.uid).spi;
} }
public int getSpi() { public int getSpi() {
@@ -702,6 +661,16 @@ public class IpSecService extends IIpSecService.Stub {
mOwnedByTransform = true; mOwnedByTransform = true;
} }
@Override
public void invalidate() throws RemoteException {
getUserRecord().removeSpiRecord(mResourceId);
}
@Override
protected ResourceTracker getResourceTracker() {
return getUserRecord().mSpiQuotaTracker;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder strBuilder = new StringBuilder(); StringBuilder strBuilder = new StringBuilder();
@@ -723,27 +692,24 @@ public class IpSecService extends IIpSecService.Stub {
} }
} }
private final class UdpSocketRecord extends KernelResource { private final class EncapSocketRecord extends KernelResourceRecord {
private FileDescriptor mSocket; private FileDescriptor mSocket;
private final int mPort; private final int mPort;
UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) { EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
super(resourceId, binder); super(resourceId);
mSocket = socket; mSocket = socket;
mPort = port; mPort = port;
} }
/** always guarded by IpSecService#this */ /** always guarded by IpSecService#this */
@Override @Override
protected void releaseResources() { public void freeUnderlyingResources() {
Log.d(TAG, "Closing port " + mPort); Log.d(TAG, "Closing port " + mPort);
IoUtils.closeQuietly(mSocket); IoUtils.closeQuietly(mSocket);
mSocket = null; mSocket = null;
}
@Override getResourceTracker().give();
protected ResourceTracker getResourceTracker() {
return mUserQuotaTracker.getUserRecord(this.uid).socket;
} }
public int getPort() { public int getPort() {
@@ -754,6 +720,16 @@ public class IpSecService extends IIpSecService.Stub {
return mSocket; return mSocket;
} }
@Override
protected ResourceTracker getResourceTracker() {
return getUserRecord().mSocketQuotaTracker;
}
@Override
public void invalidate() {
getUserRecord().removeEncapSocketRecord(mResourceId);
}
@Override @Override
public String toString() { public String toString() {
return new StringBuilder() return new StringBuilder()
@@ -861,13 +837,14 @@ public class IpSecService extends IIpSecService.Stub {
/* requestedSpi can be anything in the int range, so no check is needed. */ /* requestedSpi can be anything in the int range, so no check is needed. */
checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex"); checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
int resourceId = mNextResourceId.getAndIncrement(); int resourceId = mNextResourceId.getAndIncrement();
int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
String localAddress = ""; String localAddress = "";
try { try {
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) { if (!userRecord.mSpiQuotaTracker.isAvailable()) {
return new IpSecSpiResponse( return new IpSecSpiResponse(
IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi); IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
} }
@@ -881,9 +858,11 @@ public class IpSecService extends IIpSecService.Stub {
remoteAddress, remoteAddress,
requestedSpi); requestedSpi);
Log.d(TAG, "Allocated SPI " + spi); Log.d(TAG, "Allocated SPI " + spi);
mSpiRecords.put( userRecord.mSpiRecords.put(
resourceId, resourceId,
new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi)); new RefcountedResource<SpiRecord>(
new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
binder));
} catch (ServiceSpecificException e) { } catch (ServiceSpecificException e) {
// TODO: Add appropriate checks when other ServiceSpecificException types are supported // TODO: Add appropriate checks when other ServiceSpecificException types are supported
return new IpSecSpiResponse( return new IpSecSpiResponse(
@@ -897,26 +876,17 @@ 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 KernelResource> void releaseKernelResource( private void releaseResource(RefcountedResourceArray resArray, int resourceId)
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
// this from the records.
T record = resArray.getAndCheckOwner(resourceId);
if (record == null) { resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
throw new IllegalArgumentException(
typeName + " " + resourceId + " is not available to be deleted");
}
record.release();
resArray.remove(resourceId);
} }
/** 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 synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
releaseKernelResource(mSpiRecords, resourceId, "SecurityParameterIndex"); UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
releaseResource(userRecord.mSpiRecords, resourceId);
} }
/** /**
@@ -969,10 +939,11 @@ public class IpSecService extends IIpSecService.Stub {
} }
checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket"); checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
int resourceId = mNextResourceId.getAndIncrement(); int resourceId = mNextResourceId.getAndIncrement();
FileDescriptor sockFd = null; FileDescriptor sockFd = null;
try { try {
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).socket.isAvailable()) { if (!userRecord.mSocketQuotaTracker.isAvailable()) {
return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
} }
@@ -991,8 +962,10 @@ public class IpSecService extends IIpSecService.Stub {
OsConstants.UDP_ENCAP, OsConstants.UDP_ENCAP,
OsConstants.UDP_ENCAP_ESPINUDP); OsConstants.UDP_ENCAP_ESPINUDP);
mUdpSocketRecords.put( userRecord.mEncapSocketRecords.put(
resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port)); resourceId,
new RefcountedResource<EncapSocketRecord>(
new EncapSocketRecord(resourceId, sockFd, port), binder));
return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd); return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
} catch (IOException | ErrnoException e) { } catch (IOException | ErrnoException e) {
IoUtils.closeQuietly(sockFd); IoUtils.closeQuietly(sockFd);
@@ -1004,9 +977,9 @@ public class IpSecService extends IIpSecService.Stub {
/** close a socket that has been been allocated by and registered with the system server */ /** close a socket that has been been allocated by and registered with the system server */
@Override @Override
public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException { public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
releaseKernelResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket"); releaseResource(userRecord.mEncapSocketRecords, resourceId);
} }
/** /**
@@ -1014,6 +987,8 @@ public class IpSecService extends IIpSecService.Stub {
* IllegalArgumentException if they are not. * IllegalArgumentException if they are not.
*/ */
private void checkIpSecConfig(IpSecConfig config) { private void checkIpSecConfig(IpSecConfig config) {
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
if (config.getLocalAddress() == null) { if (config.getLocalAddress() == null) {
throw new IllegalArgumentException("Invalid null Local InetAddress"); throw new IllegalArgumentException("Invalid null Local InetAddress");
} }
@@ -1042,12 +1017,9 @@ public class IpSecService extends IIpSecService.Stub {
break; break;
case IpSecTransform.ENCAP_ESPINUDP: case IpSecTransform.ENCAP_ESPINUDP:
case IpSecTransform.ENCAP_ESPINUDP_NON_IKE: case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
if (mUdpSocketRecords.getAndCheckOwner( // Retrieve encap socket record; will throw IllegalArgumentException if not found
config.getEncapSocketResourceId()) == null) { userRecord.mEncapSocketRecords.getResourceOrThrow(
throw new IllegalStateException( config.getEncapSocketResourceId());
"No Encapsulation socket for Resource Id: "
+ config.getEncapSocketResourceId());
}
int port = config.getEncapRemotePort(); int port = config.getEncapRemotePort();
if (port <= 0 || port > 0xFFFF) { if (port <= 0 || port > 0xFFFF) {
@@ -1071,9 +1043,8 @@ public class IpSecService extends IIpSecService.Stub {
+ " exclusive with other Authentication or Encryption algorithms"); + " exclusive with other Authentication or Encryption algorithms");
} }
if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) { // Retrieve SPI record; will throw IllegalArgumentException if not found
throw new IllegalStateException("No SPI for specified Resource Id"); userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
}
} }
} }
@@ -1090,16 +1061,27 @@ public class IpSecService extends IIpSecService.Stub {
checkIpSecConfig(c); checkIpSecConfig(c);
checkNotNull(binder, "Null Binder passed to createTransportModeTransform"); checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
int resourceId = mNextResourceId.getAndIncrement(); int resourceId = mNextResourceId.getAndIncrement();
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
// Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
List<RefcountedResource> dependencies = new ArrayList<>(3);
if (!userRecord.mTransformQuotaTracker.isAvailable()) {
return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
} }
SpiRecord[] spis = new SpiRecord[DIRECTIONS.length]; SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
int encapType, encapLocalPort = 0, encapRemotePort = 0; int encapType, encapLocalPort = 0, encapRemotePort = 0;
UdpSocketRecord socketRecord = null; EncapSocketRecord socketRecord = null;
encapType = c.getEncapType(); encapType = c.getEncapType();
if (encapType != IpSecTransform.ENCAP_NONE) { if (encapType != IpSecTransform.ENCAP_NONE) {
socketRecord = mUdpSocketRecords.getAndCheckOwner(c.getEncapSocketResourceId()); RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
c.getEncapSocketResourceId());
dependencies.add(refcountedSocketRecord);
socketRecord = refcountedSocketRecord.getResource();
encapLocalPort = socketRecord.getPort(); encapLocalPort = socketRecord.getPort();
encapRemotePort = c.getEncapRemotePort(); encapRemotePort = c.getEncapRemotePort();
} }
@@ -1109,7 +1091,12 @@ public class IpSecService extends IIpSecService.Stub {
IpSecAlgorithm crypt = c.getEncryption(direction); IpSecAlgorithm crypt = c.getEncryption(direction);
IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction); IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction)); RefcountedResource<SpiRecord> refcountedSpiRecord =
userRecord.mSpiRecords.getRefcountedResourceOrThrow(
c.getSpiResourceId(direction));
dependencies.add(refcountedSpiRecord);
spis[direction] = refcountedSpiRecord.getResource();
int spi = spis[direction].getSpi(); int spi = spis[direction].getSpi();
try { try {
mSrvConfig mSrvConfig
@@ -1140,8 +1127,12 @@ public class IpSecService extends IIpSecService.Stub {
} }
} }
// Both SAs were created successfully, time to construct a record and lock it away // Both SAs were created successfully, time to construct a record and lock it away
mTransformRecords.put( userRecord.mTransformRecords.put(
resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord)); resourceId,
new RefcountedResource<TransformRecord>(
new TransformRecord(resourceId, c, spis, socketRecord),
binder,
dependencies.toArray(new RefcountedResource[dependencies.size()])));
return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId); return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
} }
@@ -1152,8 +1143,9 @@ public class IpSecService extends IIpSecService.Stub {
* other reasons. * other reasons.
*/ */
@Override @Override
public void deleteTransportModeTransform(int resourceId) throws RemoteException { public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
releaseKernelResource(mTransformRecords, resourceId, "IpSecTransform"); UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
releaseResource(userRecord.mTransformRecords, resourceId);
} }
/** /**
@@ -1163,14 +1155,10 @@ 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 KernelResources in this block UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
TransformRecord info;
// FIXME: this code should be factored out into a security check + getter
info = mTransformRecords.getAndCheckOwner(resourceId);
if (info == null) { // Get transform record; if no transform is found, will throw IllegalArgumentException
throw new IllegalArgumentException("Transform " + resourceId + " is not active"); TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
}
// TODO: make this a function. // TODO: make this a function.
if (info.pid != getCallingPid() || info.uid != getCallingUid()) { if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
@@ -1202,7 +1190,7 @@ public class IpSecService extends IIpSecService.Stub {
* used: reserved for future improved input validation. * used: reserved for future improved input validation.
*/ */
@Override @Override
public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
throws RemoteException { throws RemoteException {
try { try {
mSrvConfig mSrvConfig
@@ -1221,13 +1209,7 @@ public class IpSecService extends IIpSecService.Stub {
pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead")); pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
pw.println(); pw.println();
pw.println("mUserQuotaTracker:"); pw.println("mUserResourceTracker:");
pw.println(mUserQuotaTracker); pw.println(mUserResourceTracker);
pw.println("mTransformRecords:");
pw.println(mTransformRecords);
pw.println("mUdpSocketRecords:");
pw.println(mUdpSocketRecords);
pw.println("mSpiRecords:");
pw.println(mSpiRecords);
} }
} }