From af36d192c39df2e080c07e1eaa4a508af656254f Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Mon, 2 Feb 2026 09:57:07 +0100 Subject: [PATCH] RBAC helper: fix data race The verbs parameter slice might be shared between different rule instances and gets sorted (= written), so we have to make a copy or (even better) also de-duplicate as in pkg/apis/rbac/helpers.go. More specifically, plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go shares the Read and ReadWrite slices, causing: WARNING: DATA RACE Read at 0x000008e5e5b0 by goroutine 124: slices.insertionSortOrdered[go.shape.string]() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/slices/zsortordered.go:14 +0x126 slices.pdqsortOrdered[go.shape.string]() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/slices/zsortordered.go:75 +0x6c4 slices.Sort[go.shape.[]string,go.shape.string]() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/slices/sort.go:18 +0x64 sort.Strings() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/sort/sort.go:181 +0xe k8s.io/kubernetes/pkg/apis/rbac/v1.(*PolicyRuleBuilder).Rule() /home/prow/go/src/k8s.io/kubernetes/pkg/apis/rbac/v1/helpers.go:98 +0x2c9 k8s.io/kubernetes/pkg/apis/rbac/v1.(*PolicyRuleBuilder).RuleOrDie() /home/prow/go/src/k8s.io/kubernetes/pkg/apis/rbac/v1/helpers.go:65 +0x2f44 k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy.ClusterRoles() /home/prow/go/src/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go:404 +0x2c13 ... Previous write at 0x000008e5e5b0 by goroutine 123: slices.insertionSortOrdered[go.shape.string]() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/slices/zsortordered.go:15 +0x2f9 slices.pdqsortOrdered[go.shape.string]() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/slices/zsortordered.go:75 +0x6c4 slices.Sort[go.shape.[]string,go.shape.string]() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/slices/sort.go:18 +0x64 sort.Strings() /home/prow/go/src/k8s.io/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.25.6.linux-amd64/src/sort/sort.go:181 +0xe k8s.io/kubernetes/pkg/apis/rbac/v1.(*PolicyRuleBuilder).Rule() /home/prow/go/src/k8s.io/kubernetes/pkg/apis/rbac/v1/helpers.go:98 +0x2c9 k8s.io/kubernetes/pkg/apis/rbac/v1.(*PolicyRuleBuilder).RuleOrDie() /home/prow/go/src/k8s.io/kubernetes/pkg/apis/rbac/v1/helpers.go:65 +0x2f44 k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy.ClusterRoles() /home/prow/go/src/k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/policy.go:404 +0x2c13 Seen in test/integration/apiserver/oidc. --- pkg/apis/rbac/v1/helpers.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/apis/rbac/v1/helpers.go b/pkg/apis/rbac/v1/helpers.go index 669e48c8a6f..d30e33a65a5 100644 --- a/pkg/apis/rbac/v1/helpers.go +++ b/pkg/apis/rbac/v1/helpers.go @@ -24,6 +24,7 @@ import ( "sort" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" ) // +k8s:deepcopy-gen=false @@ -37,30 +38,36 @@ type PolicyRuleBuilder struct { func NewRule(verbs ...string) *PolicyRuleBuilder { return &PolicyRuleBuilder{ - PolicyRule: rbacv1.PolicyRule{Verbs: verbs}, + PolicyRule: rbacv1.PolicyRule{Verbs: sets.NewString(verbs...).List()}, } } func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { - r.PolicyRule.APIGroups = append(r.PolicyRule.APIGroups, groups...) + r.PolicyRule.APIGroups = combine(r.PolicyRule.APIGroups, groups) return r } func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { - r.PolicyRule.Resources = append(r.PolicyRule.Resources, resources...) + r.PolicyRule.Resources = combine(r.PolicyRule.Resources, resources) return r } func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { - r.PolicyRule.ResourceNames = append(r.PolicyRule.ResourceNames, names...) + r.PolicyRule.ResourceNames = combine(r.PolicyRule.ResourceNames, names) return r } func (r *PolicyRuleBuilder) URLs(urls ...string) *PolicyRuleBuilder { - r.PolicyRule.NonResourceURLs = append(r.PolicyRule.NonResourceURLs, urls...) + r.PolicyRule.NonResourceURLs = combine(r.PolicyRule.NonResourceURLs, urls) return r } +func combine(s1, s2 []string) []string { + s := sets.NewString(s1...) + s.Insert(s2...) + return s.List() +} + func (r *PolicyRuleBuilder) RuleOrDie() rbacv1.PolicyRule { ret, err := r.Rule() if err != nil {