summaryrefslogtreecommitdiff
path: root/integrationtests/cmd/ioworkload/scenario_pidfd.go
blob: 1ac7c4dbb801787f76c94a30eb64a1561bed38bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"syscall"
	"time"
)

// pidfdGetfdSuccess duplicates an existing file descriptor through pidfd_getfd.
func pidfdGetfdSuccess() error {
	dir, cleanup, err := makeTempDir("pidfd-getfd-success")
	if err != nil {
		return err
	}
	defer cleanup()

	path := filepath.Join(dir, "pidfd-getfd-source.txt")
	fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644)
	if err != nil {
		return fmt.Errorf("open source: %w", err)
	}
	defer syscall.Close(fd)

	pidfd, err := pidfdOpen(os.Getpid(), 0)
	if err != nil {
		return fmt.Errorf("pidfd_open self: %w", err)
	}
	defer syscall.Close(pidfd)

	dupFd, err := pidfdGetfd(pidfd, fd, 0)
	if err != nil {
		return fmt.Errorf("pidfd_getfd: %w", err)
	}

	if _, err := syscall.Write(dupFd, []byte("via pidfd_getfd")); err != nil {
		syscall.Close(dupFd)
		return fmt.Errorf("write dup fd: %w", err)
	}

	// Keep the duplicated fd alive briefly so eventloop can resolve /proc fd path.
	time.Sleep(500 * time.Millisecond)
	if err := syscall.Close(dupFd); err != nil {
		return fmt.Errorf("close dup fd: %w", err)
	}
	return nil
}

// pidfdGetfdFailure performs a guaranteed-failing pidfd_getfd call while
// also probing a cross-process call that may fail under ptrace/Yama policy.
func pidfdGetfdFailure() error {
	pidfd, err := pidfdOpen(os.Getpid(), 0)
	if err != nil {
		return fmt.Errorf("pidfd_open self: %w", err)
	}
	defer syscall.Close(pidfd)

	// Best-effort probe. Depending on kernel ptrace/Yama policy, this may fail
	// with EPERM/EACCES; if it succeeds we close the returned fd and continue.
	if initPidfd, err := pidfdOpen(1, 0); err == nil {
		func() {
			defer syscall.Close(initPidfd)
			if probeFd, err := pidfdGetfd(initPidfd, 1, 0); err == nil {
				syscall.Close(probeFd)
			}
		}()
	}

	_, err = pidfdGetfd(pidfd, 99999, 0)
	if err == nil {
		return fmt.Errorf("expected pidfd_getfd with invalid source fd to fail")
	}
	return nil
}

func pidfdOpen(pid int, flags uintptr) (int, error) {
	fd, _, errno := syscall.Syscall(pidfdOpenSyscallNr(), uintptr(pid), flags, 0)
	if errno != 0 {
		return 0, errno
	}
	return int(fd), nil
}

func pidfdGetfd(pidfd int, targetFd int, flags uintptr) (int, error) {
	fd, _, errno := syscall.Syscall(
		pidfdGetfdSyscallNr(),
		uintptr(pidfd),
		uintptr(targetFd),
		flags,
	)
	if errno != 0 {
		return 0, errno
	}
	return int(fd), nil
}

func pidfdOpenSyscallNr() uintptr {
	// Go's syscall package does not expose pidfd constants on all toolchains.
	if runtime.GOARCH == "amd64" {
		return 434
	}
	if runtime.GOARCH == "arm64" {
		return 434
	}
	panic("pidfd_open syscall number not defined for GOARCH=" + runtime.GOARCH)
}

func pidfdGetfdSyscallNr() uintptr {
	// Go's syscall package does not expose pidfd constants on all toolchains.
	if runtime.GOARCH == "amd64" {
		return 438
	}
	if runtime.GOARCH == "arm64" {
		return 438
	}
	panic("pidfd_getfd syscall number not defined for GOARCH=" + runtime.GOARCH)
}