Skip to content

Commit a189985

Browse files
committed
Make RightToSDDL reverse map deterministic for colliding mask values (#81)
1 parent b49f412 commit a189985

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

sddl/rights/rights.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package rights
22

33
import (
4+
"sort"
5+
46
ntsd_rights "github.com/TheManticoreProject/winacl/rights"
57
)
68

@@ -53,7 +55,23 @@ var RightToSDDL map[uint32]string
5355

5456
func init() {
5557
RightToSDDL = make(map[uint32]string, len(SDDLToRight))
56-
for sddl, right := range SDDLToRight {
57-
RightToSDDL[right] = sddl
58+
59+
// Several SDDL tokens can share the same access-mask value (e.g. "KR"
60+
// KEY_READ and "KX" KEY_EXECUTE are both 0x00020019). Inverting the map by
61+
// ranging over it directly would pick a winner at random per run, because Go
62+
// randomises map iteration order. Iterate the tokens in sorted order and keep
63+
// the first (lexicographically smallest) token for each mask, so the reverse
64+
// lookup is deterministic and the canonical token is stable across runs.
65+
tokens := make([]string, 0, len(SDDLToRight))
66+
for sddl := range SDDLToRight {
67+
tokens = append(tokens, sddl)
68+
}
69+
sort.Strings(tokens)
70+
71+
for _, sddl := range tokens {
72+
right := SDDLToRight[sddl]
73+
if _, exists := RightToSDDL[right]; !exists {
74+
RightToSDDL[right] = sddl
75+
}
5876
}
5977
}

sddl/rights/rights_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package rights_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheManticoreProject/winacl/sddl/rights"
7+
)
8+
9+
// TestRightToSDDL_CollisionDeterministic verifies that the reverse map resolves
10+
// the KR/KX mask collision (both 0x00020019) to a stable canonical token.
11+
func TestRightToSDDL_CollisionDeterministic(t *testing.T) {
12+
const keyReadExecute = 0x00020019
13+
14+
got, ok := rights.RightToSDDL[keyReadExecute]
15+
if !ok {
16+
t.Fatalf("RightToSDDL[0x%08x] missing", keyReadExecute)
17+
}
18+
// "KR" sorts before "KX", so it is the deterministic winner.
19+
if got != "KR" {
20+
t.Errorf("RightToSDDL[0x%08x] = %q, want %q", keyReadExecute, got, "KR")
21+
}
22+
}
23+
24+
// TestRightToSDDL_IsValidInverse verifies that every mask present in the reverse
25+
// map resolves to a token that maps back to that same mask in SDDLToRight, i.e.
26+
// the reverse map is a valid inverse for every entry.
27+
func TestRightToSDDL_IsValidInverse(t *testing.T) {
28+
for mask, token := range rights.RightToSDDL {
29+
if rights.SDDLToRight[token] != mask {
30+
t.Errorf("RightToSDDL[0x%08x] = %q, but SDDLToRight[%q] = 0x%08x", mask, token, token, rights.SDDLToRight[token])
31+
}
32+
}
33+
34+
// Every forward mask must be representable in the reverse map.
35+
for token, mask := range rights.SDDLToRight {
36+
if _, ok := rights.RightToSDDL[mask]; !ok {
37+
t.Errorf("mask 0x%08x (from token %q) has no entry in RightToSDDL", mask, token)
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)