core/tests/e2e: wait for all subprocesses we created

Test Plan: `bazel test core/tests/e2e/... --runs_per_test=10`

X-Origin-Diff: phab/D548
GitOrigin-RevId: e7ed0d0f782fc38dfa94f83ded890187c6fd9c70
diff --git a/core/internal/launch/launch.go b/core/internal/launch/launch.go
index 9aa277c..a88e46d 100644
--- a/core/internal/launch/launch.go
+++ b/core/internal/launch/launch.go
@@ -21,6 +21,7 @@
 	"fmt"
 	"io"
 	"io/ioutil"
+	"log"
 	"net"
 	"os"
 	"os/exec"
@@ -211,17 +212,32 @@
 		qemuArgs = append(qemuArgs, "-no-reboot")
 	}
 
-	tpmCtx, tpmStop := context.WithCancel(
-		ctx)
-	tpmEmuCmd := exec.CommandContext(tpmCtx, "swtpm", "socket", "--tpm2", "--tpmstate", "dir="+tpmTargetDir, "--ctrl", "type=unixio,path="+tpmSocketPath)
-	systemCmd := exec.CommandContext(ctx, "qemu-system-x86_64", qemuArgs...)
+	// Start TPM emulator as a subprocess
+	tpmCtx, tpmCancel := context.WithCancel(ctx)
+	defer tpmCancel()
 
+	tpmEmuCmd := exec.CommandContext(tpmCtx, "swtpm", "socket", "--tpm2", "--tpmstate", "dir="+tpmTargetDir, "--ctrl", "type=unixio,path="+tpmSocketPath)
 	tpmEmuCmd.Stderr = os.Stderr
 	tpmEmuCmd.Stdout = os.Stdout
+
+	err = tpmEmuCmd.Start()
+	if err != nil {
+		return fmt.Errorf("failed to start TPM emulator: %w", err)
+	}
+
+	// Start the main qemu binary
+	systemCmd := exec.CommandContext(ctx, "qemu-system-x86_64", qemuArgs...)
 	systemCmd.Stderr = os.Stderr
 	systemCmd.Stdout = os.Stdout
-	go tpmEmuCmd.Run()
+
 	err = systemCmd.Run()
-	tpmStop()
-	return err
+
+	// Stop TPM emulator and wait for it to exit to properly reap the child process
+	tpmCancel()
+	log.Print("Waiting for TPM emulator to exit")
+	// Wait returns a SIGKILL error because we just cancelled its context.
+	// We still need to call it to avoid creating zombies.
+	_ = tpmEmuCmd.Wait()
+
+	return nil
 }