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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
package generate
import "testing"
func TestClassifyRetRead(t *testing.T) {
reads := []string{
"fgetxattr", "flistxattr", "getdents", "getdents64", "getxattr",
"lgetxattr", "listxattr", "llistxattr", "pread64", "preadv",
"preadv2", "process_vm_readv", "read", "readlink", "readlinkat",
"readv", "recvmsg", "recvfrom", "syslog", "mq_timedreceive", "getrandom", "msgrcv",
}
for _, name := range reads {
if got := ClassifyRet("sys_exit_" + name); got != ReadClassified {
t.Errorf("ClassifyRet(sys_exit_%s) = %q, want READ_CLASSIFIED", name, got)
}
}
}
func TestClassifyRetWrite(t *testing.T) {
writes := []string{
"process_vm_writev", "pwrite64", "pwritev", "pwritev2",
"sendmsg", "sendto", "write", "writev", "mq_timedsend", "msgsnd",
}
for _, name := range writes {
if got := ClassifyRet("sys_exit_" + name); got != WriteClassified {
t.Errorf("ClassifyRet(sys_exit_%s) = %q, want WRITE_CLASSIFIED", name, got)
}
}
}
func TestClassifyRetTransfer(t *testing.T) {
transfers := []string{
"copy_file_range", "sendfile64", "splice", "tee", "vmsplice",
}
for _, name := range transfers {
if got := ClassifyRet("sys_exit_" + name); got != TransferClassified {
t.Errorf("ClassifyRet(sys_exit_%s) = %q, want TRANSFER_CLASSIFIED", name, got)
}
}
}
func TestClassifyRetUnclassified(t *testing.T) {
unclassified := []string{
"openat", "close", "rename", "unlink", "fcntl", "dup", "dup2", "dup3",
"mkdir", "rmdir", "chmod", "chown", "chdir", "stat", "lseek",
"truncate", "fallocate", "mmap", "fsync", "flock", "recvmmsg", "sendmmsg",
// syncfs(2) returns int 0/-1 (no byte count); it commits the filesystem
// containing args[0]'s fd and transfers no bytes, so its exit must stay
// UNCLASSIFIED (plain ret_event), like its fsync/fdatasync siblings.
"syncfs",
// gettimeofday(2) returns int 0/-1 (no byte count); its exit carries a
// plain ret_event and must stay UNCLASSIFIED, not a read/write transfer.
"gettimeofday",
// times(2) returns a clock_t tick count (clock ticks since an arbitrary
// point in the past), or (clock_t)-1 on error. That is a tick count, NOT
// a transferred byte count, so its exit must stay UNCLASSIFIED (plain
// ret_event), like its time sibling gettimeofday.
"times",
// rseq(2) returns int 0/-1 on (un)registration of the restartable-
// sequences area; it transfers no bytes, so its exit must stay
// UNCLASSIFIED (plain ret_event), like its KindNull siblings.
"rseq",
// set_mempolicy_home_node(2) sets the home NUMA node for a memory range
// and returns int 0/-1 (no byte count), so its exit carries a plain
// ret_event and must stay UNCLASSIFIED, like its NUMA siblings
// set_mempolicy/mbind/migrate_pages/move_pages.
"set_mempolicy_home_node",
// migrate_pages(2) moves all pages of a process (selected by pid, NOT an
// fd) between NUMA node sets; on success it returns the number of pages
// that could NOT be moved (>=0, zero meaning all moved), or -1 on error.
// That count is a page tally, not a transferred byte count, so its exit
// must stay UNCLASSIFIED (plain ret_event), like its NUMA siblings
// set_mempolicy/mbind/set_mempolicy_home_node and move_pages.
"migrate_pages",
// move_pages(2) is the per-page NUMA sibling of migrate_pages(2); it also
// returns 0/-1 (with per-page status reported via a userspace array, not
// the return value), so its exit likewise stays UNCLASSIFIED.
"move_pages",
// setsid(2) returns the new session ID (a pid_t) on success, or
// (pid_t)-1 on error; that return is a session/process identifier, not a
// transferred byte count. Its exit must stay UNCLASSIFIED (plain
// ret_event), exactly like its pid-returning siblings getsid/getpid/
// getppid (asserted below), so it is never mistaken for a read/write
// byte transfer.
"setsid",
"getsid",
// setpgid(2) sets the process group ID of a process and returns int
// 0 on success or -1 on error — a status code, not a transferred byte
// count. Its exit must stay UNCLASSIFIED (plain ret_event), exactly
// like its session/process-group siblings setsid/getsid above and the
// pid-returning getpid/getppid below.
"setpgid",
"getpid",
"getppid",
// set_tid_address(2) sets the calling thread's clear_child_tid pointer
// and ALWAYS returns the caller's thread ID — it never fails and never
// returns -1 (set_tid_address(2): "always succeeds"). That return is a
// thread identifier (a pid_t/tid), NOT a transferred byte count, so its
// exit must stay UNCLASSIFIED (plain ret_event), exactly like its
// pid/tid-returning Process siblings setsid/getsid/getpid/getppid above.
"set_tid_address",
// bind(2) assigns an address to a socket and returns int 0 on success or
// -1 on error — a status code, NOT a transferred byte count. Its exit must
// stay UNCLASSIFIED (plain ret_event), exactly like its socket-setup
// siblings connect/listen/getsockname/getpeername (asserted alongside it),
// so it is never mistaken for a recvfrom/sendto-style byte transfer.
"bind",
"connect",
"listen",
"getsockname",
"getpeername",
// kexec_load(2) loads a new kernel for later execution by reboot(2) and
// returns long 0 on success or -1 on error — a status code, NOT a
// transferred byte count. Its exit must stay UNCLASSIFIED (plain
// ret_event), exactly like its sibling kexec_file_load and the
// system/admin syscall reboot below.
"kexec_load",
"kexec_file_load",
"reboot",
}
for _, name := range unclassified {
if got := ClassifyRet("sys_exit_" + name); got != Unclassified {
t.Errorf("ClassifyRet(sys_exit_%s) = %q, want UNCLASSIFIED", name, got)
}
}
}
func TestBatchMessageSyscallsDeferredFromRetByteClassification(t *testing.T) {
tests := []string{"recvmmsg", "sendmmsg"}
for _, name := range tests {
t.Run(name, func(t *testing.T) {
if got := ClassifyRet("sys_exit_" + name); got != Unclassified {
t.Fatalf("ClassifyRet(sys_exit_%s) = %q, want %q", name, got, Unclassified)
}
})
}
}
func TestClassifyRetCaseInsensitive(t *testing.T) {
if got := ClassifyRet("sys_exit_READ"); got != ReadClassified {
t.Errorf("ClassifyRet(sys_exit_READ) = %q, want READ_CLASSIFIED", got)
}
}
func TestPhaseAByteClassifiedSyscallsUseExistingRetClassifications(t *testing.T) {
tests := []struct {
name string
want RetClassification
}{
{"recvfrom", ReadClassified},
{"recvmsg", ReadClassified},
{"sendto", WriteClassified},
{"sendmsg", WriteClassified},
{"sendfile64", TransferClassified},
{"splice", TransferClassified},
{"tee", TransferClassified},
{"process_vm_readv", ReadClassified},
{"process_vm_writev", WriteClassified},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ClassifyRet("sys_exit_" + tt.name); got != tt.want {
t.Fatalf("ClassifyRet(sys_exit_%s) = %q, want %q", tt.name, got, tt.want)
}
})
}
}
|