简体   繁体   English

编译具有有限库访问权限的程序

[英]Compiling a program with limited library access

i want to compile a C program with gcc and glibc(or any other c library) but i want to limit the program access to certain functions for example program should not be compiled if it uses sockets or signal handling functions. 我想用gcc和glibc(或任何其他c库)编译一个C程序,但我想限制程序访问某些函数,例如程序如果使用套接字或信号处理函数则不应编译。

any idea how i could do this?? 任何想法我怎么能这样做?

by the way i want to use this on a simple programming contest judge 顺便说一句,我想在一个简单的编程竞赛评委中使用它

Thanks 谢谢

You cannot reliably limit access to certain functions, because a motivated developer could always find a work around. 您不能可靠地限制对某些功能的访问,因为有动力的开发人员总能找到解决方法。 For example, he could use dlsym to find the address of some function at runtime, or use asm code invoking some syscalls (or use buffer overflow techniques) or assume a particular version of the libc binary and compute some function pointers (eg by offsetting the address of some legitimate libc function like printf with a built-in offset), or cast some literal string (containing suitable machine opcodes) to a function pointer etc, etc.... 例如,他可以使用dlsym在运行时查找某个函数的地址,或者使用asm代码调用一些系统调用(或使用缓冲区溢出技术)或假设特定版本的libc二进制文件并计算一些函数指针(例如通过偏移一些合法的libc函数的地址,如printf ,内置偏移量),或者将一些文字字符串(包含合适的机器操作码)转换为函数指针等等....

However, you might consider customizing the compiler (eg if compiling with a recent GCC , customize it with your MELT extension) to detect the common cases (but not all of them). 但是,您可以考虑自定义编译器(例如,如果使用最近的GCC进行编译 ,使用MELT扩展进行自定义)以检测常见情况(但不是所有情况)。 This may mean weeks of work developing such compiler customization. 这可能意味着开发此类编译器定制的数周工作。

You might also link with your specially crafted libc , use LD_PRELOAD or ptrace , etc. 您也可以链接您特制的libc ,使用LD_PRELOADptrace等。

To forbid some behavior reliably, you should run inside some virtual container. 要可靠地禁止某些行为,您应该在某个虚拟容器中运行。

PS. PS。 Statically (soundly & reliably) detecting that some source code would never call a given set of functions is undecidable , since equivalent to the halting problem . 静态 (合理且可靠地)检测到某些源代码永远不会调用给定的一组函数是不可判定的 ,因为等同于停止问题

Please do not do that. 请不要这样做。 Even when you find a way to disallow certain functions like execl , there are plenty of ways to get around these kinds of restrictions. 即使你找到一种方法来禁止像execl这样的某些功能,也有很多方法可以解决这些限制。 For instance, a program could do their own calls to the operating system with inline-assembly or other tricks. 例如,程序可以使用内联汇编或其他技巧自己调用操作系统。

There are few things you can do: 你可以做的事情很少:

  • Lock down the environment. 锁定环境。
  • Run the program under some sort of hypervisor that terminates the program when it does something you don't want. 在某种虚拟机管理程序下运行程序,当程序执行您不想要的操作时终止该程序。 There is a Linux toolkit to do that, but I forgot its name. 有一个Linux工具包可以做到这一点,但我忘了它的名字。

If you just want to detect that a program used disallowed functions, you could run nm on the binary created by the compiler and check if any disallowed function names appear. 如果您只想检测程序使用了不允许的函数,则可以在编译器创建的二进制文件上运行nm ,并检查是否出现任何不允许的函数名称。 Notice that not all function have a symbol name equal to their name. 请注意,并非所有函数都具有与其名称相同的符号名称。

I guess I'm a bit late to the party, but I feel that none of the answers given so far are entirely correct. 我想我在派对上有点迟了,但我觉得到目前为止给出的答案都没有完全正确。 In fact it is possible to restrict the capabilities of a program in the way you ask, and to do so sanely. 实际上,可以按照您提出的方式限制程序的功能,并且可以这样做。

