Skip to content

Fix NtSecurityDescriptor.Marshal emitting offsets for an unset Owner/Group (#82)#83

Merged
p0dalirius merged 1 commit into
mainfrom
bugfix-ntsd-marshal-empty-owner-group
Jun 1, 2026
Merged

Fix NtSecurityDescriptor.Marshal emitting offsets for an unset Owner/Group (#82)#83
p0dalirius merged 1 commit into
mainfrom
bugfix-ntsd-marshal-empty-owner-group

Conversation

@p0dalirius

Copy link
Copy Markdown
Collaborator

Linked Issue

Closes #82

Root Cause

NtSecurityDescriptor.Marshal gated Owner/Group serialization only on ntsd.Owner != nil / ntsd.Group != nil. NewSecurityDescriptor() initializes both fields to &identity.Identity{} — a non-nil but unset Identity whose SID is the zero value. Marshalling such a descriptor therefore wrote non-zero OffsetOwner/OffsetGroup and an 8-byte all-zero SID for each, so the output claimed an owner and group of S-0-0-0, a SID with revision level 0 that is invalid per MS-DTYP (the only valid SID revision is 1). The SACL/DACL blocks already gate on len(Entries) > 0, but the Owner/Group blocks had no equivalent "is this actually set" check.

Fix Description

The Owner and Group blocks now additionally require SID.RevisionLevel != 0 before emitting the component, and set the corresponding offset to 0 otherwise — mirroring the presence checks used for the SACL and DACL. RevisionLevel is the canonical marker of a populated SID: both SID.FromString and SID.Unmarshal set it to a non-zero value, while the zero-value Identity has 0. This is the minimal change and does not touch the parsing path.

How Verified

  • Runtime (before): NewSecurityDescriptor().Marshal() produced 36 bytes with OffsetOwner=20, OffsetGroup=28, each pointing at a zero SID that parsed back as S-0-0-0.
  • Runtime (after): the same call produces 20 bytes (header only) with OffsetOwner=0, OffsetGroup=0, and round-trips to Owner == nil, Group == nil.
  • Tests: added TestNtSecurityDescriptor_Marshal_UnsetOwnerGroup.
  • go build ./... and go test ./... pass; existing tests that set Owner/Group via FromString (RevisionLevel = 1) are unaffected.

Test Coverage

  • Added: securitydescriptor/NtSecurityDescriptor_test.go -> TestNtSecurityDescriptor_Marshal_UnsetOwnerGroup.

Scope of Change

  • Files changed: securitydescriptor/NtSecurityDescriptor.go, securitydescriptor/NtSecurityDescriptor_test.go
  • Submodule pointer updated: no
  • Behavioral changes outside the bug fix: none

Risk and Rollout

Low blast radius. A populated owner/group (RevisionLevel = 1) marshals exactly as before; only the unset zero-value Identity, which previously serialized to an invalid SID, is now correctly represented as absent. Safe to merge directly.

@p0dalirius p0dalirius self-assigned this Jun 1, 2026
@p0dalirius p0dalirius merged commit c0b0235 into main Jun 1, 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.

1 participant