Files

114 lines
3.4 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package control
import (
"bytes"
"fmt"
"io"
"net"
"os"
"path/filepath"
"syscall"
"time"
)
const handoffMarker = "FENRIR-CONNECTION\n"
// Transfer the original connected socket, rather than forwarding its text.
// SO_PEERCRED and replies then remain attached to the actual caller, including
// when an unprivileged shared owner routes a request to a root TTY reader.
func handoff(caller *net.UnixConn, path string) error {
connection, err := net.DialTimeout("unix", path, 200*time.Millisecond)
if err != nil {
return err
}
defer connection.Close()
target := connection.(*net.UnixConn)
var expectedPID int
_, parseErr := fmt.Sscanf(filepath.Base(path), "fenrirscreenreader-%d.sock", &expectedPID)
pid, uid, credentialErr := peer(target)
info, statErr := os.Lstat(path)
if parseErr != nil || credentialErr != nil || statErr != nil || pid != expectedPID {
return fmt.Errorf("private reader identity changed")
}
if stat, ok := info.Sys().(*syscall.Stat_t); !ok || int(stat.Uid) != uid {
return fmt.Errorf("private reader ownership changed")
}
_ = target.SetDeadline(time.Now().Add(500 * time.Millisecond))
raw, err := caller.SyscallConn()
if err != nil {
return err
}
var sendErr error
err = raw.Control(func(fd uintptr) {
count, _, err := target.WriteMsgUnix([]byte(handoffMarker), syscall.UnixRights(int(fd)), nil)
sendErr = err
if sendErr == nil && count < len(handoffMarker) {
_, sendErr = io.WriteString(target, handoffMarker[count:])
}
})
if err != nil {
return err
}
return sendErr
}
func receive_request(connection *net.UnixConn) (string, *net.UnixConn, error) {
_ = connection.SetReadDeadline(time.Now().Add(250 * time.Millisecond))
var buffer [8192]byte
control := make([]byte, syscall.CmsgSpace(4))
count, controlCount, flags, _, err := connection.ReadMsgUnix(buffer[:], control)
if err != nil {
return "", nil, err
}
var files []int
messages, parseErr := syscall.ParseSocketControlMessage(control[:controlCount])
for _, message := range messages {
fds, err := syscall.ParseUnixRights(&message)
if err != nil {
parseErr = err
}
for _, fd := range fds {
syscall.CloseOnExec(fd)
}
files = append(files, fds...)
}
defer func() {
for _, fd := range files {
_ = syscall.Close(fd)
}
}()
if parseErr != nil || flags&(syscall.MSG_CTRUNC|syscall.MSG_TRUNC) != 0 {
return "", nil, fmt.Errorf("invalid connection handoff")
}
if len(files) > 0 {
if len(files) != 1 {
return "", nil, fmt.Errorf("invalid connection handoff")
}
frame := io.MultiReader(bytes.NewReader(buffer[:count]), connection)
header := make([]byte, len(handoffMarker))
if _, err := io.ReadFull(frame, header); err != nil || string(header) != handoffMarker {
return "", nil, fmt.Errorf("invalid connection handoff header")
}
// FileConn duplicates the descriptor. The received descriptor is closed
// below; the duplicate belongs to the request handler.
file := os.NewFile(uintptr(files[0]), "remote-caller")
files = nil
forwarded, err := net.FileConn(file)
file.Close()
if err != nil {
return "", nil, err
}
unix, ok := forwarded.(*net.UnixConn)
if !ok || forwarded.LocalAddr().Network() != "unix" {
forwarded.Close()
return "", nil, fmt.Errorf("handoff is not a Unix connection")
}
return "", unix, nil
}
text, err := read_request(connection, buffer[:count])
return text, nil, err
}