Concept ------- The netEstate sandbox is a way to split an application into several independent modules that cannot affect each other or the operating system the sandbox runs on. This can be very useful for security critical applications. To achieve this, the application is split into separate executables (modules) that communicate with each other via messages. The messages are generated with a special remote procedure call API and are sent/received via stdin/stdout. The main program "sandbox" starts all modules and monitors their output. If a message is received, it is forwarded to the target module. The sandbox-program will ensure that every module cannot fake messages from another so that every module can decide what requests it will accept from what module. If a module sends a special message to the sandbox-program, it will get "jailed". This means that from now on, the module can only use a configureable set of system calls. By default, these are 1 (_exit), 3 (read), 4 (write), 45 (brk) and 142 (select) used by the remote procedure call API for communication. A module jailed in this way will only be able to do these things, even if compromised (i.e. by a buffer overflow): -Communicate with other modules -Change data segment size (malloc(),etc.) -Read or write from/to file descriptors that were open at the time of jailing For further information and pricing, contact: Michael Brunnbauer netEstate GmbH Frankfurter Ring 193a 80807 Muenchen Germany Phone: +49 89 32197780 Fax: +49 89 32197789 E-Mail: brunni@netestate.de Usage of sandbox ---------------- The module list that sandbox should execute is stored in the array "conf" in sandbox.h. The array elements have this structure: struct prgconfig { char* name; // module name char* path; // path to execute char** argv; // arguments (0-terminated) unsigned char* jail; // valid system calls in jail (0-terminated) }; Example: unsigned char stdjail[]={ 1,3,4,45,142,0 }; char* argv_test1[]={ "test1",0 }; char* argv_test2[]={ "test2",0 }; struct prgconfig conf[]= { { "test 1", "./test1", argv_test1, stdjail }, { "test 2", "./test2", argv_test2, stdjail }, { 0, 0, 0, 0 } }; sandbox will assign the Nth module in conf the program id N (The id of conf[i] is i+1) and communicate with it via stdin and stdout. It will forward every message to the specified target program id and silently drop messages with fake source program id or invalid target program id. If a child requests to be "jailed" by sending a request to program id 0, sandbox will send a positive result to that child and kill it as soon as it uses a system call not specified in its configuration. If a child exits or garbage is received from a child, sandbox will terminate. Please note that this can be used from every module for a denial of service attack. Usage of the remote procedure call API -------------------------------------- The main functions are: void call(unsigned char** sendbuffer, unsigned long* sendbufferlength, unsigned long* sendbufferpos, struct request* req) Convert the request req to a message and append it to the sending buffer. sendbuffer is automatically resized if the message is too big. req is deleted automatically. You must initialize sendbuffer,sendbufferlength and sendbufferpos with 0 before passing them to the API for the first time. void answer(unsigned char** sendbuffer, unsigned long* sendbufferlength, unsigned long* sendbufferpos, struct result* res) Convert the result res to a message and append it to the sending buffer. sendbuffer is automatically resized if the message is too big. res is deleted automatically. unsigned char send_data(unsigned char* sendbuffer, unsigned long* sendbufferpos, int handle, unsigned long timeout_secs, unsigned long timeout_usecs) Send as much data from sendbuffer to the file handle (this must be STDOUT_FILENO for normal usage) as possible. send_data will wait timeout_secs seconds and timeout_usecs microseconds for the file handle to become writeable. The return-code is 0 for a timeout and 1 if some data was sent. unsigned char wait_received(unsigned char** receivebuffer, unsigned long* receivebufferlength, unsigned long* receivebufferpos, int handle, unsigned long timeout_secs, unsigned long timeout_usecs) Receive one message from handle (this must be STDOUT_FILENO for normal usage) via receivebuffer. Wait timeout_secs seconds and timeout_usecs microseconds for the file handle to become readable. The return-code is 0 for a timeout, 1 if a request was received and 2 if a result was received. The received request/result is stored in struct resultlist* results; struct requestlist* requests; Both structs have the same basic structure, a linked list: struct resultlist { struct result* aktresult; struct resultlist* nextresult; }; struct requestlist { struct request* aktrequest; struct requestlist* nextrequest; }; Requests have this structure: struct request { unsigned long messageid; // internal message id unsigned char from; // source program id unsigned char to; // target program id unsigned char function; // function id struct arglist* argv; // arguments }; Use this function to generate requests: struct request* newrequest(unsigned long messageid, // our messageid unsigned char to, // target program id unsigned char function, // target function struct arglist* argv) // argument list If you have evalutated a request, you should remove it from the request list with: void removerequest(unsigned long messageid) Results have this structure: struct result { unsigned long messageid; // internal message id unsigned char from; // source program id unsigned char to; // target program id unsigned char status; // result status struct value* v; // result value }; Use this function to generate results: struct result* newresult(unsigned long messageid, // our message id unsigned char status, // result status unsigned char to, // target program id struct value* v) // result value If you have identified and evaluated an expected result, you can remove it from the result list with: void removeresult(unsigned long messageid) Result status is one of STATUS_OK Request OK STATUS_NOSUCHFUNCTION Program has no such function STATUS_WRONGARGS Wrong argument types for function STATUS_DENIED Source program id is not allowed to use function [in this way] Values have this structure: struct value { unsigned char type; unsigned long len; // only for VALUE_DATA union { unsigned char charval; long longval; unsigned long ulongval; double doubleval; unsigned char* dataval; } val; }; type is one of: VALUE_VOID void value, val is undefined VALUE_CHAR char value, use val.charvar VALUE_LONG long value, use val.longval VALUE_ULONG unsigned long value, use val.ulongval VALUE_DOUBLE double value, use val.doubleval VALUE_STRING null-terminated string, use val.dataval VALUE_DATA data with length, use len and val.dataval Use these functions to generate values: struct value* newvalue() // for VALUE_VOID struct value* newcharvalue(unsigned char c) struct value* newlongvalue(long longval) struct value* newulongvalue(unsigned long ulongval) struct value* newdoublevalue(double doubleval) struct value* newstringvalue(char* s) struct value* newdatavalue(unsigned char* dataval,unsigned long datalen) Argument lists have this structure: struct arglist { struct value* aktvalue; struct arglist* nextvalue; }; Use this function to generate argument lists and insert values into argument lists: struct arglist* insertarglist(struct arglist* list, struct value* aktvalue) Use 0 as argument list for the first call. Programming Example 1 --------------------- This program will have the program id 2. It waits for a request for function id 1 and will return the first string argument as result to the caller. #include "log.h" #include "mm.h" #include "rpc.h" unsigned char debug=0; int main(int argc,char** argv) { struct value* v; struct arglist* a=0; unsigned long curid=1; struct request* req; struct result* res; unsigned char* receivebuffer=0; unsigned long receivebufferlength=0; unsigned long receivebufferpos=0; unsigned char* sendbuffer=0; unsigned long sendbufferlength=0; unsigned long sendbufferpos=0; unsigned char status; moduleid=2; while (1) { /* receive result */ status=wait_received(&receivebuffer, &receivebufferlength, &receivebufferpos, STDIN_FILENO,10,0); if (status==0) { log(ERR_GENERAL,__FILE__,__LINE__,0); } else if (status==1) { req=requests->aktrequest; if (req->from!=1) log(ERR_GENERAL,__FILE__,__LINE__,0); if (req->function!=1) log(ERR_GENERAL,__FILE__,__LINE__,0); if (req->argv->aktvalue->type!=VALUE_STRING) log(ERR_GENERAL,__FILE__,__LINE__,0); v=newstringvalue((char*) req->argv->aktvalue->val.dataval); res=newresult(req->messageid,STATUS_OK,req->from,v); removerequest(req->messageid); answer(&sendbuffer,&sendbufferlength,&sendbufferpos,res); while (sendbufferpos) send_data(sendbuffer,&sendbufferpos,STDOUT_FILENO,10,0); } else if (status==2) { log(ERR_GENERAL,__FILE__,__LINE__,0); }; }; exit(0); } Programming Example 2 --------------------- This program will have the program id 1 and is the counterpart to the above program. It will jail itself and then send a request for function id 1 to program id 2. The string "Hello World\n" is sent as the first argument. The program will then wait for the result and print it to stderr. #include "log.h" #include "mm.h" #include "rpc.h" unsigned char debug=0; int main(int argc,char** argv) { struct value* v; struct arglist* a=0; unsigned long curid=1; struct request* req; struct result* res; unsigned char* receivebuffer=0; unsigned long receivebufferlength=0; unsigned long receivebufferpos=0; unsigned char* sendbuffer=0; unsigned long sendbufferlength=0; unsigned long sendbufferpos=0; unsigned char status; unsigned long i; moduleid=1; /* send request to jail ourself and wait for result */ v=newvalue(); a=insertarglist(0,v); req=newrequest(curid++,0,1,a); call(&sendbuffer,&sendbufferlength,&sendbufferpos,req); while (sendbufferpos) send_data(sendbuffer,&sendbufferpos,STDOUT_FILENO,10,0); status=wait_received(&receivebuffer, &receivebufferlength, &receivebufferpos, STDIN_FILENO,10,0); if (status==0) { log(ERR_GENERAL,__FILE__,__LINE__,0); } else if (status==1) { log(ERR_GENERAL,__FILE__,__LINE__,0); } else if (status==2) { res=results->aktresult; if (res->status!=STATUS_OK) log(ERR_GENERAL,__FILE__,__LINE__,0); if (res->messageid!=curid-1) log(ERR_GENERAL,__FILE__,__LINE__,0); removeresult(res->messageid); }; /* now we are jailed */ for (i=0;i!=100;i++) { /* send request and wait for answer */ v=newstringvalue("Hello World\n"); a=insertarglist(0,v); req=newrequest(curid++,2,1,a); call(&sendbuffer,&sendbufferlength,&sendbufferpos,req); while (sendbufferpos) send_data(sendbuffer,&sendbufferpos,STDOUT_FILENO,10,0); /* receive result */ status=wait_received(&receivebuffer, &receivebufferlength, &receivebufferpos, STDIN_FILENO,10,0); if (status==0) { log(ERR_GENERAL,__FILE__,__LINE__,0); } else if (status==1) { log(ERR_GENERAL,__FILE__,__LINE__,0); } else if (status==2) { res=results->aktresult; if (res->status!=STATUS_OK) log(ERR_GENERAL,__FILE__,__LINE__,0); if (res->v->type!=VALUE_STRING) log(ERR_GENERAL,__FILE__,__LINE__,0); printvalue(stderr,res->v); removeresult(res->messageid); }; }; exit(0); }