It is true that preventing calls to arbitrary functions is, while also possible, pointless -- it would be like sealing a colander hole by hole. 确实,防止对任意函数的调用虽然也可能是毫无意义的 - 但这就像用孔密封漏洞一样。 It is also not asking the right question -- you do not, I suspect, want to prevent the coder from calculating the square root of a number but to prevent him from owning the system. 它也没有提出正确的问题 - 我怀疑你没有想要阻止编码人员计算数字的平方根,而是阻止他拥有系统。 This means to prevent him from making the system do certain things, which would invariably involve a system call, so it makes sense to focus on them instead of functions. 这意味着要防止他让系统做某些事情,这些事情总是涉及系统调用,因此关注它们而不是功能是有意义的。 Which function is used to open a socket is immaterial; 用于打开套接字的功能并不重要; they all end up using the socket system call. 他们都最终使用socket系统调用。

Access to system calls can be controlled by the kernel. 系统调用访问可以由内核来控制。 The linux kernel has a mechanism for this called seccomp that is used by various large programs such as Firefox, Chrome and Adobe Flash to sandbox their code interpreters and some smaller ones such as vsftpd to minimize their attack surface in the event that an attacker manages to find a remote code execution vulnerability (on the basis that the exploit code would find itself severely limited without the ability to call exec and others). Linux内核有一个称为seccomp的机制,它被各种大型程序(如Firefox,Chrome和Adobe Flash)用于沙盒代码解释器和一些较小的代码解析器(例如vsftpd),以便在攻击者设法攻击时最大限度地减少攻击面。找到一个远程代码执行漏洞(基于漏洞利用代码会发现自己严重受限而无法调用exec和其他人)。

Now, before I go into more detail: If you are going to take code from people you don't know (and hence can't trust), paranoia is sanity. 现在,在我详细介绍之前:如果你要从你不认识的人那里获取代码(因此不能信任),偏执就是理智。 Seccomp is good but not sufficient in this scenario because this scenario is an attacker's wet dream. Seccomp很好但在这种情况下还不够,因为这种情况是攻击者的梦想。 It would be best to stack defenses and not bother with subtlety. 最好是堆叠防御,而不是精力充沛。 So, the first three things you have to do for this are: 所以,你要做的前三件事是:

  1. Use a VM 使用VM
  2. Use a VM 使用VM
  3. Seriously, use a VM. 说真的,使用VM。

Running all programs in a virtual machine makes it much harder to exploit your main system because an attacker would have to break out of the VM in addition to all the other things he would have to do otherwise. 在虚拟机中运行所有程序使得利用主系统变得更加困难,因为除了他必须做的所有其他事情之外,攻击者还必须打破VM。 There are free implementations for this that work nicely and are not very difficult to set up. 有免费的实现可以很好地工作,并且设置起来不是很困难。 I use Virtualbox most of the time. 我大多数时候都使用Virtualbox

Once you have installed a Linux system into your VM, make a snapshot of the VM so you can go back to it if a program manages to destroy it. 一旦将Linux系统安装到VM中,就可以创建 VM 的快照,以便在程序设法销毁它时可以返回VM

Got all that set up? 有这么多设置? Good. 好。 Now, seccomp allows a process to restrict its ability to make use of system calls. 现在,seccomp允许进程限制其使用系统调用的能力。 By design, the restrictions are a one-way street; 按设计,限制是单行道; it is not possible to re-expand the process's capabilities later. 以后无法重新扩展流程的功能。 The restrictions seccomp can place are somewhat powerful; seccomp可以放置的限制有点强大; for example, not only can a process prevent itself from calling write , it can also prevent itself from calling write on any file descriptor other than STDOUT_FILENO . 例如,进程不仅可以阻止自己调用write ,还可以防止自己在STDOUT_FILENO以外的任何文件描述符上调用write Since the kernel API is rather clunky, I will be using libseccomp in the following code examples. 由于内核API相当笨重,我将在以下代码示例中使用libseccomp It has a set of very helpful man pages that should help you with the details, and your distribution is likely to have packages for it unless it is very old. 它有一组非常有用的手册页,可以帮助您了解详细信息,除非它很老,否则您的发行版可能会包含它。 A simple example to show what this is about: 一个简单的例子来说明这是什么:

