[engine] Use O_CLOEXEC when writing nsjail

This should solve the issue with nsjail file descriptors leaking into
forked subprocesses during tests and causing "text file busy" errors
when the nsjail executable gets run.

Change-Id: I293a5d31479fd6ca4d203a872cb80cb881e0a80d
Reviewed-on: https://fuchsia-review.googlesource.com/c/shac-project/shac/+/909332
Fuchsia-Auto-Submit: Oliver Newman <olivernewman@google.com>
Reviewed-by: Marc-Antoine Ruel <maruel@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/internal/sandbox/sandbox.go b/internal/sandbox/sandbox.go
index af36164..2db044c 100644
--- a/internal/sandbox/sandbox.go
+++ b/internal/sandbox/sandbox.go
@@ -24,6 +24,7 @@
 	"runtime"
 	"sort"
 	"strings"
+	"syscall"
 
 	"go.fuchsia.dev/shac-project/shac/internal/execsupport"
 )
@@ -66,9 +67,22 @@
 		execsupport.Mu.Lock()
 		defer execsupport.Mu.Unlock()
 		nsjailPath := filepath.Join(tempDir, "nsjail")
+		// O_CLOEXEC prevents subprocesses from inheriting the open FD. This
+		// doesn't happen in production, but shac tests run many shac instances
+		// in parallel in the same subprocess and it causes issues if those
+		// subprocesses do forks that inherit the open FD.
+		flag := os.O_WRONLY | os.O_CREATE | syscall.O_CLOEXEC
 		// Executable permissions are ok.
 		//#nosec CWE-276
-		if err := os.WriteFile(nsjailPath, nsjailExecutableBytes, 0o700); err != nil {
+		f, err := os.OpenFile(nsjailPath, flag, 0o700)
+		if err != nil {
+			return nil, err
+		}
+		_, err = f.Write(nsjailExecutableBytes)
+		if err2 := f.Close(); err == nil {
+			err = err2
+		}
+		if err != nil {
 			return nil, err
 		}
 		return nsjailSandbox{nsjailPath: nsjailPath}, nil