ASLR incompatibility with Cygwin binaries

Jakob Bohm jb-cygwin@wisemo.com
Sat Jul 11 04:25:56 GMT 2026


On 10/07/2026 17:20, Lee via Cygwin wrote:
> On Fri, Jul 10, 2026 at 8:31 AM Nikhil Sabharwal via Cygwin
> <cygwin@cygwin.com> wrote:
>> To the developers & maintainers of cygwin
>>
>> I wanted to reach out regarding Cygwin and its compatibility with Windows exploit protection mechanisms, specifically ASLR. My current understanding is that MSYS2 has difficulties supporting this feature due to limitations inherited from Cygwin. Certain functionality within the compatibility layer appears to rely on a fixed address layout, causing binaries with ASLR enabled to fail with error messages such as:
>>
>> *** fatal error - cygheap base mismatch detected - 0xD5F410/0xE9F410
>> These are the results I observe when running binaries built with the DYNAMICBASE flag enabled.
> This seems to work around the problem.  Note: I copied the solution
> from stackoverflow with no real understanding on my part, so I can't
> explain what's going on
>
> LDFLAGS="${LDFLAGS} -Wl,--dynamicbase,--export-all-symbols"
> # https://en.wikipedia.org/wiki/Address_space_layout_randomization
> # https://stackoverflow.com/questions/24283918/how-can-i-enable-aslr-dep-and-safeseh-on-an-exe-in-codeblocks-using-mingw
> #   ASLR with gcc has a problem: -Wl,--dynamicbase doesn't emit the
> necessary relocation table.
> #   As a workaround, you can pass -Wl,--dynamicbase,--export-all-symbols
> #   NOTE: you can't have both this and profiling (cflags='-pg') enabled!
>

Telling GCC to "export" all symbols doesn't really do anything relevant
except abuse a side effect of how GCC implements that option.

Note: This gcc/binutils bug mostly traces back to the gcc/binutils team
previously misunderstanding the PE file format design, and thus trying to
shoehorn it into concepts from a.out and other old POSIX file formats.

The PE file format combines 4 INDEPENDENT and OVERLAPPING types of 
conceptually
simple relocations:

1. COFF .o files (alone or in .a files) can contain any relocation 
imaginable
   with any level of complexity.   Via clever combinations of the 
available COFF
   relocation types and section flags, any language feature (such as C++
   templates or whatever may be needed for newer languages) can be expressed
   such that any standard PE linker will pick the correct .o files from .a
   libraries, build up internal tables and reduce the combined EXE or 
DLL to do
   everything in terms of the 3 simpler relocation types.  Among the things
   that can be done with this is to do things like reference the in memory
   address of the PE file headers (which is always memory mapped just before
   the first declared address space, as the entire PE file is 
essentially just
   mmap()-ed into the process address space with slight adjustments of 
memory
   protection according to the header).

2. True PIC code addresses symbols elsewhere in the PE file relative to the
   instruction pointer (with the linker doing the precalculations as .o 
files
   are combined).  For x86_32, this is mostly used with the relative jump
   instructions such as call, jmp and conditional jump.  If necessary, 
code can
   use well known trickery to copy the instruction pointer into a general
   register that can be referenced in the address fields of other 
instructions.
   For x86_64 and ARM, the instruction pointer itself can be referenced
   directly.  For some old RISC architectures like PowerPC, all but a few
   lines of assembly code will have the actual load address loaded into a
   specific general register dictated by the calling convention and calls
   between PE files use special instruction sequences to load that register.
    None of this involves a Unix-derived "PLT" table or other such
   complications.  The Compiler generates instruction sequences that
   reference the target symbol directly using appropriate addressing modes,
   the assembler generates symbol references and constant offsets based on
   instruction sizes (for example jump relative to (__foo - (address of the
   field set to __foo) - (how much the instruction adds to the field to
   choose the jump destination)), the linker does the subtractions leaving
   behind only the constant difference between where this instruction is
   and where __foo is.  Loading the PE file at a different offset (due to
   address space conflicts or ASLR, it doesn't matter) will cancel out
   with itself, keeping the code position independent.

3. "Base relocations" in PE files (DLL, EXE, EFI, Driver or anything else)
   are a sorted list of all the locations in the file that will need to be
   adjusted if the file is loaded at a memory offset which is different from
   the "preferred" address listed in the file header.  However the actual
   load address will always be a multiple of the "section alignment" power
   of 2 listed in the file header.
    It is the job of the linker (GNU ln or MS link) to reduce all internal
   relocation needs to the very limited number of relocation types that fit
   in this table format for the target architecture (x86_32 and x86_64 only
   support adding the difference between actual and preferred load address
   to pointer sized unaligned places in the image, other architectures have
   additional types to deal with instructions that load addresses in
   High/Low halves).
    These relocations are not done at load time, but are instead done
   inside the memory mapping machinery in the kernel as each memory page
   is (re)loaded from the PE file, hence why the format must be followed
   exactly to allow the kernel to find the table entries via random access.
   "Base relocations" are the mechanism responsible for ASLR compatibility
   and this part of the format is unchanged since 1992 (except for the
   introduction of new CPU architectures and a single informative bit
   telling the loader that ASLR is not a problem).

4. The "IAT" is a single array (not a list of arbitrary code locations as
   some GNU folk think) that is almost a PLT, which is filled at load time
   with the absolute addresses of symbols from other PE files. Compiler
   generated code will reference the IAT entry pointing to imported symbol
   _foo as __imp__foo and use CPU addressing modes that use that pointer
   like any other read only pointer variable, for example if _foo is a
   function, the compiler emits an instruction to call (*__imp__foo),
   using PIC or base relocations to reach __imp__foo.  If _foo is a
   variable the compiler emits code that references (*__imp__foo) in
   similar ways.  If _foo is a function and the compiler accidentally
   emits a normal internal call to symbol _foo, the linker generates a
   tiny stub named _foo which forwards the call to *__imp__foo. That
   stub trick won't work if _foo is a variable, and the linker is
   supposed to refuse linking this mistake into a PE file.
    In C/C++/Objective C, marking an external symbol _foo in a header
   or code with __attribute__((dllimport)) tells the compiler that
   every syntactical reference to the symbol __foo is really a
   reference to the hidden pointer __imp__foo when generating code.


Enjoy

Jakob
-- 
Jakob Bohm, CIO, Partner, WiseMo A/S.  https://www.wisemo.com
Transformervej 29, 2860 Søborg, Denmark.  Direct +45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded



More information about the Cygwin mailing list