Skip to content

udp: release the cloned PacketBuffer in Forwarder.HandlePacket#13242

Open
ibondarenko1 wants to merge 1 commit into
google:masterfrom
ibondarenko1:hardening/udp-forwarder-packetbuffer-leak
Open

udp: release the cloned PacketBuffer in Forwarder.HandlePacket#13242
ibondarenko1 wants to merge 1 commit into
google:masterfrom
ibondarenko1:hardening/udp-forwarder-packetbuffer-leak

Conversation

@ibondarenko1
Copy link
Copy Markdown

Problem

udp.Forwarder.HandlePacket clones the incoming PacketBuffer into a ForwarderRequest:

func (f *Forwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
	return f.handler(&ForwarderRequest{
		stack: f.stack,
		id:    id,
		pkt:   pkt.Clone(),
	})
}

pkt.Clone() returns a new refcounted PacketBuffer. Nothing releases it:

  • If the handler returns false (unhandled), the ForwarderRequest is discarded with no release.
  • If the handler calls CreateEndpoint, that passes r.pkt to endpoint.HandlePacket, which reads it and copies the payload into a udpPacket without taking ownership; CreateEndpoint then returns without releasing r.pkt.

So every packet entering the UDP forwarder leaks one PacketBuffer and its buffer chunks. ForwarderRequest exposes no release method.

TCP precedent

The TCP forwarder holds its segment with an explicit IncRef and releases it in ForwarderRequest.Complete() (pkg/tcpip/transport/tcp/forwarder.go):

func (r *ForwarderRequest) Complete(sendReset bool) {
	...
	r.segment.DecRef()
	r.segment = nil
}

TCP needs a client-called Complete() because the TCP handshake is asynchronous. The UDP forwarder handler is synchronous: ForwarderHandler returns handled bool inline, so the request never outlives the handler call. UDP therefore needs no Complete(); HandlePacket can release the clone itself once the handler returns.

Change

Hold the ForwarderRequest in a local and defer r.pkt.DecRef() before invoking the handler, so the cloned PacketBuffer is released on every return path. NewForwarderRequest callers, which supply their own pkt, are unaffected.

Notes

Same "the handler consumes the request" contract cleanup as #13189 for ICMP echo handlers. Honest scope: this is a netstack PacketBuffer resource leak; the practical effect depends on a deployment that wires a UDP forwarder. A refs-leak-checker build flags the leak on any UDP forwarder test.

Forwarder.HandlePacket clones the incoming PacketBuffer into a
ForwarderRequest but never releases the clone. Neither the unhandled
path nor CreateEndpoint takes ownership of it, so every packet entering
the UDP forwarder leaks one PacketBuffer.

The TCP forwarder holds its segment with an explicit IncRef and releases
it in ForwarderRequest.Complete(), which a client must call because the
TCP handshake is asynchronous. The UDP forwarder handler is synchronous
(it returns whether the request was handled), so the request never
outlives the handler call. Release the clone in HandlePacket once the
handler returns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants