20

Suppose my non-root 32-bit app runs on a 64-bit system, all filesystems of which are mounted as read-only. The app creates an image of a 64-bit ELF in memory. But due to read-only filesystems it can't dump this image to a file to do an execve on. Is there still a supported way to launch a process from this image?

Note: the main problem here is to switch from 32-bit mode to 64-bit, not doing any potentially unreliable hacks. If this is solved, then the whole issue becomes trivial — just make a custom loader.

Ruslan
  • 3,290
  • 3
  • 28
  • 49
  • If you have a `tmpfs`, you could write the image there and execute it. `tmpfs` is backed entirely by memory. Not sure this fits your requirements, though. – alienth Sep 18 '15 at 06:40
  • @alienth to mount a `tmpfs` I'd need root privileges. – Ruslan Sep 18 '15 at 10:21
  • 1
    Ruslan: @alienth's suggestion is to keep a `tmpfs` filesystem permanently mounted. This doesn't let any of the read-only memory pages in your 64bit process be backed by the contents of the disk file holding the 32bit executable, though. Instead, everything it needs has to be copied. (Unless you have the 64bit program open and mmap after it starts.) Also, you'd need to make sure you clean up the tmpfs, to avoid upward-creeping memory usage. – Peter Cordes Sep 18 '15 at 17:39

3 Answers3

28

Yes, via memfd_create and fexecve:

int fd = memfd_create("foo", MFD_CLOEXEC);
// write your image to fd however you want
fexecve(fd, argv, envp);
  • 5
    Great. I've checked, this indeed works (see [this example code](https://dumpz.org/bRatbeMHKn7S) (the child simply spins increasing `rax`)). But a couple of comments: `memfd_create` first appears in Linux 3.17. Include ``, having defined `_GNU_SOURCE` (man page appears wrong about the include and doesn't mention the need for the define). – Ruslan Sep 03 '18 at 21:01
  • 3
    @Ruslan Good find. I just sent in a patch to add the _GNU_SOURCE requirement to the man page. (The header name is already fixed in git; the website just hasn't updated yet.) – Joseph Sible-Reinstate Monica Sep 03 '18 at 22:38
2

You're looking for something like "userland exec". Implementation here. Basically, this involves loading some position-independent code that has no external references into memory, and marking it executable. This position independent code removes the previously-running executable, and reloads. Sounds like you might have to modify the userland exec I wrote at least a little.

  • 1
    IDK if you can change from compat (32bit) mode to long (64bit) mode other than with `execve`. It's going to require a kernel call of some sort. `personality(2)` sounds like it might be the right thing, but it's for stuff like changing signal numbers to emulate a BSD or other non-Linux system call ABI, I think. http://man7.org/linux/man-pages/man2/personality.2.html – Peter Cordes Sep 18 '15 at 17:44
  • 1
    The only way to switch from 32bit to 64bit mode I've come up with is [quite hacky](http://stackoverflow.com/a/32384358/673852). One can't even guarantee that it won't break in a newer kernel. Thus the implementation you point to doesn't seem workable in my use case. And, even if I do use the above mentioned hack, the process still looks like 32 bit to the kernel: e.g. `ptrace`'ing it with `GETREGSET` with `NT_PRSTATUS` gives 32-bit regset. – Ruslan Sep 18 '15 at 18:42
  • As far as I can tell this doesn't close `CLOEXEC` fds – Christopher Monsanto Feb 21 '19 at 20:18
0

The comment by user732 and the link is outdated. But the gist is right; you need a "userland exec" implementation of which there are several. One that is useful in scenario's where you can't write anywhere but can execute Python scripts can be found at https://github.com/anvilsecure/ulexecve/. I wrote this one and you might want some more context via the overview blogpost introducing it to the world: https://www.anvilsecure.com/blog/userland-execution-of-binaries-directly-from-python.html

It should be rather trivial to change the code of ulexecve above such that it switches as well from 32-bit to 64-bit mode or vice versa. That's an interesting use case I didn't think about nor implement. But the approach sketched by @Ruslan should work.

Rapid7's Mettle also has a utility named noexec that basically does the same thing: https://github.com/rapid7/mettle. This is a lower-level utility written in C so you would end up with another ELF binary that would then allow you to do a userland execution.

gvb
  • 1