Input Validation for IpSecService

All of the input to IpSecService over the Binder
interface needs to be validated both for sanity
and for safety.

-Sanity check all the parameters coming from binder.
-Added setters for IpSecConfig to decouple the test
 from the IpSecManager. This was needed because the
 input validation caused the tests to fail due to a
 null parameter that was previously un-tested.
-Added the mode flag to the IpSecConfig bundle this
 oversight was found during testing.
-Expose the getResourceId() methods for testing in
 UdpEncapsulationSocket, SecurityParameterIndex, and
 IpSecTransform classes.
-Remove the unneeded getIpSecConfig() from
 IpSecTransform: unneeded now that we can synthesize
 configs.

Bug: 38397094
Test: runtest frameworks-net
Change-Id: I5241fc7fbfa9816d54219acd8d81a9f7eef10dd4
This commit is contained in:
Nathan Harold
2017-08-23 13:46:33 -07:00
parent 61cf7b284e
commit 19b99d998a
4 changed files with 299 additions and 207 deletions

View File

@@ -33,6 +33,7 @@ import android.net.IpSecSpiResponse;
import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
import android.net.IpSecUdpEncapResponse;
import android.net.NetworkUtils;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.IBinder;
@@ -42,11 +43,14 @@ import android.os.ServiceSpecificException;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -54,6 +58,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicInteger;
import libcore.io.IoUtils;
/** @hide */
@@ -252,7 +257,11 @@ public class IpSecService extends IIpSecService.Stub {
return (mReferenceCount.get() > 0);
}
public void checkOwnerOrSystemAndThrow() {
/**
* 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!");
@@ -340,7 +349,7 @@ public class IpSecService extends IIpSecService.Stub {
// 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.checkOwnerOrSystemAndThrow();
val.checkOwnerOrSystem();
}
return val;
}
@@ -405,12 +414,8 @@ public class IpSecService extends IIpSecService.Stub {
.ipSecDeleteSecurityAssociation(
mResourceId,
direction,
(mConfig.getLocalAddress() != null)
? mConfig.getLocalAddress().getHostAddress()
: "",
(mConfig.getRemoteAddress() != null)
? mConfig.getRemoteAddress().getHostAddress()
: "",
mConfig.getLocalAddress(),
mConfig.getRemoteAddress(),
spi);
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
@@ -638,11 +643,45 @@ public class IpSecService extends IIpSecService.Stub {
}
}
/**
* Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
* a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
*/
private static void checkInetAddress(String inetAddress) {
if (TextUtils.isEmpty(inetAddress)) {
throw new IllegalArgumentException("Unspecified address");
}
InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
if (checkAddr.isAnyLocalAddress()) {
throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
}
}
/**
* 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) {
switch (direction) {
case IpSecTransform.DIRECTION_OUT:
case IpSecTransform.DIRECTION_IN:
return;
}
throw new IllegalArgumentException("Invalid Direction: " + direction);
}
@Override
/** Get a new SPI and maintain the reservation in the system server */
public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
int direction, String remoteAddress, int requestedSpi, IBinder binder)
throws RemoteException {
checkDirection(direction);
checkInetAddress(remoteAddress);
/* requestedSpi can be anything in the int range, so no check is needed. */
checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
int resourceId = mNextResourceId.getAndIncrement();
int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
@@ -651,9 +690,7 @@ public class IpSecService extends IIpSecService.Stub {
try {
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) {
return new IpSecSpiResponse(
IpSecManager.Status.RESOURCE_UNAVAILABLE,
INVALID_RESOURCE_ID,
spi);
IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
}
spi =
mSrvConfig
@@ -751,6 +788,8 @@ public class IpSecService extends IIpSecService.Stub {
throw new IllegalArgumentException(
"Specified port number must be a valid non-reserved UDP port");
}
checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
int resourceId = mNextResourceId.getAndIncrement();
FileDescriptor sockFd = null;
try {
@@ -791,6 +830,67 @@ public class IpSecService extends IIpSecService.Stub {
releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
}
/**
* Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
* IllegalArgumentException if they are not.
*/
private void checkIpSecConfig(IpSecConfig config) {
if (config.getLocalAddress() == null) {
throw new IllegalArgumentException("Invalid null Local InetAddress");
}
if (config.getRemoteAddress() == null) {
throw new IllegalArgumentException("Invalid null Remote InetAddress");
}
switch (config.getMode()) {
case IpSecTransform.MODE_TRANSPORT:
if (!config.getLocalAddress().isEmpty()) {
throw new IllegalArgumentException("Non-empty Local Address");
}
// Must be valid, and not a wildcard
checkInetAddress(config.getRemoteAddress());
break;
case IpSecTransform.MODE_TUNNEL:
break;
default:
throw new IllegalArgumentException(
"Invalid IpSecTransform.mode: " + config.getMode());
}
switch (config.getEncapType()) {
case IpSecTransform.ENCAP_NONE:
break;
case IpSecTransform.ENCAP_ESPINUDP:
case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
if (mUdpSocketRecords.get(config.getEncapSocketResourceId()) == null) {
throw new IllegalStateException(
"No Encapsulation socket for Resource Id: "
+ config.getEncapSocketResourceId());
}
int port = config.getEncapRemotePort();
if (port <= 0 || port > 0xFFFF) {
throw new IllegalArgumentException("Invalid remote UDP port: " + port);
}
break;
default:
throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
}
for (int direction : DIRECTIONS) {
IpSecAlgorithm crypt = config.getEncryption(direction);
IpSecAlgorithm auth = config.getAuthentication(direction);
if (crypt == null && auth == null) {
throw new IllegalArgumentException("Encryption and Authentication are both null");
}
if (mSpiRecords.get(config.getSpiResourceId(direction)) == null) {
throw new IllegalStateException("No SPI for specified Resource Id");
}
}
}
/**
* Create a transport mode transform, which represent two security associations (one in each
* direction) in the kernel. The transform will be cached by the system server and must be freed
@@ -801,17 +901,19 @@ public class IpSecService extends IIpSecService.Stub {
@Override
public synchronized IpSecTransformResponse createTransportModeTransform(
IpSecConfig c, IBinder binder) throws RemoteException {
checkIpSecConfig(c);
checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
int resourceId = mNextResourceId.getAndIncrement();
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
}
SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
// TODO: Basic input validation here since it's coming over the Binder
int encapType, encapLocalPort = 0, encapRemotePort = 0;
UdpSocketRecord socketRecord = null;
encapType = c.getEncapType();
if (encapType != IpSecTransform.ENCAP_NONE) {
socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
socketRecord = mUdpSocketRecords.get(c.getEncapSocketResourceId());
encapLocalPort = socketRecord.getPort();
encapRemotePort = c.getEncapRemotePort();
}
@@ -823,20 +925,15 @@ public class IpSecService extends IIpSecService.Stub {
spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
int spi = spis[direction].getSpi();
try {
mSrvConfig.getNetdInstance()
mSrvConfig
.getNetdInstance()
.ipSecAddSecurityAssociation(
resourceId,
c.getMode(),
direction,
(c.getLocalAddress() != null)
? c.getLocalAddress().getHostAddress()
: "",
(c.getRemoteAddress() != null)
? c.getRemoteAddress().getHostAddress()
: "",
(c.getNetwork() != null)
? c.getNetwork().getNetworkHandle()
: 0,
c.getLocalAddress(),
c.getRemoteAddress(),
(c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
spi,
(auth != null) ? auth.getName() : "",
(auth != null) ? auth.getKey() : null,
@@ -899,12 +996,8 @@ public class IpSecService extends IIpSecService.Stub {
socket.getFileDescriptor(),
resourceId,
direction,
(c.getLocalAddress() != null)
? c.getLocalAddress().getHostAddress()
: "",
(c.getRemoteAddress() != null)
? c.getRemoteAddress().getHostAddress()
: "",
c.getLocalAddress(),
c.getRemoteAddress(),
info.getSpiRecord(direction).getSpi());
}
} catch (ServiceSpecificException e) {