114 lines
3.4 KiB
Go
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
|
|
}
|