Skip to content

DiskUtils.TryGetReparseDataBuffer reads a struct from unzeroed pooled memory and ignores the number of bytes returned #242

Closed

Description

@EgorBo

DiskUtils.TryGetReparseDataBuffer reads a SymbolicLinkReparseBuffer out of a buffer rented from ArrayPool<byte>.Shared:

byte[] buffer = ArrayPool<byte>.Shared.Rent(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
// ... DeviceIoControl(..., out _, ...)   // bytes returned are discarded
Span<byte> bufferSpan = new(buffer);
success = MemoryMarshal.TryRead(bufferSpan, out SymbolicLinkReparseBuffer reparseDataBuffer);

Two things are wrong here:

  1. Pooled arrays are not zeroed, and the number of bytes written by DeviceIoControl is ignored. The span covers the whole (larger-than-needed) array, so TryRead always succeeds, even when the driver returned fewer bytes than sizeof(SymbolicLinkReparseBuffer) (20 bytes). Reparse points with little or no payload leave the remaining fields filled with leftovers of whatever previously used the pooled array.
  2. SymbolicLinkReparseBuffer is a symlink-specific layout. Its Flags field only exists for symlinks. For junctions (mount points), and for other tags, the same four bytes are PathBuffer contents or arbitrary data.

No wrong result is observable today, because the only consumer, DiskUtils.IsJunction, reads just ReparseTag, which is always in the first four bytes of a successful response. But the method is a trap for the next person who reads another field. It reads uninitialized (stale) data and returns a struct whose fields can be garbage while the method reports success.

Recommendations

  • Capture lpBytesReturned and use new Span<byte>(buffer, 0, (int)bytesReturned), so that TryRead fails for a response that's too short.
  • Better: make the method return only what it can actually vouch for. Either return just the ReparseTag (that's all IsJunction needs), or expose a header-only struct (ReparseTag, ReparseDataLength, Reserved) and parse the tag-specific part per tag.
  • Rename or document the struct accordingly, so it's clear that Flags is valid only for IO_REPARSE_TAG_SYMLINK.

Activity

  1. ForNeVeR commented on Oct 25, 2026

    @ForNeVeR
    Owner

    In #246, we've migrated to GetFileInformationByHandleEx, so this is no longer a problem. We no longer use the array pool, and the current API usage pattern is much simpler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions