2

I like to forbid my C program certain rights, permissions or capabilities, e.g. to open any files (other than stdin, stdout, stderr) or any sockets, ideally even if run as root. The reason is, that the program embeds a Python interpreter and might run untrusted code. Simplified version:

int main(int argc, char** argv)
{
    /* TODO: drop all rights/permissions/capabilites
       to open files or sockets here! */

    Py_Initialize();
    PyRun_SimpleString(argv[1]);
    Py_Finalize();
}

This has to work with Python 2.6 on Linux 3.2. Any ideas?

Wrzlprmft
  • 4,234
  • 1
  • 28
  • 54
  • The "even if run as root" is only a worst case example. Imagine the program to be run on an embedded system without shell access, but still with untrusted `argv[1]`. Settting appropriate owner/group would not solve the problem, because I'm not aware of any owner or group who is not allowed to open any file on the system (or a socket). Even `nobody` is allowed to read `/etc/passwd` on a typical Linux system, for example. –  May 28 '15 at 12:04
  • I understand your problem. Maybe `chroot` the process then? – aldeb May 28 '15 at 12:31
  • `chroot` is not really a security feature, it can be escaped from (I don't know how, but bad people might know). Maybe I found the solution. See below. –  May 28 '15 at 14:14
  • This may or may not be usable for you, but [pypy has some sandboxing features](https://pypy.readthedocs.org/en/latest/sandbox.html) – Aereaux May 29 '15 at 17:47

5 Answers5

2

Maybe I found the answer on my own. Comments highly desired!

I'm trying to use the seccomp library to disallow all, but certain syscalls.

It seems to work, i.e. in my naïve tests I can read from stdin, write to stdout, but cannot open files via Python.

#include <stdio.h>

#include <seccomp.h>

#include <python2.7/Python.h>

#define ERR_EXIT(err) do { \
    fprintf(stderr, "%s near line %d\n", strerror(-err), __LINE__); \
    exit(-1); } while (0);

int main(int argc, char** argv)
{
    int i;
    scmp_filter_ctx ctx;
    int err;

    Py_Initialize();

    /* return illegal calls with error */
    if (!(ctx = seccomp_init(SCMP_ACT_ERRNO(1)))) {
        ERR_EXIT(1);
    }
    /* allow write, but only to stdout */
    if ((err = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write),
                    1, SCMP_A0(SCMP_CMP_EQ, STDOUT_FILENO)))) {
        ERR_EXIT(err);
    }
    /* allow read, but only from stdin */
    if ((err = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read),
                    1, SCMP_A0(SCMP_CMP_EQ, STDIN_FILENO)))) {
        ERR_EXIT(err);
    }
    /* brk, exit, exit_group, and rt_sigaction are needed by Python */
    if ((err = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0))) {
        ERR_EXIT(err);
    }
    if ((err = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0))) {
        ERR_EXIT(err);
    }
    if ((err = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0))) {
        ERR_EXIT(err);
    }
    if ((err = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0))) {
        ERR_EXIT(err);
    }

    if ((err = seccomp_load(ctx))) {
        ERR_EXIT(err);
    }

    for (i = 1; i < argc; i++) {
        PyRun_SimpleString(argv[i]);
    }

    Py_Finalize();

    return 0;
}

I very much appreciate any critique on this approach, thanks!

0

Maybe have a look at this or search for "restricted Python" elsewhere. You might be able to wrap the untrusted code such that it runs in a restricted environment.

smheidrich
  • 4,063
  • 1
  • 17
  • 30
  • 2
    The linked page says prominently: _"In Python 2.3 these modules have been disabled due to various known and not readily fixable security holes. The modules are still documented here to help in reading old code..."_ Restricting Python itself is futile, IMHO. See the various discussions about restricting the `eval` builtin function, which does not work. Therefore I like to restrict the complete process. –  May 28 '15 at 12:08
0

You can use SELinux or AppArmor to restrict rights of an application on Linux.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • This (SELinux or AppArmor or TOMOYO or Grsecurity) would probably the technically best and most powerful solution. But in this case I prefer seccomp, because it needs less work for a single program. –  May 28 '15 at 21:38
0

In Unix/Linux, you can limit many resources using the command limit from the command line.

% limit
cputime      unlimited
filesize     unlimited
datasize     unlimited
stacksize    10240 kbytes
coredumpsize 0 kbytes
memoryuse    unlimited
vmemoryuse   unlimited
descriptors  4096 
memorylocked 64 kbytes
maxproc      1024 

Thus, you can limit the number file descriptors open (which both limits the number of sockets you can open and the number of files; unfortunately, it doesn't distinguish between the two types offile descriptors), or how many processes may be forked (maxproc), or how big are files you can create.

I believe that limits are inherited, so if you start a shell with certain limits restricted, all the limits you change will be inherited by any new processes invoked. I.e., you could limit what a user can do by forcing him into an environment where these limits are set.

This may not quite be what you are looking for, but it is a way to do some coarse limiting from the command line.

rts1
  • 1,416
  • 13
  • 15
  • I'm not sure whether `ulimit` (`setrlimit()` in C) can disallow closing e.g. `stdout` and then opening another file. But a combination of both `seccomp` and `setrlimit` sounds like a good thing: `seccomp` to filter syscall, `setrlimit` to prevent RAM and CPU abuse. –  May 29 '15 at 17:33
0

Just for completeness: One can certainly use systemd-nspawn, but my target system still runs upstart. At some later point I will certainly look into this solution, maybe combined with seccomp and setrlimit.