Skip to content

Commit 1c7f51f

Browse files
avagingvisor-bot
authored andcommitted
ioctl_sniffer: use seccomp_unotify to trap ioctl-s
PiperOrigin-RevId: 806465209
1 parent 0bb00ae commit 1c7f51f

File tree

13 files changed

+325
-11
lines changed

13 files changed

+325
-11
lines changed

pkg/abi/linux/ptrace_amd64.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,7 @@ func (p *PtraceRegs) StackPointer() uint64 {
6767
func (p *PtraceRegs) SetStackPointer(sp uint64) {
6868
p.Rsp = sp
6969
}
70+
71+
func (p *PtraceRegs) SyscallRet() uint64 {
72+
return p.Rax
73+
}

pkg/abi/linux/ptrace_arm64.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ func (p *PtraceRegs) StackPointer() uint64 {
7575
func (p *PtraceRegs) SetStackPointer(sp uint64) {
7676
p.Sp = sp
7777
}
78+
79+
func (p *PtraceRegs) SyscallRet() uint64 {
80+
return p.Regs[0]
81+
}

pkg/abi/linux/seccomp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131

3232
SECCOMP_FILTER_FLAG_TSYNC = 1
3333
SECCOMP_FILTER_FLAG_NEW_LISTENER = 1 << 3
34+
SECCOMP_FILTER_FLAG_TSYNC_ESRCH = 1 << 4
3435

3536
SECCOMP_USER_NOTIF_FLAG_CONTINUE = 1
3637

pkg/seccomp/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ go_library(
2020
deps = [
2121
"//pkg/abi/linux",
2222
"//pkg/bpf",
23+
"//pkg/hostsyscall",
2324
"//pkg/log",
2425
"@org_golang_x_sys//unix:go_default_library",
2526
],

pkg/seccomp/seccomp.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,14 @@ func Install(rules SyscallRules, denyRules SyscallRules, options ProgramOptions)
8282
}
8383

8484
// Perform the actual installation.
85-
if err := SetFilter(instrs); err != nil {
86-
return fmt.Errorf("failed to set filter: %v", err)
85+
if options.LogNotifications {
86+
if err := SetFilterAndLogNotifications(instrs, options); err != nil {
87+
return fmt.Errorf("failed to set filter: %v", err)
88+
}
89+
} else {
90+
if err := SetFilter(instrs); err != nil {
91+
return fmt.Errorf("failed to set filter: %v", err)
92+
}
8793
}
8894

8995
log.Infof("Seccomp filters installed.")
@@ -321,6 +327,17 @@ type ProgramOptions struct {
321327
// called >10% of the time out of all syscalls made).
322328
// It is ordered from most frequent to least frequent.
323329
HotSyscalls []uintptr
330+
331+
// LogNotifications enables logging of user notifications at the
332+
// warning level. Syscalls triggered notifications are not blocked.
333+
LogNotifications bool
334+
335+
// NotificationCallback is called when a blocked syscall is triggered.
336+
NotificationCallback NotificationCallback
337+
338+
// NotifyFDNum is a target number for the seccomp notify descriptor.
339+
// It can be used in filters to allow ioctl-s on this file descriptor.
340+
NotifyFDNum int
324341
}
325342

326343
// DefaultProgramOptions returns the default program options.
@@ -333,6 +350,7 @@ func DefaultProgramOptions() ProgramOptions {
333350
DefaultAction: action,
334351
BadArchAction: action,
335352
Optimize: true,
353+
NotifyFDNum: -1,
336354
}
337355
}
338356

pkg/seccomp/seccomp_unsafe.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,153 @@ package seccomp
1616

1717
import (
1818
"fmt"
19+
"os"
1920
"runtime"
21+
"syscall"
2022
"unsafe"
2123

2224
"golang.org/x/sys/unix"
2325
"gvisor.dev/gvisor/pkg/abi/linux"
2426
"gvisor.dev/gvisor/pkg/bpf"
27+
"gvisor.dev/gvisor/pkg/hostsyscall"
28+
"gvisor.dev/gvisor/pkg/log"
2529
)
2630

31+
// NotificationCallback is a callback which is called when a blocked syscall is triggered.
32+
type NotificationCallback func(f *os.File, req linux.SeccompNotif, ret int)
33+
34+
// SetFilterAndLogNotifications installs the given BPF program and logs user
35+
// notifications triggered by the seccomp filter. It allows the triggering
36+
// syscalls to proceed without being blocked.
37+
//
38+
// This function is intended for debugging seccomp filter violations and should
39+
// not be used in production environments.
40+
//
41+
// Note: It spawns a background goroutine to monitor a seccomp file descriptor
42+
// and log any received notifications.
43+
func SetFilterAndLogNotifications(
44+
instrs []bpf.Instruction,
45+
options ProgramOptions,
46+
) error {
47+
// PR_SET_NO_NEW_PRIVS is required in order to enable seccomp. See
48+
// seccomp(2) for details.
49+
//
50+
// PR_SET_NO_NEW_PRIVS is specific to the calling thread, not the whole
51+
// thread group, so between PR_SET_NO_NEW_PRIVS and seccomp() below we must
52+
// remain on the same thread. no_new_privs will be propagated to other
53+
// threads in the thread group by seccomp(SECCOMP_FILTER_FLAG_TSYNC), in
54+
// kernel/seccomp.c:seccomp_sync_threads().
55+
runtime.LockOSThread()
56+
defer runtime.UnlockOSThread()
57+
if _, _, errno := unix.RawSyscall6(unix.SYS_PRCTL, linux.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0, 0); errno != 0 {
58+
return errno
59+
}
60+
61+
sockProg := linux.SockFprog{
62+
Len: uint16(len(instrs)),
63+
Filter: (*linux.BPFInstruction)(unsafe.Pointer(&instrs[0])),
64+
}
65+
flags := linux.SECCOMP_FILTER_FLAG_TSYNC |
66+
linux.SECCOMP_FILTER_FLAG_NEW_LISTENER |
67+
linux.SECCOMP_FILTER_FLAG_TSYNC_ESRCH | (1 << 5)
68+
fd, errno := seccomp(linux.SECCOMP_SET_MODE_FILTER, uint32(flags), unsafe.Pointer(&sockProg))
69+
if errno != 0 {
70+
return errno
71+
}
72+
if options.NotifyFDNum > 0 {
73+
if err := unix.Dup2(int(fd), options.NotifyFDNum); err != nil {
74+
panic(fmt.Sprintf("dup2 %d -> %d: %v", fd, options.NotifyFDNum, err))
75+
}
76+
unix.Close(int(fd))
77+
fd = uintptr(options.NotifyFDNum)
78+
}
79+
f := os.NewFile(fd, "seccomp_notify")
80+
go func() {
81+
// LockOSThread should help minimizing interactions with the scheduler.
82+
runtime.LockOSThread()
83+
defer runtime.UnlockOSThread()
84+
var (
85+
req linux.SeccompNotif
86+
resp linux.SeccompNotifResp
87+
)
88+
for {
89+
req = linux.SeccompNotif{}
90+
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(f.Fd()),
91+
uintptr(linux.SECCOMP_IOCTL_NOTIF_RECV),
92+
uintptr(unsafe.Pointer(&req)))
93+
if errno != 0 {
94+
if errno == unix.EINTR {
95+
continue
96+
}
97+
panic(fmt.Sprintf("SECCOMP_IOCTL_NOTIF_RECV failed with %d", errno))
98+
}
99+
100+
attached := true
101+
if errno := hostsyscall.RawSyscallErrno(unix.SYS_PTRACE, unix.PTRACE_ATTACH, uintptr(req.Pid), 0); errno != 0 {
102+
log.Warningf("unable to attach: %v", errno)
103+
attached = false
104+
}
105+
resp = linux.SeccompNotifResp{
106+
ID: req.ID,
107+
Flags: linux.SECCOMP_USER_NOTIF_FLAG_CONTINUE,
108+
}
109+
errno = hostsyscall.RawSyscallErrno(unix.SYS_IOCTL, uintptr(f.Fd()),
110+
uintptr(linux.SECCOMP_IOCTL_NOTIF_SEND),
111+
uintptr(unsafe.Pointer(&resp)))
112+
if errno != 0 {
113+
panic(fmt.Sprintf("SECCOMP_IOCTL_NOTIF_SEND failed with %d", errno))
114+
}
115+
if !attached {
116+
if options.NotificationCallback != nil {
117+
options.NotificationCallback(f, req, 0)
118+
} else {
119+
log.Warningf("Seccomp violation: %#v", req)
120+
}
121+
continue
122+
}
123+
for {
124+
var info unix.Siginfo
125+
errno := unix.Waitid(unix.P_PID, int(req.Pid), &info, syscall.WALL|syscall.WEXITED, nil)
126+
if errno == syscall.EINTR {
127+
continue
128+
} else if errno != nil {
129+
log.Warningf("failed to wait for the child process: %v", errno)
130+
}
131+
break
132+
}
133+
ret := 0
134+
{
135+
var regs linux.PtraceRegs
136+
iovec := unix.Iovec{
137+
Base: (*byte)(unsafe.Pointer(&regs)),
138+
Len: uint64(unsafe.Sizeof(regs)),
139+
}
140+
_, _, errno := unix.RawSyscall6(
141+
unix.SYS_PTRACE,
142+
unix.PTRACE_GETREGSET,
143+
uintptr(req.Pid),
144+
linux.NT_PRSTATUS,
145+
uintptr(unsafe.Pointer(&iovec)),
146+
0, 0)
147+
if errno != 0 {
148+
log.Warningf("unable to get registers: %s", errno)
149+
}
150+
ret = int(regs.SyscallRet())
151+
}
152+
153+
if options.NotificationCallback != nil {
154+
options.NotificationCallback(f, req, ret)
155+
} else {
156+
log.Warningf("Seccomp violation: %#v", req)
157+
}
158+
if errno := hostsyscall.RawSyscallErrno(unix.SYS_PTRACE, unix.PTRACE_DETACH, uintptr(req.Pid), 0); errno != 0 {
159+
panic(fmt.Sprintf("unable to detach: %v", errno))
160+
}
161+
}
162+
}()
163+
return nil
164+
}
165+
27166
// SetFilter installs the given BPF program.
28167
func SetFilter(instrs []bpf.Instruction) error {
29168
// PR_SET_NO_NEW_PRIVS is required in order to enable seccomp. See

pkg/test/dockerutil/gpu.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ func (sgo *SniffGPUOpts) GPUCapabilities() string {
193193

194194
// prepend prepends the sniffer arguments to the given command.
195195
func (sgo *SniffGPUOpts) prepend(argv []string) []string {
196+
if *runtime != "" && *runtime != "runc" {
197+
// ioctl_sniffer isn't supported in gVisor.
198+
return argv
199+
}
196200
if sgo.DisableSnifferReason != "" {
197201
return argv
198202
}

runsc/boot/filter/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ go_library(
3232
"//pkg/seccomp/precompiledseccomp",
3333
"//pkg/sync",
3434
"//runsc/boot/filter/config",
35+
"@org_golang_x_sys//unix:go_default_library",
3536
],
3637
)
3738

runsc/boot/filter/filter.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import (
2929
// If you suspect the Sentry is getting killed due to a seccomp violation,
3030
// change this to `true` to get a panic stack trace when there is a
3131
// violation.
32-
const debugFilter = false
32+
const (
33+
debugFilterPanic = false // Panic on seccomp violation with stack trace.
34+
debugFilterWarn = false // Log seccomp violation, but continue program execution.
35+
)
3336

3437
// Options is a re-export of the config Options type under this package.
3538
type Options = config.Options
@@ -41,7 +44,7 @@ func Install(opt Options) error {
4144
}
4245
key := opt.ConfigKey()
4346
precompiled, usePrecompiled := GetPrecompiled(key)
44-
if usePrecompiled && !debugFilter {
47+
if usePrecompiled && !debugFilterPanic && !debugFilterWarn {
4548
vars := opt.Vars()
4649
log.Debugf("Loaded precompiled seccomp instructions for options %v, using variables: %v", key, vars)
4750
insns, err := precompiled.RenderInstructions(vars)
@@ -51,9 +54,13 @@ func Install(opt Options) error {
5154
return seccomp.SetFilter(insns)
5255
}
5356
seccompOpts := config.SeccompOptions(opt)
54-
if debugFilter {
57+
if debugFilterPanic {
5558
log.Infof("Seccomp filter debugging is enabled; seccomp failures will result in a panic stack trace.")
5659
seccompOpts.DefaultAction = linux.SECCOMP_RET_TRAP
60+
} else if debugFilterWarn {
61+
log.Infof("Seccomp filter debugging is enabled; seccomp failures will be logged")
62+
seccompOpts.DefaultAction = linux.SECCOMP_RET_USER_NOTIF
63+
seccompOpts.LogNotifications = true
5764
} else {
5865
log.Infof("No precompiled program found for config options %v, building seccomp program from scratch. This may slow down container startup.", key)
5966
if log.IsLogging(log.Debug) {

tools/ioctl_sniffer/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ go_binary(
5454
"//:sandbox",
5555
],
5656
deps = [
57+
"//pkg/abi/linux",
5758
"//pkg/log",
59+
"//pkg/seccomp",
5860
"//tools/ioctl_sniffer/sniffer",
61+
"@org_golang_x_sys//unix:go_default_library",
5962
],
6063
)

0 commit comments

Comments
 (0)