diff options
| author | Jason A. Donenfeld <Jason@zx2c4.com> | 2026-03-19 00:28:04 +0100 |
|---|---|---|
| committer | Jason Donenfeld <Jason@zx2c4.com> | 2026-03-24 09:00:02 -0700 |
| commit | 341b5e2c0261cc059b157f1c7a2a2c4d1f417f0d (patch) | |
| tree | f10166b18211864ae4f94cd17b72daeb77c91639 | |
| parent | abd44cbe615ecea5e4bd8e6d1bb7be63d1f4b2d6 (diff) | |
| download | go-341b5e2c0261cc059b157f1c7a2a2c4d1f417f0d.tar.xz | |
cmd/link: raise minimum windows version to 10
The minimum Windows version has been 10 for a few releases, but the PE
headers weren't updated. Windows sometimes can use these in determining
what kind of subsystem compatibility hacks to apply, which of course we
don't want now, since Go targets Windows 10. This also causes older OSes
to refuse to run the executables, rather than having them crash in some
undefined way.
This isn't trivial to do, because subsystem ≥ 10.0 means that the
Windows loader expects to see either _load_config_used.SecurityCookie
set to the initial magic value, or for IMAGE_GUARD_SECURITY_COOKIE_UNUSED
to be set. Go obviously isn't making use of these features, and neither
does clang/gcc for that matter; libssp doesn't even use SecurityCookie.
Rather, it's exclusively for MSVC's /GS protection. So it seems like the
proper thing to do is signal to the OS that it doesn't need to
initialize SecurityCookie. This check lives in ntdll!LdrInitSecurityCookie.
So, add the _load_config_used structure to the right PE section and give
it the right flag. This lets the Windows 10-marked binaries actually
run.
Change-Id: I91887073c7ad01aeb0237906aafa4ea5574ac8fa
Reviewed-on: https://go-review.googlesource.com/c/go/+/756680
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Jason Donenfeld <Jason@zx2c4.com>
Reviewed-by: Jason Donenfeld <Jason@zx2c4.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
| -rw-r--r-- | src/cmd/link/internal/ld/pe.go | 165 | ||||
| -rw-r--r-- | src/runtime/cgo/gcc_libinit_windows.c | 23 | ||||
| -rw-r--r-- | src/runtime/syscall_windows_test.go | 15 |
3 files changed, 201 insertions, 2 deletions
diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 8184a32f83..3b97061aa0 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -151,9 +151,139 @@ const ( IMAGE_REL_BASED_DIR64 = 10 ) +// IMAGE_LOAD_CONFIG_DIRECTORY64.GuardFlags and IMAGE_LOAD_CONFIG_DIRECTORY32.GuardFlags +// values. These can be combined together. const ( - PeMinimumTargetMajorVersion = 6 - PeMinimumTargetMinorVersion = 1 + IMAGE_GUARD_CF_INSTRUMENTED = 0x00000100 // Module performs control flow integrity checks using system-supplied support + IMAGE_GUARD_CFW_INSTRUMENTED = 0x00000200 // Module performs control flow and write integrity checks + IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT = 0x00000400 // Module contains valid control flow target metadata + IMAGE_GUARD_SECURITY_COOKIE_UNUSED = 0x00000800 // Module does not make use of the /GS security cookie + IMAGE_GUARD_PROTECT_DELAYLOAD_IAT = 0x00001000 // Module supports read only delay load IAT + IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION = 0x00002000 // Delayload import table in its own .didat section (with nothing else in it) that can be freely reprotected + IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT = 0x00004000 // Module contains suppressed export information + IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION = 0x00008000 // Module enables suppression of exports + IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT = 0x00010000 // Module contains longjmp target information + IMAGE_GUARD_RF_INSTRUMENTED = 0x00020000 // Module contains return flow instrumentation and metadata + IMAGE_GUARD_RF_ENABLE = 0x00040000 // Module requests that the OS enable return flow protection + IMAGE_GUARD_RF_STRICT = 0x00080000 // Module requests that the OS enable return flow protection in strict mode + IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK = 0xF0000000 // Stride of Guard CF function table encoded in these bits (additional count of bytes per element) + IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT = 28 // Shift to right-justify Guard CF function table stride +) + +type IMAGE_LOAD_CONFIG_CODE_INTEGRITY struct { + Flags uint16 + Catalog uint16 + CatalogOffset uint32 + Reserved uint32 +} + +type IMAGE_LOAD_CONFIG_DIRECTORY32 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity IMAGE_LOAD_CONFIG_CODE_INTEGRITY + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint32 + VolatileMetadataPointer uint32 + GuardEHContinuationTable uint32 + GuardEHContinuationCount uint32 + GuardXFGCheckFunctionPointer uint32 + GuardXFGDispatchFunctionPointer uint32 + GuardXFGTableDispatchFunctionPointer uint32 + CastGuardOsDeterminedFailureMode uint32 + GuardMemcpyFunctionPointer uint32 +} + +type IMAGE_LOAD_CONFIG_DIRECTORY64 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity IMAGE_LOAD_CONFIG_CODE_INTEGRITY + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint64 + VolatileMetadataPointer uint64 + GuardEHContinuationTable uint64 + GuardEHContinuationCount uint64 + GuardXFGCheckFunctionPointer uint64 + GuardXFGDispatchFunctionPointer uint64 + GuardXFGTableDispatchFunctionPointer uint64 + CastGuardOsDeterminedFailureMode uint64 + GuardMemcpyFunctionPointer uint64 +} + +const ( + PeMinimumTargetMajorVersion = 10 + PeMinimumTargetMinorVersion = 0 ) // DOS stub that prints out @@ -1141,6 +1271,29 @@ func Peinit(ctxt *Link) { ctxt.loader.SetAttrSpecial(sb.Sym(), true) ctxt.loader.SetAttrLocal(sb.Sym(), true) } + + // The _load_config_used symbol is required to be present on modern + // Windows. We later wire this up to the PE data directory. + sb := ctxt.loader.CreateSymForUpdate("_load_config_used", 0) + sb.SetType(sym.SRODATA) + sb.SetAlign(int32(ctxt.Arch.PtrSize)) + var buf bytes.Buffer + if pe64 { + lc := IMAGE_LOAD_CONFIG_DIRECTORY64{ + Size: uint32(binary.Size(&IMAGE_LOAD_CONFIG_DIRECTORY64{})), + GuardFlags: IMAGE_GUARD_SECURITY_COOKIE_UNUSED, + } + binary.Write(&buf, binary.LittleEndian, &lc) + } else { + lc := IMAGE_LOAD_CONFIG_DIRECTORY32{ + Size: uint32(binary.Size(&IMAGE_LOAD_CONFIG_DIRECTORY32{})), + GuardFlags: IMAGE_GUARD_SECURITY_COOKIE_UNUSED, + } + binary.Write(&buf, binary.LittleEndian, &lc) + } + sb.SetData(buf.Bytes()) + sb.SetSize(int64(buf.Len())) + ctxt.loader.SetAttrReachable(sb.Sym(), true) } HEADR = PEFILEHEADR @@ -1711,6 +1864,14 @@ func asmbPe(ctxt *Link) { ro.checkSegment(&Segrodata) pefile.rdataSect = ro + // This should have been added in Peinit and by now is part of the + // just-written .rdata section. + s := ctxt.loader.Lookup("_load_config_used", 0) + if s != 0 { + pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress = uint32(ctxt.loader.SymValue(s) - PEBASE) + pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size = uint32(ctxt.loader.SymSize(s)) + } + var d *peSection if ctxt.LinkMode != LinkExternal { d = pefile.addSection(".data", int(Segdata.Length), int(Segdata.Filelen)) diff --git a/src/runtime/cgo/gcc_libinit_windows.c b/src/runtime/cgo/gcc_libinit_windows.c index 78b32254cf..ed30878c3c 100644 --- a/src/runtime/cgo/gcc_libinit_windows.c +++ b/src/runtime/cgo/gcc_libinit_windows.c @@ -14,6 +14,29 @@ #include "libcgo.h" +#define IMAGE_GUARD_SECURITY_COOKIE_UNUSED 0x00000800 +// With modern mingw, we can use the normal struct: +// +// const IMAGE_LOAD_CONFIG_DIRECTORY _load_config_used = { +// .Size = sizeof(_load_config_used), +// .GuardFlags = IMAGE_GUARD_SECURITY_COOKIE_UNUSED +// }; +// +// But we support older toolchains, so instead, fix the offsets: +#ifdef _WIN64 +const ULONGLONG _load_config_used[40] = { + sizeof(_load_config_used), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + IMAGE_GUARD_SECURITY_COOKIE_UNUSED +}; +#else +const DWORD _load_config_used[48] = { + sizeof(_load_config_used), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + IMAGE_GUARD_SECURITY_COOKIE_UNUSED +}; +#endif + static volatile LONG runtime_init_once_gate = 0; static volatile LONG runtime_init_once_done = 0; diff --git a/src/runtime/syscall_windows_test.go b/src/runtime/syscall_windows_test.go index 77092d8fbf..437c723d51 100644 --- a/src/runtime/syscall_windows_test.go +++ b/src/runtime/syscall_windows_test.go @@ -8,6 +8,7 @@ import ( "fmt" "internal/abi" "internal/race" + "internal/syscall/windows" "internal/syscall/windows/sysdll" "internal/testenv" "io" @@ -1226,6 +1227,20 @@ var ( procSetEvent = modkernel32.NewProc("SetEvent") ) +func TestTrueVersion(t *testing.T) { + ver, err := syscall.GetVersion() + if err != nil { + t.Fatalf("GetVersion failed: %v", err) + } + wantMajor, wantMinor, wantBuild := windows.Version() + major := uint32(byte(ver)) + minor := uint32(uint8(ver >> 8)) + build := uint32(uint16(ver >> 16)) + if major != wantMajor || minor != wantMinor || build != wantBuild { + t.Errorf("GetVersion = %d.%d (Build %d), want %d.%d (Build %d)", major, minor, build, wantMajor, wantMinor, wantBuild) + } +} + func createEvent() (syscall.Handle, error) { r0, _, e0 := syscall.Syscall6(procCreateEvent.Addr(), 4, 0, 0, 0, 0, 0, 0) if r0 == 0 { |
