Skip to content

Fix bit-reversed security descriptor control flag constants (#92)#93

Merged
p0dalirius merged 1 commit into
mainfrom
bugfix-control-bits-wire-order
Jun 2, 2026
Merged

Fix bit-reversed security descriptor control flag constants (#92)#93
p0dalirius merged 1 commit into
mainfrom
bugfix-control-bits-wire-order

Conversation

@p0dalirius

Copy link
Copy Markdown
Collaborator

Linked Issue

Closes #92

Root Cause

The NT_SECURITY_DESCRIPTOR_CONTROL_* constants in securitydescriptor/control/NtSecurityDescriptorControl.go were numbered following the MS-DTYP packet-diagram convention, where "bit 0" is the most-significant bit. That convention is the 16-bit reversal of the little-endian masks actually serialized on the wire and defined in winnt.h (the SE_* values). For example the library defined SR (Self-Relative) = 0x0001 and DP (DACL Present) = 0x2000, whereas the wire/winnt.h values are SE_SELF_RELATIVE = 0x8000 and SE_DACL_PRESENT = 0x0004. All 16 constants were reversed in the same way.

Because the Control.RawValue is stored and serialized verbatim, this meant the value composed via these constants was emitted directly to the wire in its reversed form.

Fix Description

Redefine the 16 constants to the correct SE_* wire masks (OD=0x0001 … SR=0x8000). Every consumer — SDDL parsing (FromSDDLString), SDDL serialization (ToSDDLString), flag interpretation (Unmarshal/HasControl), and the flag-name maps — references the symbolic names, so marshal, unmarshal, and both SDDL directions are corrected together by this single change. No call site hardcodes a raw control mask.

The binary Unmarshal/Marshal path copies RawValue byte-for-byte, so the existing binary round-trip dataset tests are unaffected; only the meaning assigned to each bit (and the value SDDL parsing composes) changes.

How Verified

  • Runtime: FromSDDLString("O:SYG:SYD:(A;;FA;;;BA)(A;;FA;;;SY)") then Marshal() now yields a Control word of 0x8004 (SE_SELF_RELATIVE | SE_DACL_PRESENT); before the fix it was 0x2001, the exact bit-reversal, which NT servers reject with ERROR_INVALID_PARAMETER.
  • Tests: go test ./... passes, including the existing binary round-trip suite over real Windows SD hex fixtures.

Test Coverage

Added:

  • securitydescriptor/control/NtSecurityDescriptorControl_test.go::TestNtSecurityDescriptorControl_WireValues — asserts each constant equals its SE_* wire mask.
  • securitydescriptor/NtSecurityDescriptor_sddl_wire_test.go::TestFromSDDLString_MarshalWireControl — asserts the end-to-end SDDL → marshal Control word is 0x8004.

Scope of Change

  • Files changed: securitydescriptor/control/NtSecurityDescriptorControl.go, securitydescriptor/control/NtSecurityDescriptorControl_test.go, securitydescriptor/NtSecurityDescriptor_sddl_wire_test.go
  • Submodule pointer updated: no
  • Behavioral changes outside the bug fix: none

Risk and Rollout

The corrected Control word changes the bytes emitted by Marshal() for any descriptor with control flags set, and changes the flag strings reported for parsed descriptors. This is the intended correction (previous output was non-interoperable with Windows). Any downstream code that compensated for the old reversed output — such as a manual bit-reversal workaround applied after Marshal() — must drop that workaround once this lands.

The NT_SECURITY_DESCRIPTOR_CONTROL_* constants were defined following the
MS-DTYP packet-diagram convention where bit 0 is the most-significant bit,
which is the 16-bit reversal of the actual little-endian wire masks used by
Windows (winnt.h SE_* values). As a result every marshaled Control word was
wrong (e.g. a self-relative DACL-present descriptor serialized as 0x2001
instead of 0x8004), causing NT servers to reject the descriptor, and parsed
Windows descriptors had their flags misinterpreted.

Redefine the constants to the correct SE_* wire masks. All consumers use the
symbolic names, so marshal, unmarshal, SDDL parsing/serialization, and flag
interpretation are corrected together.
@p0dalirius p0dalirius self-assigned this Jun 2, 2026
@p0dalirius p0dalirius merged commit 7873c9f into main Jun 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security descriptor control flag constants are bit-reversed, producing malformed wire-format Control fields

1 participant