From 7c15d6058cf56e8c7801259f1f842a3a010c5f41 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 13 May 2026 10:21:56 +0300 Subject: fix: guard Pair.CalculateDurations against uint64 underflow on clock skew BPF timestamps can be non-monotonic across CPUs (NTP step, TSC skew). When exit < enter or enter < prevPairTime the uint64 subtraction wraps to a huge value, corrupting latency histograms and flamegraph weights. Clamp both Duration and DurationToPrev to 0 instead of underflowing. Add TestPairCalculateDurationsNegativeDelta to cover this case. Co-Authored-By: Claude Sonnet 4.6 --- internal/event/pair_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'internal/event/pair_test.go') diff --git a/internal/event/pair_test.go b/internal/event/pair_test.go index eb033dc..9aa3e11 100644 --- a/internal/event/pair_test.go +++ b/internal/event/pair_test.go @@ -56,6 +56,36 @@ func TestPairCalculateDurationsWithPreviousExit(t *testing.T) { } } +// TestPairCalculateDurationsNegativeDelta verifies that non-monotonic BPF +// timestamps (exit < enter due to cross-CPU clock skew) do not cause uint64 +// underflow. Both Duration and DurationToPrev must clamp to zero. +func TestPairCalculateDurationsNegativeDelta(t *testing.T) { + enter := &types.OpenEvent{ + Time: 2000, + Pid: 1, + Tid: 2, + } + // Simulate clock skew: exit timestamp is earlier than enter. + exit := &types.RetEvent{ + Time: 1900, + Pid: 1, + Tid: 2, + Ret: 0, + } + + pair := NewPair(enter) + pair.ExitEv = exit + // prevPairTime > enterTime also triggers underflow in DurationToPrev. + pair.CalculateDurations(3000) + + if pair.Duration != 0 { + t.Fatalf("Duration = %d, want 0 when exit < enter (underflow guard)", pair.Duration) + } + if pair.DurationToPrev != 0 { + t.Fatalf("DurationToPrev = %d, want 0 when enter < prevPairTime (underflow guard)", pair.DurationToPrev) + } +} + func TestPairRecycleHandlesMissingExitEvent(t *testing.T) { pair := NewPair(&types.OpenEvent{ Time: 1000, -- cgit v1.2.3