Skip to content

Commit ce46fbf

Browse files
ysksuzukijulianwiedmann
authored andcommitted
sockets: handle errors in SOCK_DESTROY probe
The SOCK_DESTROY probe dereferences sockets without first checking the iteration error. Receive-side failures are reported with a nil socket, so this can panic during startup as reported in cilium#46351. Handle iteration errors before accessing the socket. Return request-level errors, while continuing past per-message deserialization errors to preserve the existing best-effort behavior. Fixes: cilium#46351 Signed-off-by: Yusuke Suzuki <yusuke.suzuki@isovalent.com>
1 parent b2a2970 commit ce46fbf

2 files changed

Lines changed: 64 additions & 31 deletions

File tree

pkg/datapath/sockets/probe.go

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,56 @@ type inetProbe struct {
8989
port uint16
9090
}
9191

92+
type sockDestroyProbe struct {
93+
inetProbe
94+
logger *slog.Logger
95+
found bool
96+
count int
97+
}
98+
99+
func (p *sockDestroyProbe) handleSocket(s *netlink.Socket, err error) error {
100+
if err != nil {
101+
if s == nil {
102+
return err
103+
}
104+
p.logger.Debug("encountered an error while processing a socket during probe, skipping",
105+
logfields.Error, err)
106+
return nil
107+
}
108+
109+
p.logger.Debug("processing socket during SOCK_DESTROY probe",
110+
logfields.Port, p.port,
111+
logfields.Protocol, p.proto)
112+
p.count++
113+
lo := net.IP{127, 0, 0, 1}
114+
if s.ID.SourcePort == p.port && s.ID.Source.Equal(lo) {
115+
p.logger.Debug("found probe socket, attempting destroy",
116+
logfields.Port, p.port,
117+
logfields.Protocol, p.proto)
118+
destroyErr := DestroySocket(slog.Default(), *s, netlink.Proto(p.proto), 0xff)
119+
if errors.Is(destroyErr, unix.ENOTSUP) {
120+
// Note: Returning error stops iteration and passes err through to
121+
// return value of Iterate.
122+
return fmt.Errorf("%w: operation to destroy probe socket is unsupported. "+
123+
"This likely means that kernel CONFIG_INET_DIAG_DESTROY must be set in order for this functionality to work",
124+
probes.ErrNotSupported)
125+
}
126+
if destroyErr != nil {
127+
return destroyErr
128+
}
129+
p.found = true
130+
}
131+
return nil
132+
}
133+
92134
// probeForSockDestroy probes supported socket termination protocols.
93135
// To do this reliably and portably, this creates sockets for udp/tcp
94136
// and attempts to both list and destroy sockets to probe for the full
95137
// suite of inet diag features; ensuring that the sockets.Destroy will
96138
// successfully find and terminate sockets.
97-
// This is sufficient for both ip4 and ip6.
139+
// The current approach uses IPv4 loopback sockets, so it does not work in
140+
// IPv6-only clusters where IPv4 is disabled in the node's kernel. Supporting
141+
// such environments requires a family-aware probe and is left for future work.
98142
func probeForSockDestroy(ctx context.Context, logger *slog.Logger, tcp, udp bool) error {
99143
protoProbes := []inetProbe{}
100144

@@ -127,43 +171,21 @@ func probeForSockDestroy(ctx context.Context, logger *slog.Logger, tcp, udp bool
127171
}
128172

129173
var errs error
130-
for _, probe := range protoProbes {
131-
ok := false
132-
count := 0
133-
lo := net.IP{127, 0, 0, 1}
134-
if err := Iterate(uint8(probe.proto), unix.AF_INET, probe.filterMask, func(s *netlink.Socket, err error) error {
135-
logger.Debug("found probe socket, attempting destroy",
136-
logfields.Port, probe.port,
137-
logfields.Protocol, probe.proto)
138-
count++
139-
if s.ID.SourcePort == uint16(probe.port) && s.ID.Source.Equal(lo) {
140-
logger.Debug("found probe socket, attempting destroy",
141-
logfields.Port, probe.port,
142-
logfields.Protocol, probe.proto)
143-
destroyErr := DestroySocket(slog.Default(), *s, netlink.Proto(probe.proto), 0xff)
144-
if errors.Is(destroyErr, unix.ENOTSUP) {
145-
// Note: Returning error stops iteration and passes err through to
146-
// return value of Iterate.
147-
return fmt.Errorf("%w: operation to destroy probe socket is unsupported. "+
148-
"This likely means that kernel CONFIG_INET_DIAG_DESTROY must be set in order for this functionality to work",
149-
probes.ErrNotSupported)
150-
}
151-
if destroyErr != nil {
152-
return destroyErr
153-
}
154-
ok = true
155-
}
156-
return nil
157-
}); err != nil {
174+
for _, probeConfig := range protoProbes {
175+
probe := sockDestroyProbe{
176+
inetProbe: probeConfig,
177+
logger: logger,
178+
}
179+
if err := Iterate(uint8(probe.proto), unix.AF_INET, probe.filterMask, probe.handleSocket); err != nil {
158180
errs = errors.Join(errs, fmt.Errorf("failed while iterating sockets: %w", err))
159181
continue
160182
}
161-
if !ok {
183+
if !probe.found {
162184
// Unexpected: if we saw other sockets (which is very likely on host ns) then we should
163185
// have found our test sockets.
164186
// By not wrapping in the ErrNotSupported error, we indicate that this is an unexpected error
165187
// not a legitimate probing error.
166-
if count > 0 {
188+
if probe.count > 0 {
167189
return fmt.Errorf("failed to find listener socket for inet diag destroy probe")
168190
} else {
169191
proto := "tcp"

pkg/datapath/sockets/probe_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package sockets
55

66
import (
7+
"errors"
78
"testing"
89

910
"github.com/cilium/cilium/pkg/testutils"
@@ -13,6 +14,16 @@ import (
1314
"github.com/stretchr/testify/assert"
1415
)
1516

17+
func TestSockDestroyProbeHandleSocketError(t *testing.T) {
18+
wantErr := errors.New("netlink receive error")
19+
probe := sockDestroyProbe{logger: hivetest.Logger(t)}
20+
21+
// Netlink receive errors do not include a socket. The callback must return
22+
// the error before dereferencing it.
23+
gotErr := probe.handleSocket(nil, wantErr)
24+
assert.ErrorIs(t, gotErr, wantErr)
25+
}
26+
1627
func TestPrivilegedProbetInetDiagDestroyEnabled(t *testing.T) {
1728
testutils.PrivilegedTest(t)
1829
assert.NoError(t, InetDiagDestroyEnabled(hivetest.Logger(t), true, true))

0 commit comments

Comments
 (0)