Skip to content

Reject NtSecurityDescriptor component offsets inside the header (#90)#91

Merged
p0dalirius merged 1 commit into
mainfrom
enhancement-ntsd-offset-validation
Jun 1, 2026
Merged

Reject NtSecurityDescriptor component offsets inside the header (#90)#91
p0dalirius merged 1 commit into
mainfrom
enhancement-ntsd-offset-validation

Conversation

@p0dalirius

Copy link
Copy Markdown
Collaborator

Linked Issue

Closes #90

Motivation

NtSecurityDescriptor.Unmarshal validated each component offset only with Offset < len(RawBytes). With no lower bound, a non-zero offset pointing into the fixed 20-byte header (for example OffsetOwner = 4) was accepted and the header bytes were mis-parsed as a SID/ACL, producing garbage rather than an error. For a parser of untrusted security descriptors, an offset overlapping the header is structurally invalid and should be rejected. This complements the AclSize bound for ACLs (#78): together they confine each component to a valid region.

What Changed

  • Added the package constant ntSecurityDescriptorHeaderSize = 20 and used it for the existing RawBytesSize initialization as well as the new checks.
  • Each component guard in Unmarshal (Owner, Group, DACL, SACL) now requires Offset >= ntSecurityDescriptorHeaderSize && Offset < len(RawBytes); the error messages were updated to state the valid range.

Acceptance Criteria Check

  • A descriptor whose component offset is non-zero but less than 20 returns an error — TestNtSecurityDescriptor_Unmarshal_OffsetInsideHeader (sets OffsetOwner = 4).
  • Offset == 0 (component absent) is still skipped without error — unchanged outer != 0 guard; covered by the dataset involution tests, which include descriptors with absent components.
  • Well-formed descriptors parse unchanged — go test ./... green, including TestNtSecurityDescriptor_Involution over the real Windows/AD datasets; the new test also asserts the unmodified descriptor parses cleanly before corruption.
  • New test constructs a descriptor with an in-header offset and asserts an error — see above.

How Verified

  • Tests: securitydescriptor/NtSecurityDescriptor_offset_test.goTestNtSecurityDescriptor_Unmarshal_OffsetInsideHeader.
  • go build ./... and go test ./... pass.

Test Coverage

  • Added: the test above.

Scope of Change

  • Files changed: securitydescriptor/NtSecurityDescriptor.go, securitydescriptor/NtSecurityDescriptor_offset_test.go
  • Submodule pointer updated: no
  • Public API changes: none (behavioral hardening of Unmarshal; it may now return an error on malformed offsets it previously mis-parsed)
  • Behavioral changes outside the stated enhancement: none

Risk and Rollout

Low. Well-formed descriptors place every component at or past the 20-byte header, so valid input is unaffected (verified by the dataset involution test); only offsets that overlap the header now error. Safe to merge.

Notes

Related: #78 (bound ACL parsing by AclSize), #82 (Marshal no longer emits offsets for an unset Owner/Group). Full mutual-extent overlap checking between components is deferred as noted in the issue.

@p0dalirius p0dalirius self-assigned this Jun 1, 2026
@p0dalirius p0dalirius merged commit 7af1571 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