Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions fw/face/ndnlp-link-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ func MakeNDNLPLinkServiceOptions() NDNLPLinkServiceOptions {
}
}

// maxReassemblyFragments bounds the fragment count of a single reassembled
// packet. A valid L3 packet is at most MaxNDNPacketSize bytes and every
// fragment carries at least one byte, so a larger count can never complete.
const maxReassemblyFragments = defn.MaxNDNPacketSize

// NDNLPLinkService is a link service implementing the NDNLPv2 link protocol
type NDNLPLinkService struct {
linkServiceBase
Expand Down Expand Up @@ -317,6 +322,12 @@ func (l *NDNLPLinkService) handleIncomingFrame(frame []byte) {
if v, ok := LP.FragCount.Get(); ok {
fragCount = v
}
if fragCount == 0 || fragCount > maxReassemblyFragments ||
fragIndex >= fragCount || fragIndex > LP.Sequence.Unwrap() {
core.Log.Warn(l, "Received frame with invalid fragmentation fields - DROP",
"index", fragIndex, "count", fragCount, "sequence", LP.Sequence.Unwrap())
return
}
baseSequence := LP.Sequence.Unwrap() - fragIndex

core.Log.Trace(l, "Received fragment", "index", fragIndex, "count", fragCount, "base", baseSequence)
Expand Down Expand Up @@ -374,6 +385,13 @@ func (l *NDNLPLinkService) reassemble(
fragIndex uint64,
fragCount uint64,
) enc.Wire {
// Validate fragmentation fields before allocating a reassembly buffer
if fragCount == 0 || fragCount > maxReassemblyFragments || fragIndex >= fragCount {
core.Log.Warn(l, "Invalid fragmentation fields - DROP",
"index", fragIndex, "count", fragCount, "base", baseSequence)
return nil
}

var buffer enc.Wire = nil
var bufIndex int = 0

Expand Down
67 changes: 67 additions & 0 deletions fw/face/ndnlp_link_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package face

import (
"testing"

"github.com/named-data/ndnd/fw/defn"
enc "github.com/named-data/ndnd/std/encoding"
"github.com/named-data/ndnd/std/types/optional"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given your bound is 8800, there needs to be a test case with 8800 fragments.

// A single fragment with an attacker-controlled FragCount must not cause
// an unbounded reassembly buffer allocation.
func TestReassembleFragCountBound(t *testing.T) {
l := &NDNLPLinkService{}
frame := &defn.FwLpPacket{Fragment: enc.Wire{[]byte{0x01}}}

// 1<<22 fragment slots would allocate a ~96MB slice for one tiny fragment
frag := l.reassemble(frame, 1, 0, 1<<22)
assert.Nil(t, frag)
for i := range l.reassemblyBuffers {
assert.Nilf(t, l.reassemblyBuffers[i].buffer,
"buffer %d allocated %d fragment slots for an oversized FragCount",
i, len(l.reassemblyBuffers[i].buffer))
}
}

// A frame with FragIndex greater than its Sequence must be dropped before
// baseSequence is computed (uint64 underflow) and no state may be allocated.
func TestReassemblyFragIndexExceedsSequence(t *testing.T) {
l := &NDNLPLinkService{options: MakeNDNLPLinkServiceOptions()}
lp := &defn.FwLpPacket{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a valid LpPacket that could be accepted and assembled.
The other fragment would have:

  • Sequence number: 0xFFFFFFFFFFFFFFFF
  • FragIndex: 0x00
  • FragCoint: 0x02

Fragment: enc.Wire{[]byte{0x01}},
Sequence: optional.Some(uint64(0)),
FragIndex: optional.Some(uint64(1)),
FragCount: optional.Some(uint64(2)),
}
pkt := defn.FwPacket{LpPacket: lp}
frameWire := pkt.Encode()
require.NotNil(t, frameWire)

l.handleIncomingFrame(frameWire.Join())
for i := range l.reassemblyBuffers {
assert.Nilf(t, l.reassemblyBuffers[i].buffer,
"buffer %d allocated (sequence %d) for an invalid FragIndex/Sequence pair",
i, l.reassemblyBuffers[i].sequence)
}
}

// A legitimate fragmented packet must still reassemble: fragments arrive in
// order, the completed wire is returned, and the buffer is freed.
func TestReassembleValidSequence(t *testing.T) {
l := &NDNLPLinkService{}
f0 := &defn.FwLpPacket{Fragment: enc.Wire{[]byte{0x01}}}
f1 := &defn.FwLpPacket{Fragment: enc.Wire{[]byte{0x02}}}

assert.Nil(t, l.reassemble(f0, 100, 0, 2)) // incomplete
full := l.reassemble(f1, 100, 1, 2)
require.NotNil(t, full)
assert.Equal(t, enc.Wire{[]byte{0x01}, []byte{0x02}}, full)

// buffer freed after completion
for i := range l.reassemblyBuffers {
assert.Nil(t, l.reassemblyBuffers[i].buffer)
}
}
Loading