Any memory locations known to be ok to read across platforms? Doing a VM

These work on my 64-bit Linux, but some are likely not guaranteed to work:

julia> unsafe_load(Ptr{UInt8}(Int(0x00400000)))  # reading julia itself
0x7f

Is it up to ELF to use that location, and thus guaranteed to work? Except e.g. on 64-bit Windows? What's the corresponding location on Windows and macOS? Is there some intersection of addresses ok across platforms? Preferably 32-bit too?

julia> unsafe_load(Ptr{UInt8}(Int(0x277b7000)))  # start of the heap, likely not guaranteed to be stable across Julias, and 32-bit, and Windows...
0x00


julia> unsafe_load(Ptr{UInt8}(Int(0x7ffd42313000)))  # vdso, guaranteed on Linux, across libcs, but not 32-bit, then what?
0x7f

Find memory mappings Use /proc/self/maps (Linux), vmmap (macOS), or equivalent Windows tools

julia> readlines("/proc/self/maps")
218-element Vector{String}:
 "00400000-00401000 r--p 00000000" ⋯ 82 bytes ⋯ ".10.9+0.x64.linux.gnu/bin/julia"
..
 "7ffd42241000-7ffd42262000 rw-p " ⋯ 18 bytes ⋯ "                        [stack]"
 "7ffd4230f000-7ffd42313000 r--p " ⋯ 17 bytes ⋯ "                         [vvar]"
 "7ffd42313000-7ffd42315000 r-xp " ⋯ 17 bytes ⋯ "                         [vdso]"
 "ffffffffff600000-ffffffffff6010" ⋯ 21 bytes ⋯ "0 0                  [vsyscall]"

Can anyone run similar for me on macOS, i.e. vmmap, and on Windows, or if you simply know of safe addresses tell me.

The reason… I’m doing a VM for Julia, and it’s going to be obscure, will have implicit loads, I will do 2 implicit loads, and 3 more instruction, encoded into one byte, for 1.6 bits per instruction (best case).

All the ā€˜unsafe_load’ commands cause Julia to crash and the task to disappear in Windows 11.

1 Like

Thanks for checking. I also realized there’s no safe location even on Linux because of:

Why memory mappings (like [heap], [stack]) differ

1. Address Space Layout Randomization (ASLR)

  • This is the main reason.
  • ASLR randomizes the base addresses of memory regions (heap, stack, libraries, etc.) for each process.
  • It’s a security feature that helps prevent certain kinds of attacks (like buffer overflows leading to shellcode execution).

That said julia always maps in at 0x00400000 except it’s seemingly down to Julia 1.0 but not on Windows.

Which is unlike for e.g. cat where its binary address (and everything) in randomized:

shell> cat /proc/self/maps
562f08592000-562f08594000 r--p 00000000 08:02 108790662                  /usr/bin/cat
562f08594000-562f08598000 r-xp 00002000 08:02 108790662                  /usr/bin/cat
562f08598000-562f0859a000 r--p 00006000 08:02 108790662                  /usr/bin/cat
562f0859a000-562f0859b000 r--p 00007000 08:02 108790662                  /usr/bin/cat
562f0859b000-562f0859c000 rw-p 00008000 08:02 108790662                  /usr/bin/cat
562f39845000-562f39866000 rw-p 00000000 00:00 0                          [heap]

My next thought, can I map a page in at a known address (on Linux, but preferably on all platforms the same one). I think I can technically but would it likely interfere with Julia’s operation? Might Julia or libc ask for that address, or do programs usually just ask for pages and the kernel gives you a free address?

I’m not sure I can do this on Windows, or FreeBSD (or other more security obsessed BSD), but on some address at least on Windows, and fixed one on Linux (I’m just not sure if it’s advised in Julia, for fixed, even if I always can):

MAP_FIXED

          Don't interpret addr as a hint: place the mapping at
          exactly that address.  addr must be suitably aligned: for
          most architectures a multiple of the page size is
          sufficient; however, some architectures may impose
          additional restrictions.  If the memory region specified by
          addr and length overlaps pages of any existing mapping(s),
          then the overlapped part of the existing mapping(s) will be
          discarded.  If the specified address cannot be used, mmap()
          will fail.

          Software that aspires to be portable should use the
          MAP_FIXED flag with care, keeping in mind that the exact
          layout of a process's memory mappings is allowed to change
          significantly between Linux versions, C library versions,
          and operating system releases.  Carefully read the
          discussion of this flag in NOTES!

   MAP_FIXED_NOREPLACE (since Linux 4.17)
          This flag provides behavior that is similar to MAP_FIXED
          with respect to the addr enforcement, but differs in that
          MAP_FIXED_NOREPLACE never clobbers a preexisting mapped
          range.  If the requested range would collide with an
          existing mapping, then this call fails with the error
          EEXIST.  This flag can therefore be used as a way to
          atomically (with respect to other threads) attempt to map
          an address range: one thread will succeed; all others will
          report failure.

          Note that older kernels which do not recognize the
          MAP_FIXED_NOREPLACE flag will typically (upon detecting a
          collision with a preexisting mapping) fall back to a ā€œnon-
          MAP_FIXEDā€ type of behavior: they will return an address
          that is different from the requested address.  Therefore,
          backward-compatible software should check the returned
          address against the requested address.