81 lines
3.2 KiB
Go
81 lines
3.2 KiB
Go
//go:build linux
|
|
|
|
package main
|
|
|
|
// The Linux half of the noise filter — the three questions only this platform can
|
|
// answer, and the one that could be answered wrongly without anyone noticing.
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// HALF A SOCKET TABLE IS NOT A SOCKET TABLE. readSockets returns Supported with
|
|
// whatever it got when `ss` runs out of budget (observe_ops_linux.go:160-168),
|
|
// and the short-lived rule then checks its listener exemption against a set
|
|
// that may simply not contain the socket — silently suppressing the one process
|
|
// the exemption exists to protect. By this file's own rule: an exemption that
|
|
// cannot be checked is not an exemption.
|
|
func TestAPartialSocketTableReadsAsUnreadable(t *testing.T) {
|
|
old := deltaSockets
|
|
t.Cleanup(func() { deltaSockets = old })
|
|
|
|
deltaSockets = func() socketsReport {
|
|
return socketsReport{Supported: true, Sockets: []listenSocket{{PID: 7, Port: 443}}}
|
|
}
|
|
pids, ok := deltaListeningPIDs()
|
|
if !ok || !pids[7] {
|
|
t.Fatalf("a complete table read as unusable: ok=%v pids=%v", ok, pids)
|
|
}
|
|
|
|
deltaSockets = func() socketsReport {
|
|
return socketsReport{Supported: true, Partial: "tcp: timeout",
|
|
Sockets: []listenSocket{{PID: 7, Port: 443}}}
|
|
}
|
|
if _, ok := deltaListeningPIDs(); ok {
|
|
t.Fatal("a table that timed out half way through reported itself readable. " +
|
|
"The short-lived rule would then hold a process for 'opening no listener' " +
|
|
"on the strength of a list that stopped early.")
|
|
}
|
|
|
|
deltaSockets = func() socketsReport { return socketsReport{Reason: "ss not installed"} }
|
|
if _, ok := deltaListeningPIDs(); ok {
|
|
t.Error("a box with no ss reported a readable socket table")
|
|
}
|
|
}
|
|
|
|
// A PROCESS THAT EXITED BETWEEN THE WALK AND THE FILTER IS SHORT-LIVED, not
|
|
// unreadable. The filter runs 50-100 ms after the walk — a parent map, the
|
|
// package database and two `ss` execs later — so this is the most ordinary
|
|
// reason /proc/<pid>/stat is missing, and treating it as "cannot tell" reported
|
|
// the shortest-lived processes on the box instead of counting them.
|
|
func TestAnExitedProcessCountsAsShortLived(t *testing.T) {
|
|
age := deltaAgeReader()
|
|
// This process exists and has a real age.
|
|
if d, ok := age(os.Getpid()); !ok || d < 0 {
|
|
t.Fatalf("this process read as age %v ok=%v", d, ok)
|
|
}
|
|
// A pid that cannot exist: /proc has no entry, which is what an exited
|
|
// process looks like a few milliseconds later.
|
|
d, ok := age(1 << 30)
|
|
if !ok {
|
|
t.Fatal("a process that is gone read as 'cannot tell its age', so the short-lived " +
|
|
"rule reports it instead of counting it — the exact population the rule exists " +
|
|
"to keep out of the findings")
|
|
}
|
|
if d != 0 {
|
|
t.Errorf("a process that is gone read as %v old, want 0", d)
|
|
}
|
|
}
|
|
|
|
// Linux reads both classes. If either constant said otherwise, an accounting
|
|
// taken while `ss` or the account database failed would be written
|
|
// `unsupported` again, and every reader of the line would be told a retake
|
|
// changes nothing when a retake is exactly the fix.
|
|
func TestLinuxReadsBothClassesSoAFailedReaderIsUnread(t *testing.T) {
|
|
if !listenersReadableHere || !identitiesReadableHere {
|
|
t.Fatalf("linux must declare both classes readable: listeners=%v identities=%v",
|
|
listenersReadableHere, identitiesReadableHere)
|
|
}
|
|
}
|