#include <seccomp.h>
#include <stdio.h>
#include <unistd.h>

int main() {
  scmp_filter_ctx ctx;

  puts("foo");                // works as usual. (needed here because it forces
  fputs("bar\n", stderr);     // some initialisation. More on that later)

  ctx = seccomp_init(SCMP_ACT_KILL);              // default action: kill process
  seccomp_rule_add(ctx, 
                   SCMP_ACT_ALLOW,                       // allow
                   SCMP_SYS(write),                      // calls to write
                   1,                                    // under one condition:
                   SCMP_A0(SCMP_CMP_EQ, STDOUT_FILENO)); // if the first argument
                                                         // is STDOUT_FILENO
  seccomp_load(ctx);

  puts("foo");                      // this will still work
  fputs("bar\n", stderr);           // this will make the kernel kill the process
  fprintf(stderr, "bar\n");         // so would this
  fputc('b', stderr);               // and this
  write(STDERR_FILENO, "bar\n", 4); // and this
                                    // and any other write to anything but stdout

  return 0;
}

So we have reasonably fine-grained control over allowed system calls, which is nice. 因此,我们对允许的系统调用进行了相当细粒度的控制,这很好。 It leaves the problem of identifying the system calls that need to be allowed for the proper operation of the program, a couple of which are non-trivial to decide. 它留下了识别系统调用的问题,这些系统调用需要被允许用于程序的正确操作,其中一些是非常重要的。 This is a design question you'll have to answer yourself. 这是一个你必须自己回答的设计问题。 The system calls are listed in /usr/include/asm/unistd_64.h . 系统调用列在/usr/include/asm/unistd_64.h

So how do we apply it to a piece of code from an untrustworthy source? 那么我们如何将它应用于来自不值得信任的来源的一段代码呢?

Patching the code with sed or something similar is an idea one might have, but it is too unreliable to make sense for security-critical applications. 使用sed或类似的东西修补代码是一个可能有的想法,但对于安全关键型应用程序而言,它是不太可靠的。 A "safe loader" that prohibits system calls before calling the program with execv runs into the problem that it cannot prohibit the execve system call, which is one of those one would want to prohibit most. 在使用execv调用程序之前禁止系统调用的“安全加载程序”会遇到无法禁止execve系统调用的问题,这是其中一个想要禁止的问题之一。 Moreover, execv requires a bunch of other system calls (such as access , mmap , open , fstat , close , mprotect , and arch_prctl ) before the main function of that program is even entered. 此外, execv在进入该程序的main功能之前需要一堆其他系统调用(例如accessmmapopenfstatclosemprotectarch_prctl )。 So what to do? 那么该怎么办?

IMPORTANT UPDATE: This section originally included an attempt that used LD_PRELOAD to load the seccomp code; 重要更新:此部分最初包含使用LD_PRELOAD加载seccomp代码的尝试; @virusdefender correctly pointed out that this had a glaring hole in it because user code could control whether the function was actually run. @virusdefender正确地指出,它有一个明显的漏洞,因为用户代码可以控制该函数是否实际运行。 The new approach makes the runtime linker call our function, closing that hole. 新方法使运行时链接器调用我们的函数,关闭该漏洞。

One way is to use a shared library that has nothing in it except a constructor and destructor which are run at load and unload time, respectively. 一种方法是使用其中没有任何内容的共享库,除了分别在加载和卸载时运行的构造函数和析构函数。 The linker will load the library before running code from the binary, so the constructor will be run and the filter installed before the user code takes control. 链接器将在从二进制文件运行代码之前加载库,因此将在用户代码获得控制权之前运行构造函数并安装过滤器。

Code as follows: 代码如下:

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

static scmp_filter_ctx ctx;

