Yet another variation of the ``be careful with that packet'' theme is making sure that you never go past the end of the packet when parsing it. This problem typically occurs when extracting a string, for instance, and not making sure that the string does not extend past the end of the packet received from the client.
Consider a protocol that transmits strings encoded as an integer word containing the length, followed by the string data itself. Assume the attacker sends a packet of just a dozen bytes containing a string. The length word claims that the string is 8000 characters long - but there's no data following it! If the application is careless, it will use the length value without further checking, trying to extract the next 8000 bytes from the packet. The effects of this misguided attempt usually range from crashing the network application to exposing random application memory to the attacker.
However, this sort of bug can have much worse consequences. Here's a very drastic one that was present in the Sun RPC library for quite a while. RPC supports an authentication mechanism colloquially referred to as secure RPC, which is based on a public key mechanism and was intended to provide strong NFS authentication for those that needed it. Today, it's usually referred to as Diffie-Hellman or DH RPC authentication.
The RPC reference implementation released by Sun in the mid-eighties had several flaws. One of them was that it didn't properly defend against what's called a replay attack: An attacker watching your secure RPC requests go by grabs one such request, extracts the authentication information and resends it with a fake protocol request. The server, due to some faulty check, does not recognize that it has already seen these credentials before, accepts them, and executes the fake request with your privilege.
In itself, this flaw is bad enough; the second problem however made this bug exploitable by anybody as long he was able to connect to the RPC service. It eliminated the requirement to be able to watch the network traffic, which radically increases the severity of this bug. Both problems seem to be fixed in the current versions of Solaris, by the way.
To understand the nature of this bug, one has to know that RPC credentials are encoded as a type field, followed by a length field, followed by the number of bytes indicated by the length field.7.8 This opaque chunk of bytes is called the credential, and is interpreted by the authentication function for the authentication flavor indicated by the type field.
When an RPC server receives a request using Diffie Hellman authentication, it extracts the authentication type, copies the opaque credentials to a buffer, and invokes the Diffie Hellman specific authentication function. The problem with this code was that it didn't check that the client actually included the correct amount of data in the credentials. In particular, it would accept requests that indicated DH authentication in the type field, but came with 0 credential bytes.
What's so bad about this? Well, the RPC server would ``extract'' zero bytes, i.e. the credentials buffer would remain unchanged from the previous call. So when dispatching to the DH verifier function, it would find the credentials from the previous call, and because it doesn't reject replayed credentials, accept them as valid! This means an attacker could execute RPC requests as whoever happened to call the RPC service most recently.
FIXME: Need figure here
The proper defense against this sort of bug is to always make sure that if you extract some data item from the packet, you never go past the end of the packet. One fairly practical approach is to have a pointer variable that points to the end of the packet, and whenever you extract a chunk of bytes from the packet, make sure that the end of this chunk is less or equal to your end-of-packet pointer. For instance, the following function will extract a variable length string from the packet, and advance the packet pointer:
char *
get_string(caddr_t *pktp, caddr_t end)
{
caddr_t data = *pktp;
u_int32_t count;
char *s;
/* extract string length */
if (data + 4 > end)
return NULL;
memcpy(&count, data, 4);
count = ntohl(count);
data += 4;
/* extract character array */
if (data + count > end)
return NULL;
s = (char *) malloc(count+1);
memcpy(s, data, count);
s[count] = '\0';
data += count;
*pktp = data;
return s;
}
XXX: Should refer to some commonly available, good buffer extraction library here. If there was one! Maybe convince Chris to package his vsftpd lib separately :-)