Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
268 lines
6.8 KiB
Go
268 lines
6.8 KiB
Go
// Linux volatile input custody; no raw input is written to ordinary disk.
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
type storageIdentity struct {
|
|
dev uint64
|
|
ino uint64
|
|
uid uint32
|
|
}
|
|
|
|
func volatileIdentity(path string) (storageIdentity, error) {
|
|
var id storageIdentity
|
|
if filepath.Dir(path) != "/dev/shm" || !strings.HasPrefix(filepath.Base(path), "forge-receive-scan-") {
|
|
return id, errors.New("volatile-storage-refused")
|
|
}
|
|
fd, err := syscall.Open(path, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return id, errors.New("volatile-storage-unavailable")
|
|
}
|
|
defer syscall.Close(fd)
|
|
var st syscall.Stat_t
|
|
var fs syscall.Statfs_t
|
|
if syscall.Fstat(fd, &st) != nil || syscall.Fstatfs(fd, &fs) != nil || fs.Type != 0x01021994 || st.Mode&07777 != 0700 || st.Uid != uint32(os.Getuid()) {
|
|
return id, errors.New("volatile-storage-refused")
|
|
}
|
|
return storageIdentity{uint64(st.Dev), st.Ino, st.Uid}, nil
|
|
}
|
|
|
|
func removeVolatile(path string, expected storageIdentity) error {
|
|
if _, err := os.Lstat(path); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
id, err := volatileIdentity(path)
|
|
if err != nil || id != expected {
|
|
return errors.New("volatile-storage-identity-changed")
|
|
}
|
|
if os.RemoveAll(path) != nil {
|
|
return errors.New("volatile-storage-cleanup-failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Pin the exact parent using Linux pidfd, not a reusable numeric process ID.
|
|
func parentDescriptor(parent int) (*os.File, error) {
|
|
if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
|
|
return nil, errors.New("cleanup-platform-unsupported")
|
|
}
|
|
if parent <= 1 || os.Getppid() != parent {
|
|
return nil, errors.New("cleanup-parent-refused")
|
|
}
|
|
fd, _, errno := syscall.Syscall(434, uintptr(parent), 0, 0) // pidfd_open, Linux 5.3+
|
|
if errno != 0 {
|
|
return nil, errors.New("cleanup-parent-unavailable")
|
|
}
|
|
f := os.NewFile(fd, "parent-pidfd")
|
|
if os.Getppid() != parent || fd >= 1024 {
|
|
f.Close()
|
|
return nil, errors.New("cleanup-parent-refused")
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func waitParent(f *os.File, duration time.Duration) bool {
|
|
fd := int(f.Fd())
|
|
var deadline time.Time
|
|
if duration >= 0 {
|
|
deadline = time.Now().Add(duration)
|
|
}
|
|
for {
|
|
// select may mutate both arguments, including when interrupted.
|
|
var set syscall.FdSet
|
|
set.Bits[fd/64] |= 1 << uint(fd%64)
|
|
var timeout *syscall.Timeval
|
|
if duration >= 0 {
|
|
remaining := time.Until(deadline)
|
|
if remaining < 0 {
|
|
remaining = 0
|
|
}
|
|
value := syscall.NsecToTimeval(remaining.Nanoseconds())
|
|
timeout = &value
|
|
}
|
|
n, err := syscall.Select(fd+1, &set, nil, nil, timeout)
|
|
if err == syscall.EINTR {
|
|
continue
|
|
}
|
|
return err == nil && n > 0
|
|
}
|
|
}
|
|
|
|
func signalParent(f *os.File, signal syscall.Signal) {
|
|
_, _, _ = syscall.Syscall6(424, f.Fd(), uintptr(signal), 0, 0, 0, 0) // pidfd_send_signal
|
|
}
|
|
|
|
func cleanupProcess(args []string) int {
|
|
if len(args) != 5 {
|
|
return 4
|
|
}
|
|
dev, e1 := strconv.ParseUint(args[1], 10, 64)
|
|
ino, e2 := strconv.ParseUint(args[2], 10, 64)
|
|
uid, e3 := strconv.ParseUint(args[3], 10, 32)
|
|
parent, e4 := strconv.Atoi(args[4])
|
|
if e1 != nil || e2 != nil || e3 != nil || e4 != nil {
|
|
return 4
|
|
}
|
|
expected := storageIdentity{dev, ino, uint32(uid)}
|
|
id, err := volatileIdentity(args[0])
|
|
if err != nil || id != expected {
|
|
return 3
|
|
}
|
|
process, err := parentDescriptor(parent)
|
|
if err != nil {
|
|
return 4
|
|
}
|
|
defer process.Close()
|
|
if _, err = os.Stdout.Write([]byte("R")); err != nil {
|
|
return 4
|
|
}
|
|
eof := make(chan bool, 1)
|
|
go func() { var b [1]byte; n, e := os.Stdin.Read(b[:]); eof <- n == 0 && e == io.EOF }()
|
|
select {
|
|
case ok := <-eof:
|
|
if !ok {
|
|
return 4
|
|
}
|
|
case <-time.After(180 * time.Second):
|
|
signalParent(process, syscall.SIGTERM)
|
|
if !waitParent(process, 5*time.Second) {
|
|
signalParent(process, syscall.SIGKILL)
|
|
if !waitParent(process, -1) {
|
|
return 4
|
|
}
|
|
}
|
|
}
|
|
if removeVolatile(args[0], expected) != nil {
|
|
return 3
|
|
}
|
|
return 0
|
|
}
|
|
|
|
type volatileStorage struct {
|
|
path string
|
|
id storageIdentity
|
|
pipe io.WriteCloser
|
|
command *exec.Cmd
|
|
done chan error
|
|
}
|
|
|
|
func newVolatileStorage() (*volatileStorage, error) {
|
|
root, err := syscall.Open("/dev/shm", syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, errors.New("volatile-storage-unavailable")
|
|
}
|
|
var fs syscall.Statfs_t
|
|
err = syscall.Fstatfs(root, &fs)
|
|
syscall.Close(root)
|
|
if err != nil || fs.Type != 0x01021994 {
|
|
return nil, errors.New("volatile-storage-unavailable")
|
|
}
|
|
path, err := os.MkdirTemp("/dev/shm", "forge-receive-scan-")
|
|
if err != nil {
|
|
return nil, errors.New("volatile-storage-unavailable")
|
|
}
|
|
id, err := volatileIdentity(path)
|
|
if err != nil {
|
|
os.RemoveAll(path)
|
|
return nil, err
|
|
}
|
|
cmd := exec.Command("/proc/self/exe", "--cleanup-storage", path, fmt.Sprint(id.dev), fmt.Sprint(id.ino), fmt.Sprint(id.uid), strconv.Itoa(os.Getpid()))
|
|
cmd.Env = []string{"PATH=/usr/bin:/bin", "LANG=C"}
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
writer, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
removeVolatile(path, id)
|
|
return nil, errors.New("cleanup-watchdog-unavailable")
|
|
}
|
|
reader, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
writer.Close()
|
|
removeVolatile(path, id)
|
|
return nil, errors.New("cleanup-watchdog-unavailable")
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
writer.Close()
|
|
reader.Close()
|
|
removeVolatile(path, id)
|
|
return nil, errors.New("cleanup-watchdog-unavailable")
|
|
}
|
|
v := &volatileStorage{path, id, writer, cmd, make(chan error, 1)}
|
|
go func() { v.done <- cmd.Wait() }()
|
|
ready := make(chan bool, 1)
|
|
go func() {
|
|
var b [1]byte
|
|
_, e := io.ReadFull(reader, b[:])
|
|
reader.Close()
|
|
ready <- e == nil && b[0] == 'R'
|
|
}()
|
|
select {
|
|
case ok := <-ready:
|
|
if ok && v.live() {
|
|
return v, nil
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
}
|
|
v.close()
|
|
return nil, errors.New("cleanup-watchdog-unavailable")
|
|
}
|
|
|
|
func (v *volatileStorage) live() bool {
|
|
select {
|
|
case err := <-v.done:
|
|
v.done <- err
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (v *volatileStorage) close() error {
|
|
err := removeVolatile(v.path, v.id)
|
|
v.pipe.Close()
|
|
select {
|
|
case outcome := <-v.done:
|
|
if outcome != nil && err == nil {
|
|
err = errors.New("cleanup-watchdog-failed")
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
v.command.Process.Kill()
|
|
<-v.done
|
|
if err == nil {
|
|
err = errors.New("cleanup-watchdog-failed")
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Source-reading/writing children cannot survive loss of their exact parent.
|
|
func runChild(cmd *exec.Cmd) error {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGKILL}
|
|
return cmd.Run()
|
|
}
|
|
|
|
func internalProcess(args []string) (int, bool) {
|
|
if len(args) == 0 {
|
|
return 0, false
|
|
}
|
|
switch args[0] {
|
|
case "--cleanup-storage":
|
|
return cleanupProcess(args[1:]), true
|
|
case "--batch-scanner":
|
|
return batchScannerProcess(args[1:]), true
|
|
}
|
|
return 0, false
|
|
}
|