mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-20 13:36:32 +09:00
policy/v2: stop ViaRoutesForPeer appending into pm.pol.Grants
Readers share pm.pol under RLock; appending ACL grants into its spare capacity raced between concurrent callers.
This commit is contained in:
1 parent
a9cc142ebe
commit
a99c8a030c
2 files changed
+50
-1
No files matched your search
@@ -1321,7 +1321,9 @@ func (pm *PolicyManager) ViaRoutesForPeer(viewer, peer types.NodeView) types.Via
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
grants := pm.pol.Grants
|
// Clip so the appends below allocate: callers share pm.pol under RLock,
|
||||||
|
// and writing into its spare capacity races between them.
|
||||||
|
grants := slices.Clip(pm.pol.Grants)
|
||||||
for _, acl := range pm.pol.ACLs {
|
for _, acl := range pm.pol.ACLs {
|
||||||
grants = append(grants, aclToGrants(acl)...)
|
grants = append(grants, aclToGrants(acl)...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package v2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -100,3 +101,49 @@ func TestPolicyManagerConcurrentReads(t *testing.T) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestViaRoutesForPeerDoesNotWritePolicyGrants guards against
|
||||||
|
// ViaRoutesForPeer appending ACL-derived grants into the backing array of
|
||||||
|
// pm.pol.Grants. Readers hold only the shared RLock, so any spare capacity
|
||||||
|
// in that slice becomes a write/write race between concurrent callers.
|
||||||
|
func TestViaRoutesForPeerDoesNotWritePolicyGrants(t *testing.T) {
|
||||||
|
users := types.Users{{Name: "user1", Email: "user1@headscale.net"}}
|
||||||
|
|
||||||
|
a := node("a", "100.64.0.1", "fd7a:115c:a1e0::1", users[0])
|
||||||
|
a.ID = 1
|
||||||
|
b := node("b", "100.64.0.2", "fd7a:115c:a1e0::2", users[0])
|
||||||
|
b.ID = 2
|
||||||
|
nodes := types.Nodes{a, b}
|
||||||
|
|
||||||
|
policy := `{
|
||||||
|
"grants": [
|
||||||
|
{"src": ["*"], "dst": ["*"], "ip": ["tcp:22"]}
|
||||||
|
],
|
||||||
|
"acls": [
|
||||||
|
{"action": "accept", "src": ["*"], "dst": ["*:*"]}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
pm, err := NewPolicyManager([]byte(policy), users, nodes.ViewSlice())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Guarantee spare capacity instead of relying on the JSON decoder's
|
||||||
|
// growth policy.
|
||||||
|
pm.pol.Grants = slices.Grow(pm.pol.Grants, 4)
|
||||||
|
spare := pm.pol.Grants[len(pm.pol.Grants):cap(pm.pol.Grants)]
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for range 8 {
|
||||||
|
wg.Go(func() {
|
||||||
|
for range 50 {
|
||||||
|
pm.ViaRoutesForPeer(a.View(), b.View())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for i, g := range spare {
|
||||||
|
assert.Zero(t, g, "spare slot %d of pm.pol.Grants was written", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user