elastic / elastic/beats

[bug-hunter] Journald reader panics on Close after journalctl restart failure

Open Beginner friendly
#49,771 1 comment 0 reactions 0 assignees View on GitHub
needs_team
Dominant language
Go
Stars
12.7k
Forks
5k
Avg merge
2d 15m
Merged PRs (30d)
385

Description

## Impact
When `journalctl` exits and the reader attempts a restart that fails, `Reader.Close()` dereferences a nil `jctl` and panics. This can crash journald ingestion (and potentially terminate the Beat process) instead of surfacing a normal error.

## Reproduction Steps
1. Create and run this standalone repro from repo root:

```go
package main

import (
"context"
"errors"
"fmt"
"strings"

"github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalctl"
"github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalfield"
input "github.com/elastic/beats/v7/filebeat/input/v2"
"github.com/elastic/elastic-agent-libs/logp"
)

type fakeJctl struct {
next func(input.Canceler) ([]byte, error)
}

func (f *fakeJctl) Next(c input.Canceler) ([]byte, error) { return f.next(c) }
func (f *fakeJctl) Kill() error { return nil }

func main() {
ctx := context.Background()
factoryCalls := 0
factory := func(c input.Canceler, logger *logp.Logger, args ...string) (journalctl.Jctl, error) {
for _, a := range args {
if strings.Contains(a, "--version") {
return &fakeJctl{next: func(input.Canceler) ([]byte, error) {
return []byte("systemd 259 (259.3-1-arch)\n"), nil
}}, nil
}
}
if factoryCalls == 0 {
factoryCalls++
return &fakeJctl{next: func(input.Canceler) ([]byte, error) {
return nil, errors.New("journalctl exited with code 42")
}}, nil
}
return nil, errors.New("simulated restart spawn failure")
}

reader, err := journalctl.New(
logp.NewNopLogger(),
ctx,
nil,
nil,
nil,
journalfield.IncludeMatches{},
[]int{},
journalctl.SeekHead,
"",
0,
"",
false,
factory,
)
if err != nil {
panic(err)
}

_, err = reader.Next(ctx)
fmt.Printf("Next returned error: %v\n", err)
fmt.Println("Calling reader.Close() ...")
_ = reader.Close()
}
```

2. Run:

```bash
go run /tmp/gh-aw/agent/repro_journald_nil_close.go
```

## Expected vs Actual
**Expected:** After restart failure, `Next()` should return an error and `Close()` should safely return an error (or nil), without panic.

**Actual:** `Close()` panics with nil-pointer dereference.

Observed output:

```text
Next returned error: cannot restart journalctl: simulated restart spawn failure
Calling reader.Close() ...
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x685a38]

goroutine 1 [running]:
github.com/elastic/beats/v7/filebeat/input/journald/pkg/journalctl.(*Reader).Close(...)
filebeat/input/journald/pkg/journalctl/reader.go:313
```

## Failing Test
A minimal test can mock factory behavior to:
- return a working `Jctl` initially,
- return `(nil, err)` on restart path,
- assert `reader.Next(...)` returns `cannot restart journalctl`,
- then assert `reader.Close()` does not panic.

## Evidence
- `filebeat/input/journald/pkg/journalctl/reader.go:301-304` assigns `r.jctl = jctl` even when `newJctl` returns error.
- `filebeat/input/journald/pkg/journalctl/reader.go:370-373` restart path can return `cannot restart journalctl`.
- `filebeat/input/journald/pkg/journalctl/reader.go:313` `Close()` unconditionally calls `r.jctl.Kill()`.
- `filebeat/input/journald/input.go:221` uses `defer reader.Close()`, so the panic is reachable on error returns from read loop.

> [!NOTE]
>
> 🔒 Integrity filtering filtered 1 item
>
> Integrity filtering activated and filtered the following item during workflow execution.
> This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.
>
> - issue:elastic/beats#unknown (`search_issues`: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".)
>
>

---
[What is this?](https://ela.st/github-ai-tools) | [From workflow: Bug Hunter](https://github.com/elastic/beats/actions/runs/23742207004)

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
> - [x] expires on Apr 6, 2026, 11:39 AM UTC

Contributor guide

Open the contributing guide

Research direction

Start in filebeat/input/journald/pkg/journalctl/reader.go, especially the restart path around lines 301-304 and Close around line 313; review filebeat/input/journald/input.go to understand deferred cleanup. Add focused regression coverage for a failed restart, verifying Next returns the restart error and Close completes without a panic.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, operating-systems
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.