Mprotect entire program to run dangerous code

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. All this is good and good, and allows me to “save” the evaluation state by synchronizing the mmap'd area on the disk and restarting it later (most likely, on another machine for load balancing). However, this method does not always work - because this code could make changes to the program that are not in the mmap'd area, and this information will be lost.

So, what I would like to do is to do absolutely everything (except for this mmap'd region) read-only before calling the code. Thus, I have a guarantee that the executable code cannot change the state of anything other than the mmap'd area, which I can serialize / deserialize as desired.

By the way, this is Linux on x86_64

thanks

+4
source share
1 answer

Firstly, an observation: it says nothing that you should mmap() get the machine instructions in memory or save them back to a file. read() and write() can do this too, just note that for this purpose you must make writable and executable personal matching.

Obviously, you cannot reliably disable writing to the stack area, which will invoke the executable code that you download if it is executed in the same process, as this will render the stack unusable. You can get around this by annotating your variables or using assembly.

The next option is fork() . You could execute exec in child into a special wrapper executable that allows minimal damage and introspection using malicious executable code (provides just load / dump), or you can do the same if the child modifies itself with the same effect. It is still not 100% safe.

Proposal0

  • Create a standalone binary file that is associated with minimal libraries ( -nodefaultlibs ).
  • After the fork , ptrace(PTRACE_TRACEME) in the child element (so that you can reliably read the contents of the memory and perform other interventions), and close all the descriptors except for the channel (just in stdin for simplicity). exec() to the binary file of the above shell.

In the wrapper binary:

  • mmap private area in a famous location with write and execute permissions. Alternatively, you can statically highlight this area if the size is fixed.
  • Read the contents of the pipe in the region.
  • Close the handset. 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 cannot raise , sigreturn should not have any beneficial effect.
  • Remove write permissions from the main stack (should be the only stack). Since you do not intend to return, and immediately jump immediately, you will not need to touch the stack again.
  • Go to the starting location within the region. Do this using the assembly or create a pointer to a function and call it (if you can make it work without clicking on the stack). You should now execute the memory region, which is the only writable region. The main stack was protected, and the heap should not be used due to lack of library support.

In parent:

  • Using ptrace or wait , catch a failed or successful termination.
  • Read the displayed area in a known place through /proc/<pid>/mem or the equivalent file.
+4
source

Source: https://habr.com/ru/post/1380995/


All Articles