Skip to content

Instantly share code, notes, and snippets.

@empijei
Last active November 27, 2016 02:16
Show Gist options
  • Save empijei/51ae1aa9d439abacd8bdb76f337a88ad to your computer and use it in GitHub Desktop.
Save empijei/51ae1aa9d439abacd8bdb76f337a88ad to your computer and use it in GitHub Desktop.
//Here are some more details on the problem, shown in an example:
package main
import (
"net/http"
"runtime"
"time"
)
//The simplest possible http.Handler
type simpleHandler struct {
}
func (t simpleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//To see the expected behavior use a timeout like this one and remove the loop.
//time.Sleep(5*time.Second)
for i := 0; ; i++ {
//This is just a simulation of some complex computation.
//The scheduler calls are emitted only at compile time
//so any sequence that the compiler considers atomic
//like this one cannot be interrupted.
//Details on how the compiler decides when to call the scheduler
//will be documented, but to make it simple I'd say there could be any
//non-blocking operation.
//I used an endless sum as a PoC.
//NOTE:Even if the scheduler were to in intervene, a detail that I will
//explain in section 2 will show that it wouldn't still be enough to free
//the resources this goroutine is using (the socket, the request's body and data,
//possibly acquired locks...)
}
}
func main() {
//Force one OS thread. More complex examples can be made that work on n
//OS threads but this is just a PoC that I wanted to keep simple.
//If you remove this line it will require x requests to starve the main thread
//where x==number of available OS threads
runtime.GOMAXPROCS(1)
//Create a new simple handler
sh := simpleHandler{}
//Add a timeout of 2 seconds to ideally prevent attackers from exausting
//server resources. There are other ways to do this, none prevents this problem
//whitout changing the handler inner code. This is the standard library one.
tsh := http.TimeoutHandler(sh, time.Second*2, "Too slow!")
//This starts an http server on port 8080 using the timed-out Handler.
//In theory every connection should receive "Too slow!" as an http response
//body but none will as the main thread will *never* be rescheduled and therefore
//the timeout code to provide an answer will never be executed.
//Moreover, any new connection will not be handled as there will be no thread
//available to do so, causing the starvation I will explain in section 3.
//This will prevent any other goroutine from being scheduled.
//
//NOTE: to add more fun there will be no logging working during this starvation phase.
//(happy debugging!)
http.ListenAndServe(":8080", tsh)
}
/*The same example in PHP looks roughly like this:
<?php
set_time_limit(2);
for($i=0;;$i++){
}
?>
Which yelds this result:
::1:36880 [500]: /index.php - Maximum execution time of 2 seconds exceeded
After 2 seconds. As expected.
The same works for any language that uses proper os threads like C# or Ruby.
I chose PHP because it is just more compact and widely known.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment