From 4a009f2dae67c5f971bd9674f077ac3ac69166fb Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 8 Apr 2026 22:01:43 +0200 Subject: [PATCH 1/2] bugfix: Race condition on agent.Active field. `agent.Active` is a plain `bool` read and written from multiple goroutines without any mutex or atomic protection. A goroutine can read a stale value of `Active` while another goroutine is setting it to `false`. This leads to tasks being dispatched to terminated agents, or active agents being incorrectly skipped. Under the Go memory model, unsynchronized bool access is a data race. --- AdaptixServer/core/server/mgr_task.go | 4 ++-- AdaptixServer/core/server/server.go | 7 ++++--- AdaptixServer/core/server/ts_agent.go | 8 ++++---- AdaptixServer/core/server/ts_terminal.go | 2 +- AdaptixServer/core/server/ts_tunnels.go | 4 ++-- AdaptixServer/core/server/utils.go | 12 ++++++++++-- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/AdaptixServer/core/server/mgr_task.go b/AdaptixServer/core/server/mgr_task.go index c7f59d49f..ed25e3e08 100644 --- a/AdaptixServer/core/server/mgr_task.go +++ b/AdaptixServer/core/server/mgr_task.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/Adaptix-Framework/axc2" + adaptix "github.com/Adaptix-Framework/axc2" ) type TaskHandler interface { @@ -141,7 +141,7 @@ func (tm *TaskManager) Create(agentId string, cmdline string, client string, tas return } - if !agent.Active { + if !agent.IsActive() { return } diff --git a/AdaptixServer/core/server/server.go b/AdaptixServer/core/server/server.go index 93d3505ac..9fc478049 100644 --- a/AdaptixServer/core/server/server.go +++ b/AdaptixServer/core/server/server.go @@ -14,7 +14,7 @@ import ( "os" "time" - "github.com/Adaptix-Framework/axc2" + adaptix "github.com/Adaptix-Framework/axc2" "github.com/goccy/go-yaml" ) @@ -112,11 +112,12 @@ func (ts *Teamserver) RestoreData() { PivotParent: nil, PivotChilds: safe.NewSlice(), Tick: false, - Active: true, } if agentData.Mark == "Terminated" { - agent.Active = false + agent.SetActive(false) + } else { + agent.SetActive(true) } if agentData.Mark == "" { diff --git a/AdaptixServer/core/server/ts_agent.go b/AdaptixServer/core/server/ts_agent.go index 25620fbaa..e3cd5a9f3 100644 --- a/AdaptixServer/core/server/ts_agent.go +++ b/AdaptixServer/core/server/ts_agent.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/Adaptix-Framework/axc2" + adaptix "github.com/Adaptix-Framework/axc2" ) func (ts *Teamserver) TsAgentList() (string, error) { @@ -90,8 +90,8 @@ func (ts *Teamserver) TsAgentCreate(agentCrc string, agentId string, beat []byte PivotParent: nil, PivotChilds: safe.NewSlice(), Tick: false, - Active: true, } + agent.SetActive(true) agent.SetData(agentData) // --- PRE HOOK --- @@ -137,7 +137,7 @@ func (ts *Teamserver) TsAgentCommand(agentName string, agentId string, clientNam if err != nil { return err } - if !agent.Active { + if !agent.IsActive() { return fmt.Errorf("agent '%v' not active", agentId) } @@ -615,7 +615,7 @@ func (ts *Teamserver) TsAgentTerminate(agentId string, terminateTaskId string) e if !ok { return errors.New("invalid agent type") } - agent.Active = false + agent.SetActive(false) agent.UpdateData(func(d *adaptix.AgentData) { d.Mark = "Terminated" }) diff --git a/AdaptixServer/core/server/ts_terminal.go b/AdaptixServer/core/server/ts_terminal.go index 3aaa45bab..12579337f 100644 --- a/AdaptixServer/core/server/ts_terminal.go +++ b/AdaptixServer/core/server/ts_terminal.go @@ -41,7 +41,7 @@ func (ts *Teamserver) TsAgentTerminalCreateChannel(terminalData string, wsconn * if err != nil { return err } - if !agent.Active { + if !agent.IsActive() { return fmt.Errorf("agent '%v' not active", td.AgentId) } diff --git a/AdaptixServer/core/server/ts_tunnels.go b/AdaptixServer/core/server/ts_tunnels.go index 1652396d6..f4d91aeb5 100644 --- a/AdaptixServer/core/server/ts_tunnels.go +++ b/AdaptixServer/core/server/ts_tunnels.go @@ -17,7 +17,7 @@ import ( "sync" "time" - "github.com/Adaptix-Framework/axc2" + adaptix "github.com/Adaptix-Framework/axc2" "github.com/gorilla/websocket" ) @@ -45,7 +45,7 @@ func (ts *Teamserver) TsTunnelClientStart(AgentId string, Listen bool, Type int, if !ok { return "", fmt.Errorf("invalid agent type for '%v'", AgentId) } - if agent.Active == false { + if !agent.IsActive() { return "", fmt.Errorf("agent '%v' not active", AgentId) } diff --git a/AdaptixServer/core/server/utils.go b/AdaptixServer/core/server/utils.go index 765a841d9..fdd09e887 100644 --- a/AdaptixServer/core/server/utils.go +++ b/AdaptixServer/core/server/utils.go @@ -14,7 +14,7 @@ import ( "sync" "sync/atomic" - "github.com/Adaptix-Framework/axc2" + adaptix "github.com/Adaptix-Framework/axc2" "github.com/gorilla/websocket" ) @@ -69,7 +69,7 @@ type Agent struct { data adaptix.AgentData Extender adaptix.ExtenderAgent Tick bool - Active bool + active atomic.Bool HostedTasks *safe.Queue // taskData TaskData HostedTunnelTasks *safe.Queue // taskData TaskData @@ -82,6 +82,14 @@ type Agent struct { PivotChilds *safe.Slice } +func (a *Agent) IsActive() bool { + return a.active.Load() +} + +func (a *Agent) SetActive(v bool) { + a.active.Store(v) +} + func (a *Agent) GetData() adaptix.AgentData { a.mu.RLock() defer a.mu.RUnlock() From 1ede049290c5e595601b69611d9827d29e7b2fc7 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 8 Apr 2026 22:33:03 +0200 Subject: [PATCH 2/2] bugfix: Race condition on Agent.Tick field. `agent.Tick` is a plain `bool` read and written from multiple goroutines without any mutex or atomic protection. `TsAgentProcessData` writes `Tick = true` from listener/handler goroutines while `TsAgentTickUpdate` concurrently reads and resets it in a background loop (800ms interval). --- AdaptixServer/core/server/server.go | 1 - AdaptixServer/core/server/ts_agent.go | 7 +++---- AdaptixServer/core/server/utils.go | 10 +++++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/AdaptixServer/core/server/server.go b/AdaptixServer/core/server/server.go index 9fc478049..76f442b27 100644 --- a/AdaptixServer/core/server/server.go +++ b/AdaptixServer/core/server/server.go @@ -111,7 +111,6 @@ func (ts *Teamserver) RestoreData() { RunningJobs: safe.NewMap(), PivotParent: nil, PivotChilds: safe.NewSlice(), - Tick: false, } if agentData.Mark == "Terminated" { diff --git a/AdaptixServer/core/server/ts_agent.go b/AdaptixServer/core/server/ts_agent.go index e3cd5a9f3..011422b49 100644 --- a/AdaptixServer/core/server/ts_agent.go +++ b/AdaptixServer/core/server/ts_agent.go @@ -89,7 +89,6 @@ func (ts *Teamserver) TsAgentCreate(agentCrc string, agentId string, beat []byte RunningJobs: safe.NewMap(), PivotParent: nil, PivotChilds: safe.NewSlice(), - Tick: false, } agent.SetActive(true) agent.SetData(agentData) @@ -854,7 +853,7 @@ func (ts *Teamserver) TsAgentSetTick(agentId string, listenerName string) error }) _ = ts.DBMS.DbAgentTick(agent.GetData()) } - agent.Tick = true + agent.SetTicked(true) } else if listenerChanged { agent.UpdateData(func(d *adaptix.AgentData) { d.Listener = listenerName @@ -879,8 +878,8 @@ func (ts *Teamserver) TsAgentTickUpdate() { } agentData := agent.GetData() if agentData.Async { - if agent.Tick { - agent.Tick = false + if agent.IsTicked() { + agent.SetTicked(false) agentSlice = append(agentSlice, agentData.Id) } } diff --git a/AdaptixServer/core/server/utils.go b/AdaptixServer/core/server/utils.go index fdd09e887..08469efd0 100644 --- a/AdaptixServer/core/server/utils.go +++ b/AdaptixServer/core/server/utils.go @@ -68,7 +68,7 @@ type Agent struct { mu sync.RWMutex data adaptix.AgentData Extender adaptix.ExtenderAgent - Tick bool + tick atomic.Bool active atomic.Bool HostedTasks *safe.Queue // taskData TaskData @@ -82,6 +82,14 @@ type Agent struct { PivotChilds *safe.Slice } +func (a *Agent) IsTicked() bool { + return a.tick.Load() +} + +func (a *Agent) SetTicked(v bool) { + a.tick.Store(v) +} + func (a *Agent) IsActive() bool { return a.active.Load() }