6

I would like to execute arbitrary (potentially dangerous) binaries on my server. Therefore, I have used objcopy to rename the "main" symbol to "other_main" so that I could link in my own small main function that sets an appropriate value for RLIMIT_CPU and toggles the SECCOMP flag before calling other_main. I am quite happy with this solution so far.

The problem now is, that the 3rd party program code might contain some calls to malloc that might kill the program instantly (sbrk isn't allowed). Therefore I would like to pre-allocate some reasonable sized array (e.g. 20MB) before setting SECCOMP that should be used by malloc / realloc / calloc / free. Unfortunately, I don't know how to archive the last step. Do I have to implement all those 4 functions on my own? How can I inject my own functions to the stdlib (e.g., what happens when printf calls malloc internally?).

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
tux21b
  • 90,183
  • 16
  • 117
  • 101
  • Is renaming symbols realy works? I really wonder what OS do with a stripped binaries. – Dmitry Poroh Jul 13 '12 at 20:34
  • Renaming symbols works fine here. objcopy seems to be really powerful. You are apparently not allowed to strip the binaries, but that's not an issue for me, because I compile the binaries on my own. It's just the C code that is untrustworthy. – tux21b Jul 13 '12 at 20:46
  • Can you provide your users with a different C library (eg. Newlib) than your OS's library? If so, it would be very easy to to write your own `sbrk` (that can't get out of your sandbox) and then malloc/realloc/calloc/free and friends will all work. – Kevin Vermeer Jul 13 '12 at 20:59
  • Sorry. I miss that you relink and create new program. – Dmitry Poroh Jul 13 '12 at 21:00
  • @KevinVermeer Providing a different patched C library might be indeed an option, but I must admit that I was hoping for an easier solution that might also work with C++. – tux21b Jul 13 '12 at 21:05
  • 3
    Note that simply renaming `main` may not be sufficient -- a binary could contain initializers which run before `main`. –  Jul 13 '12 at 21:39
  • Oh, that's indeed a problem. Thanks for pointing that out. Is it safer if I link in my own _start and tell the linker my new entry point accordingly? – tux21b Jul 14 '12 at 07:21

3 Answers3

3

Not all malloc implementations are based on sbrk(), there is for example GNU mmalloc. This doc also might be useful if custom implementation is needed.

+two simple malloc implementations here

pmod
  • 10,450
  • 1
  • 37
  • 50
  • The only system calls that are allowed are read, write, sigreturn and exit. So I can't use mmap either, unless I pre-allocate enough memory at the start of the program (a reasonable sized static array seems to be fine for my use case). The link however looks useful, assuming that I really have to implement my own malloc. But I still don't know how I can replace the internal malloc calls used in printf and other places in the stdlib. – tux21b Jul 13 '12 at 20:57
  • I have corrected the answer: dlmalloc actually does use sbrk(). However, there I found GNU mmaloc which is based on mmap(). In your case you might consider using static pool[] in place of heap + simple handling functionality on its basis. For example, you may keep N references also in static array. That would be pseudo-dynamic memory of course. – pmod Jul 13 '12 at 21:09
2

For malloc and free, your program just needs to define its own versions. Most libc implementations that I've seen (including glibc, klibc, and dietlibc) will happily use your memory allocator routines. So, before entering seccomp mode, allocate a large chunk of memory using mmap or sbrk and then have your malloc/free allocate from this chunk. memmgr is a simple heap allocator that can be adapted easily for allocation out of a fixed buffer.

The real problem with seccomp is that the set of system calls it allows (read, write, exit, and sigreturn) is simply not sufficient to run programs linked against more or less any libc out there. For example:

  • in glibc, exit and _exit call exit_group
  • in glibc, printf can call mmap
  • in dietlibc, scanf can call ioctl
  • etc., etc.

There are typically good reasons why these calls are necessary. For example, dietlibc uses ioctl to check if stdin is a tty when input is read from stdin, in order to flush stdout. This is standard behavior to ensure that prompts are visible before reading interactive input if the output is line buffered.

So, I have come to the conclusion that the original seccomp mode is more or less useless. Mode 2 (a.k.a. "filter mode"), however, is much more useful since it allows you to whitelist specific system calls. I have a proof of concept on my github page which runs programs in seccomp mode 2, but allows them to use printf and scanf, as well as allocate memory using malloc/free.

1

seccompsandbox:

  • enables seccomp in one thread, which performs RPC (via read/write over a pre-allocated socketpair) to another (non-seccomp) thread in the same process which is able to perform privileged operations such as mmap
  • patching functions such as malloc (in-memory, at runtime) to redirect to their seccomp-safe wrappers

Chromium's seccomp Sandbox has some more details on how it works.

ephemient
  • 198,619
  • 38
  • 280
  • 391