From 6c014d227c0cbaea5fe1333b31ba5e9d57229f5e Mon Sep 17 00:00:00 2001
From: Alexandr Stelnykovych <stenya@gmail.com>
Date: Tue, 17 Dec 2024 15:49:52 +0200
Subject: [PATCH] [fix] Panic while accessing SleepyTicker methods
 Stop()/SetSleep()

The time.Ticker object was stored as a value type, but it is expected to be a pointer according to its implementation:
```
func (t *Ticker) Stop()
func (t *Ticker) Reset(d Duration)
```

This was leading to an application crash.

STR 1:
Run `portmaster-core` without privileged rights. It will not be able to start the kernel driver (Windows).
During unloading of already initialized modules, the process crashes because of stopping SleepyTicker instances in workers of the "network" module.

STR 2:
Run tests from `service\mgr\sleepyticker_test.go`
---
 service/mgr/sleepyticker.go      |  4 +--
 service/mgr/sleepyticker_test.go | 57 ++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 2 deletions(-)
 create mode 100644 service/mgr/sleepyticker_test.go

diff --git a/service/mgr/sleepyticker.go b/service/mgr/sleepyticker.go
index 075912a1..ce0b20b4 100644
--- a/service/mgr/sleepyticker.go
+++ b/service/mgr/sleepyticker.go
@@ -4,7 +4,7 @@ import "time"
 
 // SleepyTicker is wrapper over time.Ticker that respects the sleep mode of the module.
 type SleepyTicker struct {
-	ticker         time.Ticker
+	ticker         *time.Ticker
 	normalDuration time.Duration
 	sleepDuration  time.Duration
 	sleepMode      bool
@@ -16,7 +16,7 @@ type SleepyTicker struct {
 // If sleepDuration is set to 0 ticker will not tick during sleep.
 func NewSleepyTicker(normalDuration time.Duration, sleepDuration time.Duration) *SleepyTicker {
 	st := &SleepyTicker{
-		ticker:         *time.NewTicker(normalDuration),
+		ticker:         time.NewTicker(normalDuration),
 		normalDuration: normalDuration,
 		sleepDuration:  sleepDuration,
 		sleepMode:      false,
diff --git a/service/mgr/sleepyticker_test.go b/service/mgr/sleepyticker_test.go
new file mode 100644
index 00000000..9e2175c7
--- /dev/null
+++ b/service/mgr/sleepyticker_test.go
@@ -0,0 +1,57 @@
+package mgr
+
+import (
+	"testing"
+	"time"
+)
+
+func TestSleepyTickerStop(t *testing.T) {
+	normalDuration := 100 * time.Millisecond
+	sleepDuration := 200 * time.Millisecond
+
+	st := NewSleepyTicker(normalDuration, sleepDuration)
+	st.Stop() // no panic expected here
+}
+
+func TestSleepyTicker(t *testing.T) {
+	normalDuration := 100 * time.Millisecond
+	sleepDuration := 200 * time.Millisecond
+
+	st := NewSleepyTicker(normalDuration, sleepDuration)
+
+	// Test normal mode
+	select {
+	case <-st.Wait():
+		// Expected tick
+	case <-time.After(normalDuration + 50*time.Millisecond):
+		t.Error("expected tick in normal mode")
+	}
+
+	// Test sleep mode
+	st.SetSleep(true)
+	select {
+	case <-st.Wait():
+		// Expected tick
+	case <-time.After(sleepDuration + 50*time.Millisecond):
+		t.Error("expected tick in sleep mode")
+	}
+
+	// Test sleep mode with sleepDuration == 0
+	st = NewSleepyTicker(normalDuration, 0)
+	st.SetSleep(true)
+	select {
+	case <-st.Wait():
+		t.Error("did not expect tick when sleepDuration is 0")
+	case <-time.After(normalDuration):
+		// Expected no tick
+	}
+
+	// Test stopping the ticker
+	st.Stop()
+	select {
+	case <-st.Wait():
+		t.Error("did not expect tick after stopping the ticker")
+	case <-time.After(normalDuration):
+		// Expected no tick
+	}
+}