fix(talosctl): protect k8sNames map writes with mutex

Concurrent goroutines wrote to k8sNames without synchronization,
causing a data race during drain. The comment claiming "no mutex
needed" was wrong - each goroutine writes a different key, but
the Go map implementation is not safe for concurrent writes.

Fixes #13247

Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
This commit is contained in:
Mateusz Urbanek 2026-04-30 14:26:29 +02:00
parent cc2be213a8
commit dedb7a96c1
No known key found for this signature in database
GPG Key ID: F16F84591E26D77F

View File

@ -7,6 +7,7 @@ package talos
import (
"context"
"fmt"
"sync"
"time"
"golang.org/x/sync/errgroup"
@ -41,9 +42,10 @@ func drainNodes(ctx context.Context, c *client.Client, nodes []string, drainTime
updateCh := make(chan nodeUpdate)
// k8sNames collects Talos IP -> K8s node name mappings produced by each goroutine.
// Each goroutine writes to its own slot, so no mutex is needed.
k8sNames := make(map[string]string, len(nodes))
var mapMux sync.Mutex // protects k8sNames map during writes
var eg errgroup.Group
// Aggregator goroutine: reads from updateCh, updates ProgressWriter, prints.
@ -72,7 +74,9 @@ func drainNodes(ctx context.Context, c *client.Client, nodes []string, drainTime
return fmt.Errorf("error resolving Kubernetes node name for %s: %w", node, resolveErr)
}
mapMux.Lock()
k8sNames[node] = k8sNodeName
mapMux.Unlock()
// reportFn sends progress through the channel to the aggregator.
reportFn := func(upd reporter.Update) {
@ -129,9 +133,7 @@ func uncordonNodes(ctx context.Context, c *client.Client, nodeNames map[string]s
}
}()
for talosIP, k8sNodeName := range nodeNames {
_ = talosIP // only k8sNodeName is needed for K8s API calls
for _, k8sNodeName := range nodeNames {
eg.Go(func() error {
reportFn := func(upd reporter.Update) {
updateCh <- nodeUpdate{node: k8sNodeName, update: upd}