At elitmus we use ruby to create most of our tools and most of our applications are also written in ruby. Recently I started exploring ways to build our tools especially the backend tools in languages other than ruby which have much lesser memory footprint and better efficiency. One such cases was to create a sandboxed environment for running untrusted code on our servers. After evaluating multiple languages, I decided to use golang because of it’s excellent library support coupled with the fact the docker(a sandboxed env) was also written in go.

One of the many challenges we faced while creating our sandbox was redirection of the standard output of untrusted code, as this simple code below will fill up the disk if redirected to file or use all system resources if redirected to a buffer.

1 while ( true ) printf ( “ I am the green monster ” );

So the problem is,how to limit the size of a file or buffer?, I started with buffers as they are more easy to implement. I assumed that the Write method of the Buffer struct which writes to the buffer, will panic with ErrTooLarge error if buffer size is above it’s capacity, which i hoped to catch using recover builtin function.

This is the code snippet below.

1 defer func () { 2 if r := recover (); r != nil { 3 fmt . Println ( "Should catch if anyone panics" ) 4 } 5 }() 6 a := bytes . NewBuffer ( make ([] byte , 0 , 2 )) 7 for { 8 _ , err := a . Write ([] byte ( "create boom" )) 9 if err != nil { 10 fmt . Println ( err . Error ()) 11 return 12 } 13 14 }

On running this code, my system was frozen and crashed a little later. This is not what i expected, On further investigation by looking to source code and reading the bytes package documentation again, i found out that Write method in the bytes package is growing the capacity of the buffer if the buffer capacity is not enough, which in turn is increasing the amount of memory and resources used by the system.

After some googling and with good help from the go community(thanks to dave cheney), i decided to create wrapper around the buffer struct and implement my own io.Writer interface by implementing Write method for the wrapper which writes to the buffer.

My custom wrapper’s will take capacity as parameter when initializing and the Write method will do the required action if there is a buffer overflow, instead of increasing the capacity like the Write method from bytes package. This is done by monitoring the size of the buffer before writing to the buffer.

This is code snippet of my custom wrapper.

1 type MyBuffer struct { 2 cap int 3 mybuf * bytes . Buffer 4 } 5 6 func ( b * MyBuffer ) Write ( p [] byte ) ( n int , err error ) { 7 if len ( p ) + b . mybuf . Len () > b . cap { 8 fmt . Printf ( b . mybuf . String ()) 9 panic ( "Buffer Overflow" ) 10 } else { 11 b . mybuf . Write ( p ) 12 } 13 return len ( p ), nil 14 } 15 16 func NewBuffer ( buf [] byte , cap int ) * MyBuffer { 17 return & MyBuffer { mybuf : bytes . NewBuffer ( buf ), cap : cap } 18 } 19 20 func main () { 21 22 defer func () { 23 if r := recover (); r != nil { 24 fmt . Println ( "recover in yes" ) 25 } 26 }() 27 28 a := NewBuffer ( make ([] byte , 0 , 100 ), 200 ) 29 for { 30 _ , err := a . Write ([] byte ( "Check for Buffer Overflow" )) 31 if err != nil { 32 fmt . Println ( err . Error ()) 33 return 34 } 35 } 36 }

On running this code, it worked as expected, hopefully will be deployed in production. The same goes for files as well.

Note: useful links, on docker,on golang bytes package