From e0bd987946edafaec993f5b5ef6a4a056ace3760 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sun, 4 Oct 2026 21:37:46 +0800 Subject: [PATCH] require matching live tcpdump before real call --- internal/rpc/approved_recorded_real.go | 19 ++++++++++++++++++ internal/rpc/approved_recorded_real_test.go | 22 +++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/internal/rpc/approved_recorded_real.go b/internal/rpc/approved_recorded_real.go index fc3bf4c..2cb6211 100644 --- a/internal/rpc/approved_recorded_real.go +++ b/internal/rpc/approved_recorded_real.go @@ -201,9 +201,28 @@ func verifyCallEvidence(root, id, trunk, target string) error { if end == -1 || len(state) <= end+2 || state[end+2] == 'Z' || state[end+2] == 'X' { return errors.New("real call capture process has ended") } + // /proc//exe is not readable by the unprivileged Agent when + // the host evidence script starts tcpdump as root. + comm, err := os.ReadFile(filepath.Join("/proc", fields[0], "comm")) + if err != nil || strings.TrimSpace(string(comm)) != "tcpdump" { + return errors.New("real call capture process is not tcpdump") + } + args, err := os.ReadFile(filepath.Join("/proc", fields[0], "cmdline")) + if err != nil || !capturesCall(strings.Split(string(args), "\x00"), id) { + return errors.New("real call capture process does not record this call") + } return nil } +func capturesCall(args []string, id string) bool { + for i := 0; i+1 < len(args); i++ { + if args[i] == "-w" && filepath.IsAbs(args[i+1]) && filepath.Base(args[i+1]) == "capture.pcap" && filepath.Base(filepath.Dir(args[i+1])) == id { + return true + } + } + return false +} + func (r *ApprovedRecordedRealCall) Run(ctx context.Context, approved ApprovedExecution) error { if ctx == nil { return errors.New("real call requires a context") diff --git a/internal/rpc/approved_recorded_real_test.go b/internal/rpc/approved_recorded_real_test.go index 99160cc..7445a05 100644 --- a/internal/rpc/approved_recorded_real_test.go +++ b/internal/rpc/approved_recorded_real_test.go @@ -33,8 +33,8 @@ func TestRealCallRequiresLiveCaptureBoundToSignedIdentity(t *testing.T) { if err := os.WriteFile(marker, []byte(fmt.Sprintf("%d\t%s\t%s\n", os.Getpid(), trunk, target)), 0640); err != nil { t.Fatal(err) } - if err := verifyCallEvidence(root, id, trunk, target); err != nil { - t.Fatal(err) + if err := verifyCallEvidence(root, id, trunk, target); err == nil { + t.Fatal("a live process other than tcpdump must not arm real dialing") } if err := verifyCallEvidence(root, id, trunk, "15830461047"); err == nil { t.Fatal("capture bound to a different callee cannot authorize a real call") @@ -50,6 +50,24 @@ func TestRealCallRequiresLiveCaptureBoundToSignedIdentity(t *testing.T) { } } +func TestCaptureProcessMustWriteThisCall(t *testing.T) { + id := "event-1" + args := []string{"/usr/bin/tcpdump", "-i", "any", "-nn", "-s0", "-U", "-w", "/var/lib/sip-go-agent/evidence/event-1/capture.pcap", "udp port 5060 or (udp portrange 10000-20000)"} + if !capturesCall(args, id) { + t.Fatal("the host script's tcpdump command must be recognized") + } + for _, bad := range [][]string{ + {"/usr/bin/tcpdump", "-w", "/var/lib/sip-go-agent/evidence/other/capture.pcap"}, + {"/usr/bin/tcpdump", "-w", "/var/lib/sip-go-agent/evidence/event-1/other.pcap"}, + {"/usr/bin/tcpdump", "-w", "event-1/capture.pcap"}, + {"/usr/bin/tcpdump", "/var/lib/sip-go-agent/evidence/event-1/capture.pcap"}, + } { + if capturesCall(bad, id) { + t.Fatalf("unrelated capture command accepted: %q", bad) + } + } +} + func TestApprovedRecordedRealCallReportsOnlyEndedObservedCall(t *testing.T) { fixture, stub, puts, _, approved := recordedMockFixture(t, nil, time.Second) approved.CallerID, approved.DialedCallee, approved.RingTimeout = "BD93205882", "7089"+approved.Callee, time.Second