Skip to content

Commit cbdb2c6

Browse files
zelinggvisor-bot
authored andcommitted
Randomize TCP source port selection
Drop PickEphemeralPortStable which uses a hash-based algorithm from RFC 6056, instead just use PickEphemeralPort that uses a randomized algorithm from RFC 6056 to avoid potential security concerns. Discovered by Inon Kaplan (PhD candidate in the Hebrew University School of Computer Science and Engineering), Ron Even (BSc graduate of Bar Ilan University) and Amit Klein (faculty member in the Hebrew University School of Computer Science and Engineering). Details will be provided in their paper, to be presented in a forthcoming academic conference. PiperOrigin-RevId: 581334218
1 parent b042aee commit cbdb2c6

File tree

4 files changed

+2
-126
lines changed

4 files changed

+2
-126
lines changed

pkg/tcpip/ports/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ go_library(
1313
],
1414
visibility = ["//visibility:public"],
1515
deps = [
16-
"//pkg/atomicbitops",
1716
"//pkg/rand",
1817
"//pkg/sync",
1918
"//pkg/tcpip",

pkg/tcpip/ports/ports.go

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package ports
1919
import (
2020
"math"
2121

22-
"gvisor.dev/gvisor/pkg/atomicbitops"
2322
"gvisor.dev/gvisor/pkg/rand"
2423
"gvisor.dev/gvisor/pkg/sync"
2524
"gvisor.dev/gvisor/pkg/tcpip"
@@ -228,13 +227,6 @@ type PortManager struct {
228227
ephemeralMu sync.RWMutex
229228
firstEphemeral uint16
230229
numEphemeral uint16
231-
232-
// hint is used to pick ports ephemeral ports in a stable order for
233-
// a given port offset.
234-
//
235-
// hint must be accessed using the portHint/incPortHint helpers.
236-
// TODO(gvisor.dev/issue/940): S/R this field.
237-
hint atomicbitops.Uint32
238230
}
239231

240232
// NewPortManager creates new PortManager.
@@ -264,38 +256,12 @@ func (pm *PortManager) PickEphemeralPort(rng rand.RNG, testPort PortTester) (por
264256
return pickEphemeralPort(rng.Uint32(), firstEphemeral, numEphemeral, testPort)
265257
}
266258

267-
// portHint atomically reads and returns the pm.hint value.
268-
func (pm *PortManager) portHint() uint32 {
269-
return pm.hint.Load()
270-
}
271-
272-
// incPortHint atomically increments pm.hint by 1.
273-
func (pm *PortManager) incPortHint() {
274-
pm.hint.Add(1)
275-
}
276-
277-
// PickEphemeralPortStable starts at the specified offset + pm.portHint and
278-
// iterates over all ephemeral ports, allowing the caller to decide whether a
279-
// given port is suitable for its needs and stopping when a port is found or an
280-
// error occurs.
281-
func (pm *PortManager) PickEphemeralPortStable(offset uint32, testPort PortTester) (port uint16, err tcpip.Error) {
282-
pm.ephemeralMu.RLock()
283-
firstEphemeral := pm.firstEphemeral
284-
numEphemeral := pm.numEphemeral
285-
pm.ephemeralMu.RUnlock()
286-
287-
p, err := pickEphemeralPort(pm.portHint()+offset, firstEphemeral, numEphemeral, testPort)
288-
if err == nil {
289-
pm.incPortHint()
290-
}
291-
return p, err
292-
}
293-
294259
// pickEphemeralPort starts at the offset specified from the FirstEphemeral port
295260
// and iterates over the number of ports specified by count and allows the
296261
// caller to decide whether a given port is suitable for its needs, and stopping
297262
// when a port is found or an error occurs.
298263
func pickEphemeralPort(offset uint32, first, count uint16, testPort PortTester) (port uint16, err tcpip.Error) {
264+
// This implements Algorithm 1 as per RFC 6056 Section 3.3.1.
299265
for i := uint32(0); i < uint32(count); i++ {
300266
port := uint16(uint32(first) + (offset+i)%uint32(count))
301267
ok, err := testPort(port)

pkg/tcpip/ports/ports_test.go

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package ports
1616

1717
import (
1818
"math"
19-
"math/rand"
2019
"testing"
2120

2221
"github.com/google/go-cmp/cmp"
@@ -434,70 +433,6 @@ func TestPickEphemeralPort(t *testing.T) {
434433
}
435434
}
436435

437-
func TestPickEphemeralPortStable(t *testing.T) {
438-
const (
439-
firstEphemeral = 32000
440-
numEphemeralPorts = 1000
441-
)
442-
443-
for _, test := range []struct {
444-
name string
445-
f func(port uint16) (bool, tcpip.Error)
446-
wantErr tcpip.Error
447-
wantPort uint16
448-
}{
449-
{
450-
name: "no-port-available",
451-
f: func(port uint16) (bool, tcpip.Error) {
452-
return false, nil
453-
},
454-
wantErr: &tcpip.ErrNoPortAvailable{},
455-
},
456-
{
457-
name: "port-tester-error",
458-
f: func(port uint16) (bool, tcpip.Error) {
459-
return false, &tcpip.ErrBadBuffer{}
460-
},
461-
wantErr: &tcpip.ErrBadBuffer{},
462-
},
463-
{
464-
name: "only-port-16042-available",
465-
f: func(port uint16) (bool, tcpip.Error) {
466-
if port == firstEphemeral+42 {
467-
return true, nil
468-
}
469-
return false, nil
470-
},
471-
wantPort: firstEphemeral + 42,
472-
},
473-
{
474-
name: "only-port-under-16000-available",
475-
f: func(port uint16) (bool, tcpip.Error) {
476-
if port < firstEphemeral {
477-
return true, nil
478-
}
479-
return false, nil
480-
},
481-
wantErr: &tcpip.ErrNoPortAvailable{},
482-
},
483-
} {
484-
t.Run(test.name, func(t *testing.T) {
485-
pm := NewPortManager()
486-
if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
487-
t.Fatalf("failed to set ephemeral port range: %s", err)
488-
}
489-
portOffset := uint32(rand.Int31n(int32(numEphemeralPorts)))
490-
port, err := pm.PickEphemeralPortStable(portOffset, test.f)
491-
if diff := cmp.Diff(test.wantErr, err); diff != "" {
492-
t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)
493-
}
494-
if port != test.wantPort {
495-
t.Errorf("got PickEphemeralPort(..) = (%d, nil); want (%d, nil)", port, test.wantPort)
496-
}
497-
})
498-
}
499-
}
500-
501436
// TestOverflow addresses b/183593432, wherein an overflowing uint16 causes a
502437
// port allocation failure.
503438
func TestOverflow(t *testing.T) {

pkg/tcpip/transport/tcp/endpoint.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package tcp
1616

1717
import (
1818
"container/heap"
19-
"encoding/binary"
2019
"fmt"
2120
"io"
2221
"math"
@@ -29,7 +28,6 @@ import (
2928
"gvisor.dev/gvisor/pkg/sleep"
3029
"gvisor.dev/gvisor/pkg/sync"
3130
"gvisor.dev/gvisor/pkg/tcpip"
32-
"gvisor.dev/gvisor/pkg/tcpip/hash/jenkins"
3331
"gvisor.dev/gvisor/pkg/tcpip/header"
3432
"gvisor.dev/gvisor/pkg/tcpip/ports"
3533
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
@@ -2246,28 +2244,6 @@ func (e *endpoint) registerEndpoint(addr tcpip.FullAddress, netProto tcpip.Netwo
22462244
// endpoint would be trying to connect to itself).
22472245
sameAddr := e.TransportEndpointInfo.ID.LocalAddress == e.TransportEndpointInfo.ID.RemoteAddress
22482246

2249-
// Calculate a port offset based on the destination IP/port and
2250-
// src IP to ensure that for a given tuple (srcIP, destIP,
2251-
// destPort) the offset used as a starting point is the same to
2252-
// ensure that we can cycle through the port space effectively.
2253-
portBuf := make([]byte, 2)
2254-
binary.LittleEndian.PutUint16(portBuf, e.ID.RemotePort)
2255-
2256-
h := jenkins.Sum32(e.protocol.portOffsetSecret)
2257-
for _, s := range [][]byte{
2258-
e.ID.LocalAddress.AsSlice(),
2259-
e.ID.RemoteAddress.AsSlice(),
2260-
portBuf,
2261-
} {
2262-
// Per io.Writer.Write:
2263-
//
2264-
// Write must return a non-nil error if it returns n < len(p).
2265-
if _, err := h.Write(s); err != nil {
2266-
panic(err)
2267-
}
2268-
}
2269-
portOffset := h.Sum32()
2270-
22712247
var twReuse tcpip.TCPTimeWaitReuseOption
22722248
if err := e.stack.TransportProtocolOption(ProtocolNumber, &twReuse); err != nil {
22732249
panic(fmt.Sprintf("e.stack.TransportProtocolOption(%d, %#v) = %s", ProtocolNumber, &twReuse, err))
@@ -2284,7 +2260,7 @@ func (e *endpoint) registerEndpoint(addr tcpip.FullAddress, netProto tcpip.Netwo
22842260
}
22852261

22862262
bindToDevice := tcpip.NICID(e.ops.GetBindToDevice())
2287-
if _, err := e.stack.PickEphemeralPortStable(portOffset, func(p uint16) (bool, tcpip.Error) {
2263+
if _, err := e.stack.PickEphemeralPort(e.stack.SecureRNG(), func(p uint16) (bool, tcpip.Error) {
22882264
if sameAddr && p == e.TransportEndpointInfo.ID.RemotePort {
22892265
return false, nil
22902266
}

0 commit comments

Comments
 (0)