This Time Self-Hosted
dark mode light mode Search

Security considerations: peeing in the sandbox; or why you cannot trust ebuilds not to do any harm

The Gentoo packaging software stack is composed of various scripts and pieces of code that makes it very easy to build software from sources in a controlled manner, which I think is the main attractive for most users, it’s at least my main attractive. I have the same control over the software I’d have when building everything from source but I don’t have to waste time to figure out all the dependencies and the options. At least when I’m not the maintainer of the ebuild of course.

Some of the things that Gentoo makes available to developers (and users) are also security checks that stops people from shooting straight to their feet. One of these features is the sandbox software that tries to ensure that the software being built and installed does not try to write outside the control of Portage. It provides a sort-of sandbox around the processes and makes sure it only writes in that limited area. Similar approaches are also implemented by a few different projects.

Unfortunately I’ve noticed that some users and a few developers too mistakes the sandbox tool for a security measure, which is absolutely is not. It’s just a safety device that is used to catch software that mistakenly tries to write outside the area it is told to write to. Not only malicious code in ebuilds can be implemented by using the pkg_* functions (that execute without sandbox) or by using the addwrite directive, or by explicitly disabling the sandbox, but if software wants to explicitly disable sandbox, it’s quite easy to do that.

For the sake of argument, the following code appends a funny phrase to your message of the day in the system, which is usually accessible only by root:

#include <stdio.h>

int main() {
  FILE *fp = fopen("/etc/motd", "a");

  if ( !fp ) {
    fprintf(stderr, "Unable to open /etc/motd in writingn");
    return 1;
  }

  fprintf(fp, "I peed in your sandbox!n");
  fclose(fp);

  return 0;
}
Code language: C++ (cpp)

Now if you compile this code and try to execute it as an user it won’t be able to access the file thus it will result in an error. If you execute it with root it will be able to write to the file, but sandbox will prevent that; on the other hand, compiling it explicitly static will make it bypass sandbox just fine:

flame@yamato % gcc test.c -o test; gcc -static test.c -o test-static
flame@yamato % sudo sandbox ./test
ACCESS DENIED  open_wr:   /etc/motd
Unable to open /etc/motd in writing
--------------------------- ACCESS VIOLATION SUMMARY ---------------------------
LOG FILE "/var/log/sandbox/sandbox-15132.log"

VERSION 1.0
FORMAT: F - Function called
FORMAT: S - Access Status
FORMAT: P - Path as passed to function
FORMAT: A - Absolute Path (not canonical)
FORMAT: R - Canonical Path
FORMAT: C - Command Line

F: open_wr
S: deny
P: /etc/motd
A: /etc/motd
R: /etc/motd
C: ./test
--------------------------------------------------------------------------------
flame@yamato % sudo sandbox ./test-static
QA: Static ELF ./test-static
Code language: CSS (css)

As you can see, executing the standard built version will fail with a sandbox violation, while the static copy will just warn that a static executable will be executed. Which would be fine if it means that static executable calls were considered harmful and taken care of, but since all the software based on KDE 3’s admin/ directory are going to call the dynamic linker explicitly (which is obviously a static executable), and that is not currently masked out, it really does not make much sense just yet.

But still, this does not says anything about sandbox as a security measure, because you can easily write code that works around the sandbox, again, let’s see some alternative code:

#include <stdio.h>
#include <stdlib.h>

int main() {
  setenv("SANDBOX_ON", "0");
  FILE *fp = fopen("/etc/motd", "a");

  if ( !fp ) {
    fprintf(stderr, "Unable to open /etc/motd in writing");
    return 1;
  }

  fprintf(fp, "I peed in your sandbox!n");
  fclose(fp);

  return 0;
}Code language: C++ (cpp)

Or more subtly to avoid disabling the sandbox altogether, which might be quite easy to identify.

#include <stdlio.h>
#include <stdlib.h>

int main() {
  setenv("SANDBOX_WRITE", "/etc/motd");

  FILE *fp = fopen("/etc/motd", "a");

  if ( !fp ) {
    fprintf(stderr, "Unable to open /etc/motd in writing");
    return 1;
  }

  fprintf(fp, "I peed in your sandbox!n");
  fclose(fp);

  return 0;
}Code language: C++ (cpp)

And to be even more evil, one can just disable the sandbox from loading before calling whatever command:

#include <stdio.h>
#include <stdlib.h>

int main() {
  unsetenv("LD_PRELOAD");

  system("/bin/echo 'I peed in your sandbox!' > /etc/motd");

  return 0;
}Code language: C++ (cpp)

Now these options can be easily worked around by intercepting setenv() calls and at least warn that the sandbox variables are being tinkered with. But one can be even more subtle and work around the sandbox in another very interesting way:

#include <stdio.h>
#include <dlfcn.h>

FILE *(*my_fopen)(const char*, const char*);

int main() {
  void *libc = dlopen("libc.so.6", RTLD_LAZY|RTLD_GLOBAL);
  my_fopen = dlsym(libc, "fopen");

  FILE *fp = my_fopen("/etc/motd", "a");

  if ( !fp ) {
    fprintf(stderr, "Unable to open /etc/motd in writing");
    return 1;
  }

  fprintf(fp, "I peed in your sandbox!n");
  fclose(fp);

  return 0;
}Code language: C++ (cpp)

In this case the original fopen() call is loaded from the C library itself, exactly like the sandbox code does to call the original implementation. This would require dlopen() to be wrapped around and controlled, too.

I guess I should be looking forward to try closing up some of these holes in sandbox just so that we can make it more useful to avoid shooting ourselves in the feet, but one cannot rely on this software alone as a security measure. A good alternative approach would be to make sure that the software gets built and installed always with low-privilege users; as it is right now, even though build can be done with the portage user, root is always used for the software to be installed into the destination directory, which the sandbox cannot help with.

There are a number of issues that can easily come to mind when you think that sandbox is not a security measure, so I’ll probably try to write more about this in the future, for now please try to consider what this means on a much deeper level, and review your packages, just so you’re sure.

Comments 3
  1. No offence, but I think this is a dead-end. Given that an ebuild is run as root for the install phase, there’s simply no way to guarantee what a random file will do without review. That’s what Gentoo devs and users do, ofc.Even if it were possible (eg one can run in a VM and use {ps}trace and so on) the review is still necessary, so I don’t see the point in spending valuable technical time on this.The first and best defence is source transparency combined with _eyeballs_.minor lang point: ‘the main attractive’ should be ‘the main attraction’ (similarly for next usage.)

  2. To be honest – I don’t feel that the need of secure ebuild sandbox is needed. If the ebuild is not trusted it can just download a false gnome-panel from bad-cracker.com and execute at the same time as the user log into. Not trusted ebuilds need to be review anyway.On the other hand the sandbox is good defence before a badly written ebuilds or makefiles.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.