Skip to content

Commit

Permalink
firewall reject packets: cleanup error cases
Browse files Browse the repository at this point in the history
We found a few types of packets that could cause a panic here, live if
a non-TCP packet has TCP header options set. These change should shore
up any edge cases and ensure we reserve the maximum amount of buffer
space we need to create the reject packet.
  • Loading branch information
wadey committed Aug 21, 2023
1 parent 5a131b2 commit 7921c0f
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 9 deletions.
13 changes: 13 additions & 0 deletions header/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ type m map[string]interface{}
const (
Version uint8 = 1
Len = 16

// The total Nebula packet overhead is 60 bytes:
// - HeaderLen bytes for the Nebula header.
// - 16 bytes for the encryption cipher's AEAD 128-bit tag.
// NOTE: both AESGCM and ChaChaPoly have a 16 byte tag, but if we add other
// ciphers in the future we could calculate this based on the cipher,
// returned by (cipher.AEAD).Overhead().
// - 20 bytes for our IPv4 header.
// (max is 60 bytes, but we don't use IPv4 options)
// TODO: Could routers along the path inject a larger IPv4 header? If so,
// we may need to increase this.
// - 8 bytes for our UDP header.
NebulaOverhead = Len + 16 + 20 + 8
)

type MessageType uint8
Expand Down
22 changes: 18 additions & 4 deletions inside.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,24 @@ func (f *Interface) rejectOutside(packet []byte, ci *ConnectionState, hostinfo *
}

// Use some out buffer space to build the packet before encryption
// Need 40 bytes for the reject packet (20 byte ipv4 header, 20 byte tcp rst packet)
// Leave 100 bytes for the encrypted packet (60 byte Nebula header, 40 byte reject packet)
out = out[:140]
outPacket := iputil.CreateRejectPacket(packet, out[100:])
const maxOutLen = iputil.MaxRejectPacketSize + header.NebulaOverhead
outPacket := iputil.CreateRejectPacket(packet, out[maxOutLen:maxOutLen+iputil.MaxRejectPacketSize])
out = out[:maxOutLen]

if len(outPacket) == 0 {
return
}

if len(outPacket) > iputil.MaxRejectPacketSize {
if f.l.GetLevel() >= logrus.DebugLevel {
f.l.
WithField("packet", packet).
WithField("outPacket", outPacket).
Debug("rejectOutside: packet too big, not sending")
}
return
}

f.sendNoMetrics(header.Message, 0, ci, hostinfo, nil, outPacket, nb, out, q)
}

Expand Down
39 changes: 34 additions & 5 deletions iputil/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ import (
"golang.org/x/net/ipv4"
)

const (
// Need 96 bytes for the largest reject packet:
// - 20 byte ipv4 header
// - 8 byte icmpv4 header
// - 68 byte body (60 byte max orig ipv4 header + 8 byte orig icmpv4 header)
MaxRejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8
)

func CreateRejectPacket(packet []byte, out []byte) []byte {
// TODO ipv4 only, need to fix when inside supports ipv6
if len(packet) < ipv4.HeaderLen || int(packet[0]>>4) != ipv4.Version {
return nil
}

switch packet[9] {
case 6: // tcp
return ipv4CreateRejectTCPPacket(packet, out)
Expand All @@ -19,7 +30,16 @@ func CreateRejectPacket(packet []byte, out []byte) []byte {
func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {
ihl := int(packet[0]&0x0f) << 2

// ICMP reply includes header and first 8 bytes of the packet
if ihl > 60 {
// invalid
return nil
}
if len(packet) < ihl {
// We need at least this many bytes for this to be a valid packet
return nil
}

// ICMP reply includes original header and first 8 bytes of the packet
packetLen := len(packet)
if packetLen > ihl+8 {
packetLen = ihl + 8
Expand All @@ -30,9 +50,9 @@ func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {
out = out[:(outLen)]

ipHdr := out[0:ipv4.HeaderLen]
ipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2) // version, ihl
ipHdr[1] = 0 // DSCP, ECN
binary.BigEndian.PutUint16(ipHdr[2:], uint16(ipv4.HeaderLen+8+packetLen)) // Total Length
ipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2) // version, ihl
ipHdr[1] = 0 // DSCP, ECN
binary.BigEndian.PutUint16(ipHdr[2:], uint16(outLen)) // Total Length

ipHdr[4] = 0 // id
ipHdr[5] = 0 // .
Expand Down Expand Up @@ -76,6 +96,15 @@ func ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte {
ihl := int(packet[0]&0x0f) << 2
outLen := ipv4.HeaderLen + tcpLen

if ihl > 60 {
// invalid
return nil
}
if len(packet) < ihl+tcpLen {
// We need at least this many bytes for this to be a valid packet
return nil
}

out = out[:(outLen)]

ipHdr := out[0:ipv4.HeaderLen]
Expand Down
73 changes: 73 additions & 0 deletions iputil/packet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package iputil

import (
"net"
"testing"

"github.com/stretchr/testify/assert"
"golang.org/x/net/ipv4"
)

func Test_CreateRejectPacket(t *testing.T) {
h := ipv4.Header{
Len: 20,
Src: net.IPv4(10, 0, 0, 1),
Dst: net.IPv4(10, 0, 0, 2),
Protocol: 1, // ICMP
}

b, err := h.Marshal()
if err != nil {
t.Fatalf("h.Marhshal: %v", err)
}
b = append(b, []byte{0, 3, 0, 4}...)

expectedLen := ipv4.HeaderLen + 8 + h.Len + 4
out := make([]byte, expectedLen)
rejectPacket := CreateRejectPacket(b, out)
assert.NotNil(t, rejectPacket)
assert.Len(t, rejectPacket, expectedLen)

// ICMP with max header len
h = ipv4.Header{
Len: 60,
Src: net.IPv4(10, 0, 0, 1),
Dst: net.IPv4(10, 0, 0, 2),
Protocol: 1, // ICMP
Options: make([]byte, 40),
}

b, err = h.Marshal()
if err != nil {
t.Fatalf("h.Marhshal: %v", err)
}
b = append(b, []byte{0, 3, 0, 4, 0, 0, 0, 0}...)

expectedLen = MaxRejectPacketSize
out = make([]byte, MaxRejectPacketSize)
rejectPacket = CreateRejectPacket(b, out)
assert.NotNil(t, rejectPacket)
assert.Len(t, rejectPacket, expectedLen)

// TCP with max header len
h = ipv4.Header{
Len: 60,
Src: net.IPv4(10, 0, 0, 1),
Dst: net.IPv4(10, 0, 0, 2),
Protocol: 6, // TCP
Options: make([]byte, 40),
}

b, err = h.Marshal()
if err != nil {
t.Fatalf("h.Marhshal: %v", err)
}
b = append(b, []byte{0, 3, 0, 4}...)
b = append(b, make([]byte, 16)...)

expectedLen = ipv4.HeaderLen + 20
out = make([]byte, expectedLen)
rejectPacket = CreateRejectPacket(b, out)
assert.NotNil(t, rejectPacket)
assert.Len(t, rejectPacket, expectedLen)
}

0 comments on commit 7921c0f

Please sign in to comment.