//go:build windows package control import ( "context" "fmt" "log/slog" "os" "syscall" "unsafe" "golang.org/x/sys/windows" ) // pipePath is a kernel-local named pipe: no TCP, no firewall prompt. const pipePath = `\\.\pipe\gpu-turnstile` // sddlPipe grants full access to Administrators, SYSTEM and the pipe owner, // and read+write to authenticated users — except network logons, so the // pipe cannot be reached from another machine over SMB. const sddlPipe = "D:(D;;GRGW;;;NU)(A;;GA;;;BA)(A;;GA;;;SY)(A;;GA;;;OW)(A;;GRGW;;;AU)" var ( procConvertSDDL = windows.NewLazySystemDLL("advapi32.dll"). NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW") procWaitNamedPipe = windows.NewLazySystemDLL("kernel32.dll"). NewProc("WaitNamedPipeW") ) func waitNamedPipe(name *uint16, timeout uint32) error { r, _, err := procWaitNamedPipe.Call(uintptr(unsafe.Pointer(name)), uintptr(timeout)) if r == 0 { return err } return nil } func securityAttributesFromSDDL(sddl string) (*windows.SecurityAttributes, error) { s, err := windows.UTF16PtrFromString(sddl) if err != nil { return nil, err } var sd *uint16 // SECURITY_DESCRIPTOR*, kept for the process lifetime r, _, callErr := procConvertSDDL.Call( uintptr(unsafe.Pointer(s)), 1, /* SDDL_REVISION_1 */ uintptr(unsafe.Pointer(&sd)), 0) if r == 0 { return nil, fmt.Errorf("invalid SDDL: %w", callErr) } sa := &windows.SecurityAttributes{ Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})), SecurityDescriptor: (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(sd)), } return sa, nil } // Serve starts the pipe listener in the background and returns; only a // setup failure is reported. Each client connection is answered in its own // goroutine. On shutdown the process exit reaps everything. func Serve(ctx context.Context, h Handler, log *slog.Logger) error { sa, err := securityAttributesFromSDDL(sddlPipe) if err != nil { return err } name, err := windows.UTF16PtrFromString(pipePath) if err != nil { return err } go func() { for ctx.Err() == nil { pipe, err := windows.CreateNamedPipe(name, windows.PIPE_ACCESS_DUPLEX, windows.PIPE_TYPE_BYTE|windows.PIPE_READMODE_BYTE|windows.PIPE_WAIT, 16, 4096, 4096, 0, sa) if err != nil { log.Warn("control channel stopped", "err", err) return } go func() { // Blocks until a client connects; on process exit the // handle goes away with everything else. A client that // raced us and connected between CreateNamedPipe and // ConnectNamedPipe reports ERROR_PIPE_CONNECTED — that is // a success, not a failure. if err := windows.ConnectNamedPipe(pipe, nil); err != nil && err != errnoPipeConnected { windows.CloseHandle(pipe) return } serveConn(&pipeConn{f: os.NewFile(uintptr(pipe), pipePath), h: pipe}, h) }() } }() return nil } // errnoPipeConnected is ConnectNamedPipe's "the client connected before we // called" result, which means the connection is established. var errnoPipeConnected = syscall.Errno(535) // ERROR_PIPE_CONNECTED // pipeConn adapts a pipe handle to io.ReadWriteCloser. Close flushes first // (FlushFileBuffers blocks until the client has read the reply) and then // disconnects — closing the bare handle right after writing can discard // unread reply bytes, which clients see as an empty, failed request. type pipeConn struct { f *os.File h windows.Handle } func (c *pipeConn) Read(p []byte) (int, error) { return c.f.Read(p) } func (c *pipeConn) Write(p []byte) (int, error) { return c.f.Write(p) } func (c *pipeConn) Close() error { windows.FlushFileBuffers(c.h) //nolint:errcheck // best effort windows.DisconnectNamedPipe(c.h) //nolint:errcheck // best effort return c.f.Close() } // Ask sends one command to the running service and returns its reply. func Ask(cmd string) (string, error) { name, err := windows.UTF16PtrFromString(pipePath) if err != nil { return "", err } if err := waitNamedPipe(name, 2000); err != nil { return "", ErrUnavailable } handle, err := windows.CreateFile(name, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, 0, 0) if err != nil { return "", ErrUnavailable } f := os.NewFile(uintptr(handle), pipePath) defer f.Close() return readReply(f, cmd) }