Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@jvns
Created May 25, 2023 12:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jvns/14b8f65537004a56013260d9219ef36f to your computer and use it in GitHub Desktop.
Save jvns/14b8f65537004a56013260d9219ef36f to your computer and use it in GitHub Desktop.
package main
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"syscall"
"time"
)
func bubblewrap(command ...string) *exec.Cmd {
bwrap_args := []string{
"--setenv", "HOME", "/root",
"--setenv", "TERM", "xterm-256color",
"--setenv", "PATH", "/usr/local/sbin:/usr/local/bin:/usr/bin:/bin",
"--setenv", "BINARY_NAME", os.Getenv("BINARY_NAME"),
"--unshare-pid", "--unshare-net", "--unshare-uts", "--unshare-user",
"--ro-bind", "/usr", "/usr",
"--ro-bind", "/lib", "/lib",
"--ro-bind", "/tmp", "/tmp",
"--ro-bind-try", "/lib32", "/lib32",
"--ro-bind-try", "/lib64", "/lib64",
"--ro-bind-try", "/libx32", "/libx32",
"--ro-bind", "/bin", "/bin",
"--ro-bind", "/etc", "/etc",
"--ro-bind", "/root", "/root",
"--ro-bind", "/app", "/app",
"--proc", "/proc", "--dev", "/dev",
}
bwrap_args = append(bwrap_args, command...)
return exec.Command("bwrap", bwrap_args...)
}
func runWithTimeout(cmd *exec.Cmd, timeout time.Duration) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
outChan := make(chan []byte, 1)
errChan := make(chan error, 1)
go func() {
output, err := cmd.CombinedOutput()
if err != nil {
errChan <- fmt.Errorf("failed to run command: %v\n%v", err, string(output))
return
}
outChan <- output
}()
select {
case <-ctx.Done():
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
return nil, errors.New("command timed out")
case err := <-errChan:
return nil, err
case out := <-outChan:
return out, nil
}
}
func run(rw http.ResponseWriter, request *http.Request) {
rw.Header().Set("Access-Control-Allow-Origin", "*")
if request.Method == "OPTIONS" {
rw.WriteHeader(http.StatusOK)
return
}
// get program from request params
program, err := ioutil.ReadAll(request.Body)
// get the line of the reponse that contains "line_number"
output, err := runProgram(program)
json := extractJSON(output)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
rw.Write([]byte(err.Error()))
return
}
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(json))
}
func runProgram(program []byte) (string, error) {
// make temp file
sourceFile, err := ioutil.TempFile("", "temp_*.c")
defer os.Remove(sourceFile.Name())
if err != nil {
return "", err
}
// write program to temp file
_, err = sourceFile.Write(program)
if err != nil {
return "", err
}
// close temp file
err = sourceFile.Close()
if err != nil {
return "", err
}
// compile program with clang -g
outputFile := sourceFile.Name()[0 : len(sourceFile.Name())-2]
defer os.Remove(outputFile)
cmd := exec.Command("clang", "-O0", "-g", "-o", outputFile, sourceFile.Name())
output, err := runWithTimeout(cmd, 5*time.Second)
if err != nil {
return "", fmt.Errorf("failed to compile program: %v\n%s", err, output)
}
// set environment variable with output file name
err = os.Setenv("BINARY_NAME", outputFile)
if err != nil {
return "", fmt.Errorf("failed to set environment variable: %v", err)
}
// run lldb with script
cmd = bubblewrap("lldb", "-s", "stack_trace")
// print cmd so that someone could run it
fmt.Println(cmd.String())
lldbOutput, err := runWithTimeout(cmd, 1*time.Second)
if err != nil {
return "", fmt.Errorf("failed to run lldb: %v\n%s", err, lldbOutput)
}
return string(lldbOutput), nil
}
func extractJSON(output string) string {
// find the line that contains "line_number"
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, "line_number") {
return line
}
}
return ""
}
func test() {
program, err := ioutil.ReadFile(os.Args[2])
result, err := runProgram(program)
if err != nil {
fmt.Println("Error: ", err)
}
fmt.Println("Result: ", result)
}
func main() {
if len(os.Args) > 1 && os.Args[1] == "--test" {
test()
return
}
// start http server on port 8080
// static files are in the "static" directory
http.HandleFunc("/run/", run)
http.ListenAndServe(":8080", nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment