sandbox: restrict run time
Assuming that wall time is a reasonable proxy for cpu time.
Change-Id: If6a3fe141f16d4b400047154d1513c6d32c945c5
Reviewed-on: https://go-review.googlesource.com/2851
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go
index 6f10f16..dbca0b1 100644
--- a/sandbox/sandbox.go
+++ b/sandbox/sandbox.go
@@ -3,6 +3,8 @@
// license that can be found in the LICENSE file.
// TODO(adg): add logging
+// TODO(proppy): restrict memory use
+// TODO(adg): send exit code to user
// Command sandbox is an HTTP server that takes requests containing go
// source files, and builds and executes them in a NaCl sanbox.
@@ -10,6 +12,7 @@
import (
"encoding/json"
+ "errors"
"fmt"
"io/ioutil"
"log"
@@ -17,8 +20,11 @@
"os"
"os/exec"
"path/filepath"
+ "time"
)
+const maxRunTime = 500 * time.Millisecond
+
type Request struct {
Body string
}
@@ -31,16 +37,16 @@
func compileHandler(w http.ResponseWriter, r *http.Request) {
var req Request
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, fmt.Sprintf("request error: %v", err), http.StatusBadRequest)
+ http.Error(w, fmt.Sprintf("error decoding request: %v", err), http.StatusBadRequest)
return
}
resp, err := compileAndRun(&req)
if err != nil {
- http.Error(w, fmt.Sprintf("sandbox error: %v", err), http.StatusInternalServerError)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
- http.Error(w, fmt.Sprintf("response error: %v", err), http.StatusInternalServerError)
+ http.Error(w, fmt.Sprintf("error encoding response: %v", err), http.StatusInternalServerError)
return
}
}
@@ -66,12 +72,14 @@
}
return nil, fmt.Errorf("error building go source: %v", err)
}
- // TODO(proppy): restrict run time and memory use.
cmd = exec.Command("sel_ldr_x86_64", "-l", "/dev/null", "-S", "-e", exe)
rec := new(Recorder)
cmd.Stdout = rec.Stdout()
cmd.Stderr = rec.Stderr()
- if err := cmd.Run(); err != nil {
+ if err := runTimeout(cmd, maxRunTime); err != nil {
+ if err == timeoutErr {
+ return &Response{Errors: "process took too long"}, nil
+ }
if _, ok := err.(*exec.ExitError); !ok {
return nil, fmt.Errorf("error running sandbox: %v", err)
}
@@ -83,6 +91,27 @@
return &Response{Events: events}, nil
}
+var timeoutErr = errors.New("process timed out")
+
+func runTimeout(cmd *exec.Cmd, d time.Duration) error {
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+ errc := make(chan error, 1)
+ go func() {
+ errc <- cmd.Wait()
+ }()
+ t := time.NewTimer(d)
+ select {
+ case err := <-errc:
+ t.Stop()
+ return err
+ case <-t.C:
+ cmd.Process.Kill()
+ return timeoutErr
+ }
+}
+
func healthHandler(w http.ResponseWriter, r *http.Request) {
if err := healthCheck(); err != nil {
http.Error(w, "Health check failed: "+err.Error(), http.StatusInternalServerError)