From bc699b3d30a327b85a3781f8e598e3e75842296c Mon Sep 17 00:00:00 2001 From: YLChen-007 Date: Sun, 12 Apr 2026 13:08:07 +0800 Subject: [PATCH] gopls/internal/template: fix panic in completion when cursor is between opening braces Add a bounds check in completer.complete() to prevent a slice bounds out of range panic when the cursor is positioned between the two '{' characters of a template action delimiter '{{'. When an LSP client requests code completion at such a position, c.offset (computed as tk.Start + len("{{")) can exceed the cursor position (start), causing c.p.buf[c.offset:start] to panic with 'slice bounds out of range [8:7]'. This crashes the entire gopls process, resulting in a Denial of Service for the IDE user. The fix returns an empty completion list when c.offset > start, which is the correct behavior since there is no meaningful text to complete at a position inside the delimiter characters. Includes a regression test that directly constructs the problematic state and verifies complete() returns gracefully without panicking. --- gopls/internal/template/completion.go | 3 ++ gopls/internal/template/completion_test.go | 39 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/gopls/internal/template/completion.go b/gopls/internal/template/completion.go index fb09ba4ba01..9c9a58ee6d0 100644 --- a/gopls/internal/template/completion.go +++ b/gopls/internal/template/completion.go @@ -127,6 +127,9 @@ func (c *completer) complete() (*protocol.CompletionList, error) { if err != nil { return ans, err } + if c.offset > start { + return ans, nil + } sofar := c.p.buf[c.offset:start] if len(sofar) == 0 || sofar[len(sofar)-1] == ' ' || sofar[len(sofar)-1] == '\t' { return ans, nil diff --git a/gopls/internal/template/completion_test.go b/gopls/internal/template/completion_test.go index 279864ab80d..d024d43571b 100644 --- a/gopls/internal/template/completion_test.go +++ b/gopls/internal/template/completion_test.go @@ -73,6 +73,45 @@ func TestParsed(t *testing.T) { } } +// TestCompleteBoundsCheck tests that complete() does not panic +// when c.offset > start (e.g. cursor between the two '{' characters). +// This is a regression test for a DoS vulnerability where a crafted +// cursor position within "{{" caused a slice bounds out of range panic. +func TestCompleteBoundsCheck(t *testing.T) { + buf := []byte("hello {{ }}") + p := parseBuffer("", buf) + if p.parseErr != nil { + t.Logf("parseBuffer (expected): %v", p.parseErr) + } + // Simulate cursor at position 7 (between '{' and '{'). + // This creates a situation where c.offset (= start + len("{{")) > cursorPos. + pos, err := p.mapper.OffsetPosition(7) + if err != nil { + t.Fatal(err) + } + // Manually construct a completer that triggers the problematic case: + // set offset to 8 (simulating tk.start=6, offset = 6 + len("{{") = 8) + // with cursor position 7 (start < c.offset). + c := &completer{ + p: p, + pos: pos, + offset: 8, + ctx: protocol.CompletionContext{TriggerKind: protocol.Invoked}, + syms: make(map[string]symbol), + } + // This must not panic. + ans, err := c.complete() + if err != nil { + t.Fatalf("complete returned error: %v", err) + } + if ans == nil { + t.Fatal("complete returned nil") + } + if len(ans.Items) != 0 { + t.Errorf("expected no completions, got %d", len(ans.Items)) + } +} + func testCompleter(t *testing.T, tx tparse) *completer { // seen chars up to ^ offset := strings.Index(tx.marked, "^")