Skip to content

Commit

Permalink
Merge pull request #122 from compscidr/jason/limit-options-data
Browse files Browse the repository at this point in the history
Limit stream parsing so we don't overshoot the packet
  • Loading branch information
compscidr authored Aug 22, 2024
2 parents a036d7d + a06c959 commit a22693d
Show file tree
Hide file tree
Showing 15 changed files with 66 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ abstract class ICMP {
logger.debug("bytesRecieved: $bytesRecieved\n${stringDumper.dumpBufferToString(recvBuffer, 0, bytesRecieved)}")

recvBuffer.position(0) // bug in the string dumping code
val response = ICMPHeader.fromStream(recvBuffer, inetAddress is Inet4Address)
val response = ICMPHeader.fromStream(recvBuffer, isIcmpV4 = inetAddress is Inet4Address)

// todo: verify checksum. The Inetaddress we have is the destination address, but I'm
// not entirely sure what the source address will be. Probably need to use the fd to
Expand Down
25 changes: 16 additions & 9 deletions icmp-common/src/main/java/com/jasonernst/icmp_common/ICMPHeader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,27 @@ abstract class ICMPHeader(val type: ICMPType, val code: UByte, val checksum: USh
const val ICMP_HEADER_MIN_LENGTH: UShort = 4u
const val ICMP_CHECKSUM_OFFSET: UShort = 2u

fun fromStream(byteBuffer: ByteBuffer, isIcmpV4: Boolean = true, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPHeader {
byteBuffer.order(order)
if (byteBuffer.remaining() < ICMP_HEADER_MIN_LENGTH.toInt()) {
throw PacketHeaderException("Buffer too small, expected at least ${ICMP_HEADER_MIN_LENGTH.toInt()} bytes, got ${byteBuffer.remaining()}")
/**
* The limit is used to prevent reading past the end of the buffer. This is useful for ICMP
* packets with payloads. If you are parsing a stream that contains multiple IP packets with
* ICMP in them, you might find that if you read to the end of the buffer, you will
* interpret the next IP header as part of the ICMP packet. The limit lets us control this
* and can be set based on the IP header's payload length.
*/
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), isIcmpV4: Boolean = true, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPHeader {
buffer.order(order)
if (buffer.remaining() < ICMP_HEADER_MIN_LENGTH.toInt()) {
throw PacketHeaderException("Buffer too small, expected at least ${ICMP_HEADER_MIN_LENGTH.toInt()} bytes, got ${buffer.remaining()}")
}
val newType = if (isIcmpV4) ICMPv4Type.fromValue(byteBuffer.get().toUByte()) else ICMPv6Type.fromValue(byteBuffer.get().toUByte())
val newCode = byteBuffer.get().toUByte()
val newChecksum = byteBuffer.short.toUShort()
val newType = if (isIcmpV4) ICMPv4Type.fromValue(buffer.get().toUByte()) else ICMPv6Type.fromValue(buffer.get().toUByte())
val newCode = buffer.get().toUByte()
val newChecksum = buffer.short.toUShort()
return when (newType) {
is ICMPv4Type -> {
ICMPv4Header.fromStream(byteBuffer, newType, newCode, newChecksum, order)
ICMPv4Header.fromStream(buffer, limit, newType, newCode, newChecksum, order)
}
is ICMPv6Type -> {
ICMPv6Header.fromStream(byteBuffer, newType, newCode, newChecksum, order)
ICMPv6Header.fromStream(buffer, limit, newType, newCode, newChecksum, order)
}
else -> {
throw PacketHeaderException("Unsupported ICMP type")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jasonernst.icmp_common.v4

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* https://www.rfc-editor.org/rfc/rfc792.html pg 4
Expand All @@ -14,9 +15,9 @@ class ICMPv4DestinationUnreachablePacket(code: ICMPv4DestinationUnreachableCodes
checksum: UShort = 0u,
val data: ByteArray = ByteArray(0)): ICMPv4Header(ICMPv4Type.DESTINATION_UNREACHABLE, code.value, checksum) {
companion object {
fun fromStream(buffer: ByteBuffer, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv4DestinationUnreachablePacket {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv4DestinationUnreachablePacket {
buffer.order(order)
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt()))
buffer.get(remainingBuffer)
return ICMPv4DestinationUnreachablePacket(ICMPv4DestinationUnreachableCodes.fromValue(code), checksum, data = remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.jasonernst.icmp_common.v4
import com.jasonernst.icmp_common.PacketHeaderException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* Minimal implementation of an ICMPv4 Echo Request/Reply packet.
Expand All @@ -22,14 +23,14 @@ class ICMPv4EchoPacket(
private const val ICMP_ECHO_MIN_LENGTH = 4 // 2 bytes for sequence, 2 bytes for id

fun fromStream(
buffer: ByteBuffer, icmpV4Type: ICMPv4Type, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN
buffer: ByteBuffer, limit: Int = buffer.remaining(), icmpV4Type: ICMPv4Type, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN
): ICMPv4EchoPacket {
buffer.order(order)
val isReply: Boolean = icmpV4Type == ICMPv4Type.ECHO_REPLY
if (buffer.remaining() < ICMP_ECHO_MIN_LENGTH) throw PacketHeaderException("Buffer too small")
val id = buffer.short.toUShort()
val sequence = buffer.short.toUShort()
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_ECHO_MIN_LENGTH))
buffer.get(remainingBuffer)
return ICMPv4EchoPacket(checksum, id, sequence, isReply, remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ abstract class ICMPv4Header(
checksum: UShort,
) : ICMPHeader(type = icmpV4Type, code = code, checksum = checksum) {
companion object {
fun fromStream(buffer: ByteBuffer, icmpV4Type: ICMPv4Type, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv4Header {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), icmpV4Type: ICMPv4Type, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv4Header {
return when (icmpV4Type) {
ICMPv4Type.ECHO_REPLY, ICMPv4Type.ECHO_REQUEST -> {
ICMPv4EchoPacket.fromStream(buffer, icmpV4Type, checksum, order)
ICMPv4EchoPacket.fromStream(buffer, limit, icmpV4Type, checksum, order)
}
ICMPv4Type.DESTINATION_UNREACHABLE -> {
ICMPv4DestinationUnreachablePacket.fromStream(buffer, code, checksum, order)
ICMPv4DestinationUnreachablePacket.fromStream(buffer, limit, code, checksum, order)
}
ICMPv4Type.TIME_EXCEEDED -> {
ICMPv4TimeExceededPacket.fromStream(buffer, code, checksum, order)
ICMPv4TimeExceededPacket.fromStream(buffer, limit, code, checksum, order)
}
else -> {
throw PacketHeaderException("Unsupported ICMPv4 type")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package com.jasonernst.icmp_common.v4

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

class ICMPv4TimeExceededPacket(code: ICMPv4DestinationUnreachableCodes, checksum: UShort = 0u, val data: ByteArray = ByteArray(0)): ICMPv4Header(ICMPv4Type.TIME_EXCEEDED, code.value, checksum) {
companion object {
fun fromStream(buffer: ByteBuffer, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv4TimeExceededPacket {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv4TimeExceededPacket {
buffer.order(order)
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt()))
buffer.get(remainingBuffer)
return ICMPv4TimeExceededPacket(ICMPv4TimeExceededCodes.fromValue(code), checksum, data = remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jasonernst.icmp_common.v6

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* https://www.rfc-editor.org/rfc/rfc792.html pg 4
Expand All @@ -14,9 +15,9 @@ class ICMPv6DestinationUnreachablePacket(code: ICMPv6DestinationUnreachableCodes
checksum: UShort = 0u,
val data: ByteArray = ByteArray(0)): ICMPv6Header(ICMPv6Type.DESTINATION_UNREACHABLE, code.value, checksum) {
companion object {
fun fromStream(buffer: ByteBuffer, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6DestinationUnreachablePacket {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6DestinationUnreachablePacket {
buffer.order(order)
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt()))
buffer.get(remainingBuffer)
return ICMPv6DestinationUnreachablePacket(ICMPv6DestinationUnreachableCodes.fromValue(code), checksum, data = remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.jasonernst.icmp_common.v6
import com.jasonernst.icmp_common.PacketHeaderException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* https://datatracker.ietf.org/doc/html/rfc4443
Expand All @@ -19,6 +20,7 @@ class ICMPv6EchoPacket(

fun fromStream(
buffer: ByteBuffer,
limit: Int = buffer.remaining(),
icmpV6Type: ICMPv6Type,
checksum: UShort,
order: ByteOrder = ByteOrder.BIG_ENDIAN
Expand All @@ -28,7 +30,7 @@ class ICMPv6EchoPacket(
if (buffer.remaining() < ICMP_ECHO_LENGTH) throw PacketHeaderException("Buffer too small")
val id = buffer.short.toUShort()
val sequence = buffer.short.toUShort()
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt() + ICMP_ECHO_LENGTH))
buffer.get(remainingBuffer)
return ICMPv6EchoPacket(checksum, id, sequence, isReply, remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ abstract class ICMPv6Header(
checksum: UShort
) : ICMPHeader(type = icmPv6Type, code = code, checksum = checksum) {
companion object {
fun fromStream(buffer: ByteBuffer, icmpV6Type: ICMPv6Type, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6Header {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), icmpV6Type: ICMPv6Type, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6Header {
return when (icmpV6Type) {
ICMPv6Type.ECHO_REPLY_V6, ICMPv6Type.ECHO_REQUEST_V6 -> {
ICMPv6EchoPacket.fromStream(buffer, icmpV6Type, checksum, order)
ICMPv6EchoPacket.fromStream(buffer, limit, icmpV6Type, checksum, order)
}
ICMPv6Type.DESTINATION_UNREACHABLE -> {
ICMPv6DestinationUnreachablePacket.fromStream(buffer, code, checksum, order)
ICMPv6DestinationUnreachablePacket.fromStream(buffer, limit, code, checksum, order)
}
ICMPv6Type.TIME_EXCEEDED -> {
ICMPv6TimeExceededPacket.fromStream(buffer, code, checksum, order)
ICMPv6TimeExceededPacket.fromStream(buffer, limit, code, checksum, order)
}
ICMPv6Type.MULTICAST_LISTENER_DISCOVERY_V2 -> {
ICMPv6MulticastListenerDiscoveryV2.fromStream(buffer, checksum, order)
ICMPv6MulticastListenerDiscoveryV2.fromStream(buffer, limit, checksum, order)
}
ICMPv6Type.ROUTER_SOLICITATION_V6 -> {
ICMPv6RouterSolicitationPacket.fromStream(buffer, order)
ICMPv6RouterSolicitationPacket.fromStream(buffer, limit, checksum, order)
}
ICMPv6Type.ROUTER_ADVERTISEMENT_V6 -> {
ICMPv6RouterAdvertisementPacket.fromStream(buffer, checksum, order)
ICMPv6RouterAdvertisementPacket.fromStream(buffer, limit, checksum, order)
}
else -> {
throw PacketHeaderException("Unsupported ICMPv6 type")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jasonernst.icmp_common.v6

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* https://datatracker.ietf.org/doc/html/rfc3810
Expand Down Expand Up @@ -43,9 +44,9 @@ import java.nio.ByteOrder
class ICMPv6MulticastListenerDiscoveryV2(val data: ByteArray = ByteArray(0)): ICMPv6Header(icmPv6Type = ICMPv6Type.MULTICAST_LISTENER_DISCOVERY_V2, code = 0u, checksum = 0u) {

companion object {
fun fromStream(buffer: ByteBuffer, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6MulticastListenerDiscoveryV2 {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6MulticastListenerDiscoveryV2 {
buffer.order(order)
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt()))
buffer.get(remainingBuffer)
return ICMPv6MulticastListenerDiscoveryV2(data = remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jasonernst.icmp_common.v6

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* https://www.rfc-editor.org/rfc/rfc4861.html#section-4.2 page 19
Expand Down Expand Up @@ -30,7 +31,7 @@ class ICMPv6RouterAdvertisementPacket(val curHopLimit: UByte,
// cur hop limit (1 byte) + flags (1 byte) + router lifetime (2 bytes) + reachable time (4 bytes) + retrans timer (4 bytes)
const val ICMP_ADVERTISEMENT_PACKET_MIN_LENGTH: UShort = 12u

fun fromStream(buffer: ByteBuffer, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6RouterAdvertisementPacket {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6RouterAdvertisementPacket {
buffer.order(order)
val curHopLimit = buffer.get().toUByte()
val flags = buffer.get().toUByte()
Expand All @@ -39,7 +40,7 @@ class ICMPv6RouterAdvertisementPacket(val curHopLimit: UByte,
val routerLifetime = buffer.short.toUShort()
val reachableTime = buffer.int.toUInt()
val retransTimer = buffer.int.toUInt()
val options = ByteArray(buffer.remaining())
val options = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt() - ICMP_ADVERTISEMENT_PACKET_MIN_LENGTH.toInt()))
buffer.get(options)
return ICMPv6RouterAdvertisementPacket(curHopLimit, m, o, routerLifetime, reachableTime, retransTimer, options)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jasonernst.icmp_common.v6

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

/**
* https://www.rfc-editor.org/rfc/rfc4861.html#section-4.1 page 18
Expand Down Expand Up @@ -40,9 +41,9 @@ import java.nio.ByteOrder
class ICMPv6RouterSolicitationPacket(val data: ByteArray = ByteArray(0)): ICMPv6Header(icmPv6Type = ICMPv6Type.ROUTER_SOLICITATION_V6, code = 0u, checksum = 0u) {

companion object {
fun fromStream(buffer: ByteBuffer, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6RouterSolicitationPacket {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6RouterSolicitationPacket {
buffer.order(order)
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit - ICMP_HEADER_MIN_LENGTH.toInt()))
buffer.get(remainingBuffer)
return ICMPv6RouterSolicitationPacket(data = remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package com.jasonernst.icmp_common.v6

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.min

class ICMPv6TimeExceededPacket(code: ICMPv6TimeExceededCodes, checksum: UShort = 0u, val data: ByteArray = ByteArray(0)): ICMPv6Header(
ICMPv6Type.TIME_EXCEEDED, code.value, checksum) {
companion object {
fun fromStream(buffer: ByteBuffer, code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6TimeExceededPacket {
fun fromStream(buffer: ByteBuffer, limit: Int = buffer.remaining(), code: UByte, checksum: UShort, order: ByteOrder = ByteOrder.BIG_ENDIAN): ICMPv6TimeExceededPacket {
buffer.order(order)
val remainingBuffer = ByteArray(buffer.remaining())
val remainingBuffer = ByteArray(min(buffer.remaining(), limit- ICMP_HEADER_MIN_LENGTH.toInt()))
buffer.get(remainingBuffer)
return ICMPv6TimeExceededPacket(ICMPv6TimeExceededCodes.fromValue(code), checksum, data = remainingBuffer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ICMPv4Test {
val buffer = ByteBuffer.wrap(icmPv4EchoPacket.toByteArray())
val stringDumper = StringPacketDumper(logger)
stringDumper.dumpBuffer(buffer, 0, buffer.limit())
val parsedPacket = ICMPHeader.fromStream(buffer, true)
val parsedPacket = ICMPHeader.fromStream(buffer, isIcmpV4 = true)
assertEquals(icmPv4EchoPacket, parsedPacket)
}

Expand All @@ -39,7 +39,7 @@ class ICMPv4Test {
)
val buffer = ByteBuffer.wrap(icmPv4EchoPacket.toByteArray())
stringDumper.dumpBuffer(buffer, 0, buffer.limit())
val parsedPacket = ICMPHeader.fromStream(buffer, true)
val parsedPacket = ICMPHeader.fromStream(buffer, isIcmpV4 = true)
assertEquals(icmPv4EchoPacket, parsedPacket)
}

Expand All @@ -52,7 +52,7 @@ class ICMPv4Test {
)
val buffer = ByteBuffer.wrap(icmpV4DestinationUnreachablePacket.toByteArray())
stringDumper.dumpBuffer(buffer, 0, buffer.limit())
val parsedPacket = ICMPHeader.fromStream(buffer, true)
val parsedPacket = ICMPHeader.fromStream(buffer, isIcmpV4 = true)
assertEquals(icmpV4DestinationUnreachablePacket, parsedPacket)
}

Expand All @@ -65,7 +65,7 @@ class ICMPv4Test {
)
val buffer = ByteBuffer.wrap(icmPv4TimeExceededPacket.toByteArray())
stringDumper.dumpBuffer(buffer, 0, buffer.limit())
val parsedPacket = ICMPHeader.fromStream(buffer, true)
val parsedPacket = ICMPHeader.fromStream(buffer, isIcmpV4 = true)
assertEquals(icmPv4TimeExceededPacket, parsedPacket)
}

Expand Down
Loading

0 comments on commit a22693d

Please sign in to comment.