5

I have a small program that mmaps potentially dangerous executable code (with PROT_EXEC), calls prctl(PR_SET_SECCOMP, 1) and then executes this mmap'd code. This is all well and good, and allows me to "save" the state of the evaluation by sync the mmap'd region to disk, and reload it later (most likely on another machine for load balancing). However, this technique doesn't always work -- because this code might have made changes to the program that are not in the mmap'd region, and this information will be lost.

So what I would like to do, is make absolutely everything (other than this mmap'd region) read-only before calling the code. This way I have a guarantee that the executable code can't change the state of anything other than the mmap'd region which I can serialize/deserialize at will.

BTW this is Linux on x86_64

Thanks

Heptic
  • 3,076
  • 4
  • 30
  • 51
  • If you succeed in making the memory read-only, won't your program fail when it tries to write ? – cnicutar Nov 13 '11 at 13:32
  • If it tries to write outside its mmap'd region, then failing is fine – Heptic Nov 13 '11 at 13:35
  • hmmm... if you have the permission to mprotect your code and you run the executable code without giving up these permission, the executable code can simply call mprotect to regain those rights. – gby Nov 13 '11 at 15:29
  • @gby No, because the prctl call disables the ability to do system calls – Heptic Nov 13 '11 at 19:57

1 Answers1

4

Firstly, an observation: There's nothing that says you have to mmap() to get machine instructions into memory or to save them back to a file. read() and write() can do this too, just note that you should make a writable and executable private mapping for this purpose.

Obviously you can't reliably disable writing to the area of the stack that will be calling into the executable code that you'll load, if it's to be executed within the same process since this will render the stack unusable. You might work around this by annotation your variables or using assembly.

Your next option is to fork(). You could exec in the child into a special wrapper executable that allows for minimal damage and introspection by malicious executable code (provides simply load/dump), or you could do the same by having the child modify itself to the same effect. This still isn't 100% safe.

Proposal0

  • Create a stand alone binary that is linked against minimal libraries (-nodefaultlibs).
  • After a fork, ptrace(PTRACE_TRACEME) in the child (so that you can read memory contents reliably and do other interventions), and close all handles except that of a pipe (just in stdin for simplicity). exec() into the aforementioned wrapper binary.

In the wrapper binary:

  • mmap a private region at a known location with write and execute permissions. Alternatively you can statically allocate this region if the size is fixed.
  • Read the contents of the pipe into the region.
  • Close the pipe. Now the process has no open handles.
  • prctl(PR_SET_SECCOMP, 1). Now the only valid system calls are _exit and sigreturn. Since the process can't raise, sigreturn should have no useful effect.
  • Remove write permissions from the main stack (should be the only stack). Since you have no intention of returning, and will jump immediately afterward, you shouldn't need to touch the stack again.
  • Jump to the starting location inside the region. Do this using assembly, or create a function pointer and invoke it (if you can get it to work without pushing to the stack). Now you should be executing a region of memory that is the only writable region available. The main stack was protected, and the heap should not be in use due to lack of library support.

In the parent:

  • Using ptrace or wait, catch erroneous or successful completion.
  • Read the mapped region at the known location via /proc/<pid>/mem or equivalent to file.
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
  • This is a nice alternate way of doing things, but I don't see how it solves my problem. The executable code could still have made changes somewhere that are not captured by writing the mapped region to a file. – Heptic Nov 13 '11 at 20:07
  • Also, btw the reason I'm using an mmap'd region and loading the code there -- is because Linux isn't letting me set executable permissions for normal memory pages, which is a big problem for loading reflective/self modifying code – Heptic Nov 13 '11 at 20:09
  • @Heptic: No it can't. The point is to create a minimal process with no handles, and only crashing or `_exit` as the viable ways to end. As soon as it does end, the parent dumps the executable memory region. – Matt Joiner Nov 13 '11 at 22:50