From 92eb24db85e03d7d3ecaad2ed7d47f920e85c593 Mon Sep 17 00:00:00 2001 From: manon Date: Mon, 7 Sep 2026 11:35:05 +0900 Subject: [PATCH] netstat: check the scanner result and error parseNetstat discards the first Scan result and never calls Err, so a scanner failure is invisible. The loop then returns netStat, nil with whatever was parsed before the failure, and the caller cannot tell a short read from a complete one. The header case is worse than a lost error. After Scan reports false for a too-long token, the next Scan returns true with the bytes already buffered, so headers stays empty while the counter loop runs, and headers[num] panics with index out of range. net_dev.go, net_sockstat.go, net_unix.go and stat.go all end their scan with scanner.Err(); this is the outlier. Signed-off-by: manon --- netstat.go | 11 ++++++++++- netstat_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/netstat.go b/netstat.go index dbdae473..6196b3fd 100644 --- a/netstat.go +++ b/netstat.go @@ -61,7 +61,12 @@ func parseNetstat(filePath string) (NetStat, error) { defer file.Close() scanner := bufio.NewScanner(file) - scanner.Scan() + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + return NetStat{}, err + } + return netStat, nil + } // First string is always a header for stats var headers []string @@ -78,5 +83,9 @@ func parseNetstat(filePath string) (NetStat, error) { } } + if err := scanner.Err(); err != nil { + return NetStat{}, err + } + return netStat, nil } diff --git a/netstat_test.go b/netstat_test.go index dec77ab0..a0d5a5ac 100644 --- a/netstat_test.go +++ b/netstat_test.go @@ -14,6 +14,10 @@ package procfs import ( + "bufio" + "os" + "path/filepath" + "strings" "testing" ) @@ -112,3 +116,43 @@ func TestNetStat(t *testing.T) { } } } + +// writeNetstatFile writes lines to a temporary file and returns its path. +func writeNetstatFile(t *testing.T, lines ...string) string { + t.Helper() + path := filepath.Join(t.TempDir(), "stat") + if err := os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), 0o600); err != nil { + t.Fatalf("writing fixture: %v", err) + } + return path +} + +func TestParseNetstatReportsScannerErrors(t *testing.T) { + // Hex tokens, so that what the scanner has already buffered still parses + // as counters and the header index is reached. + long := strings.Repeat("00000001 ", bufio.MaxScanTokenSize/9+1) + + for _, tt := range []struct { + name string + lines []string + }{ + {"header too long", []string{long, "00000001"}}, + {"counter line too long", []string{"entries", long}}, + } { + t.Run(tt.name, func(t *testing.T) { + if _, err := parseNetstat(writeNetstatFile(t, tt.lines...)); err == nil { + t.Fatal("expected an error, got nil") + } + }) + } +} + +func TestParseNetstatEmptyFile(t *testing.T) { + stat, err := parseNetstat(writeNetstatFile(t)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(stat.Stats) != 0 { + t.Fatalf("expected no stats, got %d", len(stat.Stats)) + } +}