Skip to content

Commit c971de0

Browse files
umut-polatneilalexander
authored andcommitted
[FIXED] Nil account panic in processLeafNodeConnect
When a leafnode connection is closed during authentication (e.g. auth timeout), the account may not have been set on the client yet. If processLeafNodeConnect continues to run after the connection was closed, it dereferences c.acc in registerLeafNodeCluster, causing a nil pointer panic. Add a nil check for the account before proceeding, consistent with other code paths in the same file (initLeafNodeSmapAndSendSubs, checkJetStreamExportReconcile) that already guard against this. Add a test that races a CONNECT with an expired auth timeout to exercise the guard. Resolves #7989 Signed-off-by: umut-polat <52835619+umut-polat@users.noreply.github.com>
1 parent 6556b92 commit c971de0

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

server/leafnode.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,13 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro
21582158
acc := c.acc
21592159
c.mu.Unlock()
21602160

2161+
// If the account is not set (e.g. connection was closed due to auth
2162+
// timeout while still being processed), bail out to avoid a panic.
2163+
if acc == nil {
2164+
c.closeConnection(MissingAccount)
2165+
return ErrMissingAccount
2166+
}
2167+
21612168
// Register the cluster, even if empty, as long as we are acting as a hub.
21622169
if !proto.Hub {
21632170
acc.registerLeafNodeCluster(proto.Cluster)

server/leafnode_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12160,3 +12160,51 @@ func TestLeafNodeNoAccPanicOnLeafSubBeforeConnectOperatorMode(t *testing.T) {
1216012160
t.Fatal("Server should not have shutdown")
1216112161
}
1216212162
}
12163+
12164+
func TestLeafNodeNoAccPanicOnProcessLeafNodeConnect(t *testing.T) {
12165+
o := DefaultOptions()
12166+
o.LeafNode.Port = -1
12167+
o.LeafNode.AuthTimeout = 0.001 // Very short auth timeout (1ms)
12168+
s := RunServer(o)
12169+
defer s.Shutdown()
12170+
12171+
addr := fmt.Sprintf("127.0.0.1:%d", o.LeafNode.Port)
12172+
c, err := net.Dial("tcp", addr)
12173+
require_NoError(t, err)
12174+
defer c.Close()
12175+
12176+
// Read the INFO.
12177+
br := bufio.NewReader(c)
12178+
c.SetReadDeadline(time.Now().Add(2 * time.Second))
12179+
l, _, err := br.ReadLine()
12180+
require_NoError(t, err)
12181+
if !strings.HasPrefix(string(l), "INFO") {
12182+
t.Fatalf("Expected INFO, got %q", l)
12183+
}
12184+
12185+
// Wait for the auth timeout to fire, then send CONNECT with cluster.
12186+
// This races with the auth timeout closing the connection, which is
12187+
// the scenario from #7989 where c.acc ends up nil.
12188+
time.Sleep(5 * time.Millisecond)
12189+
12190+
connect := `CONNECT {"verbose":false,"pedantic":false,"cluster":"test-cluster"}`
12191+
c.Write([]byte(connect + "\r\n"))
12192+
12193+
// The server should close the connection without panicking.
12194+
c.SetReadDeadline(time.Now().Add(2 * time.Second))
12195+
buf := make([]byte, 256)
12196+
for {
12197+
_, err = c.Read(buf)
12198+
if err != nil {
12199+
break
12200+
}
12201+
}
12202+
12203+
// Make sure the server is still running (not panicked).
12204+
s.mu.Lock()
12205+
shutdown := s.isShuttingDown()
12206+
s.mu.Unlock()
12207+
if shutdown {
12208+
t.Fatal("Server should not have shutdown")
12209+
}
12210+
}

0 commit comments

Comments
 (0)