Return EOF, not EBADF, when receiving on a severed host UDS

SCMSender.beforeSave disconnects the endpoint on save: it shuts down and
closes the host fd, leaving fd = -1. Send already handles this state via
its IsSendClosed check (beforeSave sets sendShutdown), so writes fail
with EPIPE, but Recv had no guard and passed -1 to recvmsg(2), leaking
EBADF to applications for an fd that is still valid in their fd table.
Return ErrClosedForReceive instead, which the socket layer already
surfaces as read() == 0, matching Linux EOF semantics for a socket whose
read side is shut down and making a severed host endpoint behave like
any socket whose peer has gone away.

The guard keys on fd < 0 rather than IsRecvClosed: after a local SHUT_RD
on a live fd, the host socket may still hold queued data that must be
drained before EOF, and host recvmsg(2) handles that correctly.

PiperOrigin-RevId: 976063849
This commit is contained in:
Shailend Chand
2026-09-03 20:21:53 -07:00
committed by gVisor bot
parent 152c2f12d6
commit d894fdaee6
2 changed files with 45 additions and 0 deletions
+10
View File
@@ -277,6 +277,16 @@ func (c *HostSender) Recv(ctx context.Context, data [][]byte, args RecvArgs) (Re
c.mu.RLock()
defer c.mu.RUnlock()
if c.fd < 0 {
// The host fd was severed (e.g. disconnected on save), so no
// data will ever arrive: report EOF, as Linux does for a
// read-shutdown socket. Passing -1 to recvmsg(2) would leak
// EBADF to the application for an fd that it rightly considers
// valid. Keying on recvShutdown instead would be wrong: a live
// fd may still have host-queued data to drain after SHUT_RD.
return RecvOutput{}, false, syserr.ErrClosedForReceive
}
var cm unet.ControlMessage
if args.NumRights > 0 {
cm.EnableFDs(int(args.NumRights))
@@ -18,6 +18,7 @@ import (
"testing"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/waiter"
)
@@ -99,3 +100,37 @@ func TestConnectionedEndpointClosedHostReadiness(t *testing.T) {
t.Fatalf("Readiness(%v) for closed host endpoint = %v, want %v", mask, got, want)
}
}
// TestSeveredRecvSend verifies that an SCMSender whose host fd was severed by
// beforeSave reports EOF on receive and EPIPE on send, never EBADF: the
// application's fd is still valid even though the sentry's host fd is gone.
func TestSeveredRecvSend(t *testing.T) {
fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0)
if err != nil {
t.Fatalf("Socketpair failed: %v", err)
}
defer unix.Close(fds[1])
ep, serr := NewSCMSender(fds[0], &waiter.Queue{}, "test-addr")
if serr != nil {
unix.Close(fds[0])
t.Fatalf("NewSCMSender failed: %v", serr)
}
if err := ep.Init(); err != nil {
unix.Close(fds[0])
t.Fatalf("Init failed: %v", err)
}
ep.beforeSave()
if ep.fd != -1 {
t.Fatalf("after beforeSave, ep.fd = %d, want -1", ep.fd)
}
buf := [][]byte{make([]byte, 8)}
if _, _, serr := ep.Recv(nil, buf, RecvArgs{}); serr != syserr.ErrClosedForReceive {
t.Errorf("Recv on severed endpoint returned %v, want %v", serr, syserr.ErrClosedForReceive)
}
if _, _, serr := ep.Send(nil, [][]byte{{1}}, ControlMessages{}, Address{}); serr != syserr.ErrClosedForSend {
t.Errorf("Send on severed endpoint returned %v, want %v", serr, syserr.ErrClosedForSend)
}
}