Skip to content

Replace native PE inspection with PEReader in StrongNameUtils#13731

Merged
JeremyKuhne merged 4 commits into
dotnet:mainfrom
JeremyKuhne:strongname-pereader
May 11, 2026
Merged

Replace native PE inspection with PEReader in StrongNameUtils#13731
JeremyKuhne merged 4 commits into
dotnet:mainfrom
JeremyKuhne:strongname-pereader

Conversation

@JeremyKuhne
Copy link
Copy Markdown
Member

Summary

GetAssemblyStrongNameLevel previously walked the PE image by hand via CreateFile / CreateFileMapping / MapViewOfFile / ImageNtHeader / ImageRvaToVa plus a 32/64-bit dispatch over IMAGE_NT_HEADERS{32,64} / IMAGE_COR20_HEADER structs. The whole approach exists only to read CorHeader.StrongNameSignatureDirectory plus CorFlags.StrongNameSigned — which System.Reflection.PortableExecutable already gives us in ~20 lines.

PEReader is cross-platform and is already a dependency of Tasks. Switch to it and delete the now-unused native interop.

Changes

  • src/Tasks/StrongNameUtils.cs: rewrite GetAssemblyStrongNameLevel using PEReader / CorHeader / CorFlags. Cross-platform; no unsafe needed.
  • src/Tasks/NativeMethods.cs: drop now-unused interop
    • [DllImport]: CreateFile, GetFileType, CloseHandle, CreateFileMapping, MapViewOfFile, UnmapViewOfFile, dbghelp.ImageNtHeader, dbghelp.ImageRvaToVa
    • constants: GENERIC_READ, PAGE_READONLY, FILE_MAP_READ, FILE_TYPE_DISK, IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC, IMAGE_DIRECTORY_ENTRY_COMHEADER, COMIMAGE_FLAGS_STRONGNAMESIGNED, InvalidIntPtr
    • structs: IMAGE_FILE_HEADER, IMAGE_DATA_DIRECTORY, IMAGE_OPTIONAL_HEADER32, IMAGE_OPTIONAL_HEADER64, IMAGE_NT_HEADERS32, IMAGE_NT_HEADERS64, IMAGE_COR20_HEADER

Stats

~260 lines of native interop removed. Net diff: +16 / -352.

Validation

  • MSBuild.Dev.slnf (net10 + net472): 0 warnings, 0 errors
  • MSBuild.SourceBuild.slnf /p:DotNetBuildSourceOnly=true: 0 warnings, 0 errors

This unblocks one of the AnyCPU limitations called out in the CsWin32 migration thread: dbghelp ImageNtHeader cannot be CsWin32-generated for AnyCPU because the metadata is architecture-specific. With PEReader we no longer need it.

GetAssemblyStrongNameLevel previously walked the PE image by hand via
CreateFile / CreateFileMapping / MapViewOfFile / ImageNtHeader / ImageRvaToVa
plus a 32/64-bit dispatch over IMAGE_NT_HEADERS{32,64} / IMAGE_COR20_HEADER
structs. The whole approach exists only to read
CorHeader.StrongNameSignatureDirectory plus CorFlags.StrongNameSigned, which
System.Reflection.PortableExecutable already gives us in ~20 lines.

PEReader is cross-platform and is already a dependency of Tasks. Switch to
it and delete the now-unused native interop:

- Tasks/NativeMethods.cs: drop [DllImport] CreateFile / GetFileType /
  CloseHandle / CreateFileMapping / MapViewOfFile / UnmapViewOfFile,
  dbghelp ImageNtHeader / ImageRvaToVa, the IMAGE_* PE constants
  (GENERIC_READ, PAGE_READONLY, FILE_MAP_READ, FILE_TYPE_DISK,
  IMAGE_NT_OPTIONAL_HDR* magics, IMAGE_DIRECTORY_ENTRY_COMHEADER,
  COMIMAGE_FLAGS_STRONGNAMESIGNED), InvalidIntPtr, and the NT header
  structs (IMAGE_FILE_HEADER, IMAGE_DATA_DIRECTORY,
  IMAGE_OPTIONAL_HEADER32/64, IMAGE_NT_HEADERS32/64, IMAGE_COR20_HEADER).

~260 lines of native interop removed; result works on every platform without
FEATURE_WINDOWSINTEROP.
Copilot AI review requested due to automatic review settings May 11, 2026 16:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes strong-name state detection in MSBuild Tasks by replacing Windows-only native PE inspection (CreateFile/MapViewOfFile/dbghelp) with System.Reflection.PortableExecutable.PEReader, improving portability and removing unsafe/native interop dependencies.

Changes:

  • Rewrote StrongNameUtils.GetAssemblyStrongNameLevel to use PEReader/CorHeader/CorFlags instead of manual PE walking.
  • Removed now-dead native interop constants/structs/PInvokes from NativeMethods.cs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Tasks/StrongNameUtils.cs Switches strong-name detection to PEReader and simplifies the logic/error handling.
src/Tasks/NativeMethods.cs Deletes unused PE-parsing-related native interop after the PEReader migration.

Comment thread src/Tasks/StrongNameUtils.cs Outdated
Comment thread src/Tasks/StrongNameUtils.cs
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert MSBuild Review — PR #13731: Replace P/Invoke with PEReader in StrongNameUtils

