如何防止一个进程产生更多的孩子

我正在做一名在线评委,在我大学的局域网上进行ACM-ICPC比赛。 为此,我要求法官可能足够安全,以防止恶意程序在我的服务器上执行自己。 (这样的程序的一个例子是)

int main(){ while(1) fork(); } 

让我们调用这个程序testing代码的可执行文件。

这个程序会导致我的服务器运行法官冻结。 显然,我不希望这种情况发生。所以为了防止我试图使用ptrace.I想出了以下代码:(让我们调用这个代码监视器的可执行文件)

 #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/user.h> #include <sys/syscall.h> #include <sys/reg.h> #include<stdio.h> #include <sys/ptrace.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/user.h> #include <sys/syscall.h> #include <sys/reg.h> #include<stdio.h> #include<signal.h> #include<sys/prctl.h> #include<stdlib.h> #define NOBANNEDSYS 40 int bannedsys[50]={2,14,12,15,26,37,38,39,39,40,41,42,46,47,48,49,50,60,61,63,72,83,88,120,102,182,183,190}; int main(int argc,char **argv) { int insyscall=0; if(argc!=2) { fprintf(stderr,"Usage: %s <prog name> ",argv[0]); exit(-1); } int status = 0; int syscall_n = 0; int entering = 1; int amp; struct user_regs_struct regs; int pid = fork(); if ( !pid ) { prctl(PR_SET_PDEATHSIG, SIGKILL); ptrace( PTRACE_TRACEME, 0, 0, 0 ); execlp( argv[1],argv[1], 0 ); } else { //ptrace( PTRACE_SINGLESTEP ,pid, 0, 0 ); // ptrace( PTRACE_SYSCALL, pid, 0, 0 ); while (1) { wait( &amp); if ( WIFEXITED( amp ) ) break; //ptrace( PTRACE_SINGLESTEP ,pid, 0, 0 ); if(insyscall==0){ ptrace( PTRACE_GETREGS, pid, 0,&regs ); int i=0; for(i=0;i<NOBANNEDSYS;i++) if(regs.orig_eax==bannedsys[i]) { kill(pid,SIGKILL); printf("%d killed due to illegal system call\n",pid); abort(); } insyscall=1; } else insyscall=0; // ptrace(PTRACE_CONT,pid,0,0); // wait(&amp); ptrace( PTRACE_SYSCALL, pid, 0, 0 ); // puts("Here"); //ptrace(PTRACE_CONT, pid, 0, 0); } } return 0; } 

这段代码在阻止可能导致问题的系统调用方面效果很好。但是当被监视的代码包含像testing代码一样的循环中的fork调用时,由于抖动,机器会冻结。原因是当原始进程被终止通过监视器代码,它的小孩生还,并继续携带叉炸弹。 如何修复监视器代码,以便可以成功部署?

PS:可移植性不是一个问题。我正在寻找一个Linux的具体答案。

编辑:我调用exec之前,我使用setrlimit设置subprocess的最大数量为0,这直到现在似乎是一个很好的解决scheme。虽然这将是很好的听到来自社区,如果仍然存在漏洞的监控代码。

您可以阻止孩子并追踪新的进程:

PTRACE_O_TRACEFORK(从Linux 2.5.46开始)在(SIGTRAP | PTRACE_EVENT_FORK << 8)的下一个fork(2)调用处停止子进程并自动开始追踪新分叉的进程,该进程将以SIGSTOP开始。 新进程的PID可以通过PTRACE_GETEVENTMSG进行检索。

您可以通过这些更改获得一个工作示例:

 /* ... */ ptrace(PTRACE_SETOPTIONS,pid,NULL, PTRACE_SYSCALL | PTRACE_O_TRACEFORK) ; while (1) { printf("Waiting\n"); pid = wait(&amp); printf("Waited %d\n", amp); if (WIFEXITED(amp)) { break; } if (WSTOPSIG(amp) == SIGTRAP) { int event = (amp >> 16) & 0xffff; if (event == PTRACE_EVENT_FORK) { printf("fork caught\n"); pid_t newpid; ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &newpid); kill(newpid, SIGKILL); kill(pid, SIGKILL); break; } } if (insyscall == 0) { ptrace(PTRACE_GETREGS, pid, 0, &regs); int i = 0; for (i = 0; i < NOBANNEDSYS; i++) if (regs.orig_eax == bannedsys[i]) { kill(pid, SIGKILL); printf("%d killed due to illegal system call\n", pid); abort(); } insyscall = 1; } else { insyscall = 0; } ptrace(PTRACE_CONT, pid, NULL, 0); } 

参考

对于你正在做的事情,我强烈建议调查一下seccomp ,它允许你禁止一个进程使用除exit以外的任何系统调用, write (只对已经打开的文件描述符)和sigreturn