main.go raw

   1  package main
   2  
   3  import (
   4  	"os"
   5  	"runtime"
   6  	"unsafe"
   7  )
   8  
   9  // Milestone-2 verification for the spawn-inherited lockdown channel.
  10  //
  11  // Layout:
  12  //
  13  //   1. Parent creates a unidirectional pipe via runtime.MakePipe.
  14  //   2. Parent forks. The child inherits the pipe fds (fork copies the fd table).
  15  //   3. Child closes the read end, calls runtime.SetSecureLockdownFd(write),
  16  //      allocates a guarded arena, writes a recognizable secret pattern, then
  17  //      deliberately reads past the guard page to trip SIGSEGV. The signal
  18  //      handler wipes the arena and writes MOXIE_SECALLOC_LOCKDOWN to the
  19  //      write fd of the pipe — NOT to stderr.
  20  //   4. Parent closes the write end, reads from the read end, waits for the
  21  //      child to die, and prints LOCKDOWN_RECEIVED if the marker arrived via
  22  //      the inherited pipe.
  23  //
  24  // The harness verifies:
  25  //   - exit status of the parent is 0
  26  //   - parent stdout contains LOCKDOWN_RECEIVED (so the byte traversed the pipe)
  27  //   - parent stdout contains CHILD_EXITED_SIGSEGV (child died on the guard page)
  28  //   - the raw secret pattern does NOT appear anywhere in parent stdout/stderr
  29  //
  30  // Why bare fork instead of spawn: spawn requires moxie.Codec types and a
  31  // dispatch loop. M2.2 is about verifying the lockdown fd is routable to a
  32  // real cross-process channel, not about exercising spawn. Once the routing
  33  // is proven, integration with spawn is a separate, smaller step.
  34  
  35  func main() {
  36  	read, write := runtime.MakePipe()
  37  	if read < 0 || write < 0 {
  38  		os.Stderr.Write([]byte("FAIL: MakePipe failed\n"))
  39  		os.Exit(1)
  40  	}
  41  
  42  	pid := runtime.Fork()
  43  	if pid < 0 {
  44  		os.Stderr.Write([]byte("FAIL: Fork failed\n"))
  45  		os.Exit(1)
  46  	}
  47  
  48  	if pid == 0 {
  49  		// Child path.
  50  		runtime.Close(read)
  51  		runtime.SetSecureLockdownFd(write)
  52  
  53  		secret := []byte{:32, secure}
  54  		pattern := []byte("MOXIE_SECRET_PAYLOAD_32_BYTES_AA")
  55  		copy(secret, pattern)
  56  
  57  		// Trip the trailing guard page exactly as the M1 test does.
  58  		ptr := (*byte)(unsafe.Add(unsafe.Pointer(&secret[0]), 4096))
  59  		sink := *ptr
  60  		os.Stdout.Write([]byte{sink})
  61  		os.Stdout.Write([]byte("UNREACHABLE\n"))
  62  		os.Exit(0)
  63  	}
  64  
  65  	// Parent path.
  66  	runtime.Close(write)
  67  
  68  	// Read up to 64 bytes from the lockdown pipe. The marker is
  69  	// "MOXIE_SECALLOC_LOCKDOWN\n" (24 bytes).
  70  	rf := os.NewFile(uintptr(read), "lockdown")
  71  	buf := make([]byte, 64)
  72  	n, _ := rf.Read(buf)
  73  
  74  	status := runtime.Waitpid(pid)
  75  
  76  	if n > 0 {
  77  		// Look for the marker substring in what we received.
  78  		marker := []byte("MOXIE_SECALLOC_LOCKDOWN")
  79  		got := buf[:n]
  80  		found := false
  81  		for i := 0; i+len(marker) <= len(got); i++ {
  82  			eq := true
  83  			for j := 0; j < len(marker); j++ {
  84  				if got[i+j] != marker[j] {
  85  					eq = false
  86  					break
  87  				}
  88  			}
  89  			if eq {
  90  				found = true
  91  				break
  92  			}
  93  		}
  94  		if found {
  95  			os.Stdout.Write([]byte("LOCKDOWN_RECEIVED\n"))
  96  		} else {
  97  			os.Stdout.Write([]byte("FAIL: pipe data did not contain marker\n"))
  98  		}
  99  	} else {
 100  		os.Stdout.Write([]byte("FAIL: pipe yielded no bytes\n"))
 101  	}
 102  
 103  	// status is the raw waitpid value: low 7 bits = signal if signalled,
 104  	// bit 7 = core dump flag, bits 8-15 = exit status if exited normally.
 105  	// SIGSEGV = 11. We just check the low 7 bits.
 106  	if status >= 0 && (status&0x7f) == 11 {
 107  		os.Stdout.Write([]byte("CHILD_EXITED_SIGSEGV\n"))
 108  	} else {
 109  		os.Stdout.Write([]byte("FAIL: child did not die on SIGSEGV (status="))
 110  		writeInt(status)
 111  		os.Stdout.Write([]byte(")\n"))
 112  	}
 113  }
 114  
 115  // writeInt writes a small int as decimal to stdout without using fmt
 116  // (which we deliberately avoid here to keep the test minimal).
 117  func writeInt(n int32) {
 118  	if n == 0 {
 119  		os.Stdout.Write([]byte("0"))
 120  		return
 121  	}
 122  	if n < 0 {
 123  		os.Stdout.Write([]byte("-"))
 124  		n = -n
 125  	}
 126  	var buf [12]byte
 127  	i := len(buf)
 128  	for n > 0 {
 129  		i--
 130  		buf[i] = byte('0' + n%10)
 131  		n /= 10
 132  	}
 133  	os.Stdout.Write(buf[i:])
 134  }
 135