Excellent cleanup. This PR replaces ~200 lines of unsafe Windows-only P/Invoke (kernel32 file mapping, dbghelp PE parsing, manual struct marshaling) with ~25 lines of clean, cross-platform managed code using System.Reflection.PortableExecutable.PEReader. The corresponding dead code in NativeMethods.cs (8 P/Invoke declarations, 7 structs, and 4 constants) is correctly removed with no remaining references.

# Dimension Verdict
1 Backwards Compatibility 🟡 1 MODERATE
4 Test Coverage 🟡 1 MODERATE
22 Correctness i️ 1 NIT

✅ 21/24 dimensions clean.

Key findings:

  • Backwards CompatibilityStrongNameLevel.UnknownStrongNameLevel.None for non-.NET PE files (no COR header). This is semantically more correct but is a behavioral change. Practical impact is negligible since the only caller (AxTlbBaseReference.SigningRequirementsMatchExistingWrapper) deals with COM wrappers which are managed assemblies.
  • Test Coverage — No tests exist for GetAssemblyStrongNameLevel (pre-existing debt). The rewrite is a good opportunity to add coverage in a follow-up.
  • Correctness — Old code checked both VirtualAddress == 0 and Size == 0; new code only checks Size == 0. Equivalent for well-formed PEs.

Strengths:

  • Eliminates 8 P/Invoke declarations to kernel32.dll and dbghelp.dll — no longer Windows-only
  • Removes unsafe pointer arithmetic and Marshal.PtrToStructure — reduced attack surface
  • Exception handling is more precise: IOException/UnauthorizedAccessException for I/O failures, BadImageFormatException for corrupt PEs
  • Clean using declaration pattern with proper resource disposal
  • Removed #endregion/#region pair is correctly balanced in the resulting file

Note

🔒 Integrity filter blocked 1 item

The following item were blocked because they don't meet the GitHub integrity level.

  • #13731 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Expert Code Review (on open) for issue #13731 · ● 5.5M

Comment thread src/Tasks/StrongNameUtils.cs
Comment thread src/Tasks/StrongNameUtils.cs Outdated
Comment thread src/Tasks/StrongNameUtils.cs
…gnature VA

- Return StrongNameLevel.Unknown when CorHeader is null (native / non-managed
  image), matching the previous behavior. The old code returned Unknown when
  ImageNtHeader produced no COR20 RVA; the PEReader rewrite was conflating
  that case with None.
- Treat StrongNameSignatureDirectory as 'no signature' when either the RVA
  or the Size is zero, matching the previous (VA == 0 || Size == 0) check.
@JeremyKuhne
Copy link
Copy Markdown
Member Author

JeremyKuhne commented May 11, 2026

Addressed in 56ed289:

  • Non-managed PE (CorHeader is null) now returns StrongNameLevel.Unknown to match the previous behavior (the old code returned Unknown when ImageNtHeader produced no COR20 RVA).
  • The strong-name signature check is now RelativeVirtualAddress == 0 || Size == 0, matching the previous VA == 0 || Size == 0 check.

On tests: investigating using existing files on the system to get some coverage.

Covers all five classification outcomes the refactor cares about:

- FullySigned: BCL (mscorlib).
- DelaySigned: emitted at test time via AssemblyBuilder with a real
  public key attached and [assembly: AssemblyDelaySign(true)], producing
  a managed PE with a populated StrongNameSignatureDirectory and
  CorFlags.StrongNameSigned cleared.
- None: emitted at test time via AssemblyBuilder with no public key,
  producing a managed PE with an empty StrongNameSignatureDirectory.
- Unknown: native PE (kernel32.dll, Windows only), non-existent file,
  empty file, garbage bytes, and null input.

Gated on NETFRAMEWORK to match StrongNameUtils itself.
@JeremyKuhne
Copy link
Copy Markdown
Member Author

Failure was #13546

Co-authored-by: Jeremy Kuhne <jeremy.kuhne@microsoft.com>
Copy link
Copy Markdown
Member

@rainersigwald rainersigwald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the simplification/deletion. I am slightly paranoid though--do you think there's any reason to keep the old code under a changewave to allow opting out of the new codepath, in case there's any observable difference?

Also, will this cause a new assembly load? That's caused us problems in VS before . . .

Comment thread src/Tasks.UnitTests/StrongNameUtils_Tests.cs
@JeremyKuhne
Copy link
Copy Markdown
Member Author

do you think there's any reason to keep the old code

I don't think so. The PEReader package is heavily used so I'm pretty confident in it. https://www.nuget.org/packages/System.Reflection.Metadata

Also, will this cause a new assembly load?

It is already a dependency for this assembly, and I see the PEReader assembly loaded by VS with a simple project when I attach.

@JeremyKuhne
Copy link
Copy Markdown
Member Author

Error was #13732

@JeremyKuhne JeremyKuhne enabled auto-merge (squash) May 11, 2026 22:54
@JeremyKuhne JeremyKuhne merged commit b366b40 into dotnet:main May 11, 2026
10 checks passed
@JeremyKuhne JeremyKuhne deleted the strongname-pereader branch May 11, 2026 23:54
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.

4 participants