// Macro just to make error handling simple. Error handling is
// very important here. You don't want this to silently fail.
#define ADD_SECCOMP_RULE(ctx, ...)                      \
  do {                                                  \
    if(seccomp_rule_add(ctx, __VA_ARGS__) < 0) {        \
      perror("Could not add seccomp rule");             \
      seccomp_release(ctx);                             \
      exit(-1);                                         \
    }                                                   \
  } while(0)

// Constructor. This sets up the seccomp filter.
static void __attribute__((constructor)) seccomp_load_init(void) {
  ctx = seccomp_init(SCMP_ACT_KILL);

  if(ctx == NULL) {
    perror("Could not open seccomp context");
    exit(-1);
  }

  // Rules for system calls here.
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit      ), 0);
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write     ), 1, SCMP_A0(SCMP_CMP_EQ, STDOUT_FILENO));
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write     ), 1, SCMP_A0(SCMP_CMP_EQ, STDERR_FILENO));
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read      ), 1, SCMP_A0(SCMP_CMP_EQ, STDIN_FILENO));

  // This is needed for dynamic memory allocation
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk       ), 0);

  // These are needed for stdio initialisation. Workarounds to this are ugly, and the
  // syscalls are not terribly critical because they require file descriptors. We
  // restrict the program's ability to obtain those.
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap      ), 0);
  ADD_SECCOMP_RULE(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat     ), 0);

  if(seccomp_load(ctx) < 0) {
    perror("Could not load seccomp context");
    exit(-1);
  }
}

// Destructor; run at unload time. Just cleanup here.
static void __attribute__((destructor)) seccomp_load_free(void) {
  seccomp_release(ctx);
}

This will need to be compiled into a shared library: 这将需要编译到共享库中:

gcc -fPIC -shared -o libmyfilter.so myfilter.c

And it will need to be linked to the untrustworthy code, so that the linker loads it when the program is started: 它需要链接到不值得信任的代码,以便链接器在程序启动时加载它:

gcc -o untrustworthy_program untrustworthy_code.c -L/path/to/myfilter -lmyfilter -lseccomp

And then you can call the untrustworthy program less unsafely (inside your VM!) with 然后你可以用不安全的方式调用不值得信任的程序(在你的VM中!)

LD_LIBRARY_PATH=/path/to/myfilter ./untrustworthy_program

Where /path/to/myfilter is the directory that contains libmyfilter.so . 其中/path/to/myfilter libmyfilter.so是包含libmyfilter.so的目录。

Because the filter library uses functions from libc (and libseccomp), the libc startup stuff will be done before the seccomp filter is installed. 因为过滤器库使用libc(和libseccomp)中的函数,所以libc启动内容将在安装seccomp过滤器之前完成。 This is intentional (and was part of the rationale behind the original attempt), because libc does a number of things at startup, such as opening files, that we may want to prevent the user code from doing. 这是故意的(并且是原始尝试背后的基本原理的一部分),因为libc在启动时执行了许多操作,例如打开文件,我们可能希望阻止用户代码执行操作。 In the event that you want to allow use of another library that does things at startup that the filter should later prevent, you can use LD_PRELOAD to make the linker load it before the filter. 如果您希望允许使用另一个在启动时执行过滤器应该稍后阻止的库,则可以使用LD_PRELOAD使链接器在过滤器之前加载它。

I'm not going to go out so far on a limb as to say that this will make exploits impossible, but an attacker would, if you design your syscall filters sanely, have to find exploitable bugs both in the Linux kernel (either in seccomp or the subset of the kernel you allowed it to use) and your VM, which is very likely to be exceedingly difficult. 到目前为止,我不打算说这会使漏洞成为不可能,但攻击者如果你设计了你的系统调用过滤器,就必须在Linux内核中找到可利用的漏洞(在seccomp中)或者你允许它使用的内核子集)和你的VM,这很可能非常困难。 In the more likely case that I neglected to think of something (again), the VM will still be a useful line of defense. 在更可能的情况下,我忽略了某些事情(再次),VM仍然是一个有用的防线。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM