// Syd: rock-solid unikernel // lib/examples/go/goshell.go: Remote shell using syd via gosyd // // Copyright (c) 2023 Ali Polatel // SPDX-License-Identifier: GPL-3.0 package main import ( "fmt" "io/ioutil" "net" "os" "os/exec" "path/filepath" syd "git.sr.ht/~alip/syd/lib/src" "github.com/tmthrgd/tmpfile" ) const SYD = ` # Sandboxing types. # Enable all except stat sandboxing. sandbox/read:on sandbox/stat:off sandbox/write:on sandbox/exec:on sandbox/net:on sandbox/pid:on sandbox/mem:on # Define a modest limit for PID sandboxing pid/max:64 # Define modest limits for Memory sandboxing mem/max:256M mem/vm_max:2G # Allow /dev/null allow/read+/dev/null allow/write+/dev/null # Allow reading dynamic libraries under system paths. allow/read+/lib*/** allow/read+/usr/**/lib*/** # Allow PTYs allow/read+/dev/ptmx allow/write+/dev/ptmx allow/read+/dev/pty/[0-9]* allow/write+/dev/pty/[0-9]* # Allow execution of binaries under system paths. allow/exec+/bin/* allow/exec+/usr/**/bin/* # Allow /proc but deny pid1=syd allow/read+/proc/*** allow/write+/proc/*** deny/read+/proc/1/*** deny/write+/proc/1/*** ` func main() { // Determine the port to listen on port := "65432" // default port if len(os.Args) > 1 { port = os.Args[1] } if err := syd.Check(); err != nil { fmt.Fprintf(os.Stderr, "Not running under syd: %v\n", err) fmt.Println("Run \"syd -plib -pcontainer ./goshell\"") os.Exit(1) } fmt.Println("Initializing") // Create a temporary directory with a specific prefix tempDir, err := ioutil.TempDir("", "goshell-tmp-") if err != nil { panic(err) } fmt.Println("Temporary directory created:", tempDir) // Defer the deletion of the temporary directory defer os.RemoveAll(tempDir) // Change to the temporary directory if err := os.Chdir(tempDir); err != nil { panic(err) } // Get the canonical absolute path of the temporary directory absPath, err := filepath.Abs(tempDir) if err != nil { panic(err) } // Resolve any symbolic links canonicalPath, err := filepath.EvalSymlinks(absPath) if err != nil { panic(err) } cwd := canonicalPath os.Setenv("HOME", cwd) // Create a new temporary file tempFile, remove, err := tmpfile.TempFile("") if err != nil { panic(err) } defer tempFile.Close() // If remove is true, it's the caller's responsibility to remove // the file when no longer needed if remove { os.Remove(tempFile.Name()) } // Write the syd configuration to the file _, err = tempFile.WriteString(SYD) if err != nil { panic(err) } // Seek the file to the beginning _, err = tempFile.Seek(0, os.SEEK_SET) if err != nil { panic(err) } // Load the profile into syd. if err := syd.Load(int(tempFile.Fd())); err != nil { panic(err) } else { fmt.Println("Load: ok") } // Allow current working directory for read+write if err := syd.AllowReadAdd(cwd + "/***"); err != nil { panic(err) } else { fmt.Printf("AllowReadAdd(%s/***): ok\n", cwd) } if err := syd.AllowWriteAdd(cwd + "/**"); err != nil { panic(err) } else { fmt.Printf("AllowWriteAdd(%s/**): ok\n", cwd) } // Allow binding to requested address. if err := syd.AllowNetBindAdd("127.0.0.1!" + port); err != nil { panic(err) } else { fmt.Printf("AllowNetBind(127.0.0.1!%s)\n", port) } // Finally, lock syd so tampering is no longer possible. if err := syd.Lock(syd.LockOn); err != nil { panic(err) } else { fmt.Println("LockOn: ok") } // Listen on TCP port listener, err := net.Listen("tcp", "127.0.0.1:"+port) if err != nil { panic(err) } defer listener.Close() fmt.Println("Listening on localhost:" + port) for { // Accept a connection conn, err := listener.Accept() if err != nil { fmt.Println("Error accepting: ", err.Error()) os.Exit(1) } // Handle the connection in a new goroutine go handleRequest(conn) } } func handleRequest(conn net.Conn) { // Close the connection when function ends defer conn.Close() // Start /bin/sh and connect its input/output to the connection cmd := exec.Command("/bin/sh") cmd.Stdin, cmd.Stdout, cmd.Stderr = conn, conn, conn // Run the shell if err := cmd.Run(); err != nil { fmt.Fprintf(conn, "Error running shell: %s\n", err) return } }