Last active
November 27, 2016 02:16
-
-
Save empijei/51ae1aa9d439abacd8bdb76f337a88ad to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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