113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
//go:build windows
|
|
|
|
package control
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"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.
|
|
if err := windows.ConnectNamedPipe(pipe, nil); err != nil {
|
|
windows.CloseHandle(pipe)
|
|
return
|
|
}
|
|
f := os.NewFile(uintptr(pipe), pipePath)
|
|
serveConn(f, h) // closes f, and with it the pipe handle
|
|
}()
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|