mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
SECCOMP_IOCTL_NOTIF_SEND can fail with ENOENT after suspend/resume without the stub thread dying. The freezer sends a fake signal to userspace tasks during suspend, and seccomp user notifications are reset to INIT and reissued when the blocked syscall is interrupted by a signal. In that case, replying to the old notification ID returns ENOENT. Treat ENOENT as a resync case: wait for the replacement notification and retry the reply.
264 lines
8.6 KiB
Go
264 lines
8.6 KiB
Go
// Copyright 2021 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package systrap
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync/atomic"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/hostarch"
|
|
"gvisor.dev/gvisor/pkg/hostsyscall"
|
|
"gvisor.dev/gvisor/pkg/seccomp"
|
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
|
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
|
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
|
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
|
|
"gvisor.dev/gvisor/pkg/sentry/usage"
|
|
)
|
|
|
|
// The syscall message consists of sentry and stub messages.
|
|
const syscallThreadMessageSize = hostarch.PageSize * 2
|
|
|
|
// syscallThread implements the process of calling syscalls in a stub process.
|
|
//
|
|
// Each syscall thread owns a shared memory region to communicate with the
|
|
// Sentry. This region consists of two pages. The first page called
|
|
// sentryMessage is mapped as read-only in the stub address space. The second
|
|
// page called stubMessage is mapped as read-write in the stub process.
|
|
//
|
|
// Any memory regions that are mapped as read-write in a stub address space can
|
|
// be changed from a user code. This means that we can't trust the content of
|
|
// stubMessage, but it is used to receive a syscall return code. Therefore
|
|
// syscallThread can be used only in these cases:
|
|
// - If a system call never fails (e.g munmap).
|
|
// - If a system call has to return only one know value or if it fails,
|
|
// it doesn't not reveal any data (e.g. mmap).
|
|
type syscallThread struct {
|
|
// subproc is a link to the subprocess which is used to call native
|
|
// system calls and track when a sysmsg thread has to be recreated.
|
|
// Look at getSysmsgThread() for more details.
|
|
subproc *subprocess
|
|
|
|
// thread is a thread identifier.
|
|
thread *thread
|
|
|
|
// stackRange is the range for the sentry syscall message in the memory
|
|
// file.
|
|
stackRange memmap.FileRange
|
|
|
|
// sentryAddr is the address of the shared memory region in the Sentry
|
|
// address space.
|
|
sentryAddr uintptr
|
|
// stubAddr is the address of the shared memory region in the stub
|
|
// address space.
|
|
stubAddr uintptr
|
|
|
|
// sentryMessage is the first page of the share message that can't be
|
|
// modified by the stub thread.
|
|
sentryMessage *syscallSentryMessage
|
|
// stubMessage is the second page of the shared message that can be
|
|
// modified by the stub thread.
|
|
stubMessage *syscallStubMessage
|
|
|
|
seccompNotify *os.File
|
|
seccompNotifyResp linux.SeccompNotifResp
|
|
}
|
|
|
|
func (t *syscallThread) init(seccompNotify bool) error {
|
|
// Allocate a new shared memory message.
|
|
opts := pgalloc.AllocOpts{
|
|
Kind: usage.System,
|
|
Dir: pgalloc.TopDown,
|
|
}
|
|
fr, err := t.subproc.memoryFile.Allocate(syscallThreadMessageSize, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t.stackRange = fr
|
|
t.stubAddr = stubSysmsgStack + sysmsg.PerThreadMemSize*uintptr(t.thread.sysmsgStackID)
|
|
err = t.mapMessageIntoStub()
|
|
if err != nil {
|
|
t.destroy()
|
|
return err
|
|
}
|
|
|
|
if seccompNotify && seccompNotifyIsSupported {
|
|
if t.seccompNotify, err = t.installSeccompNotify(); err != nil {
|
|
t.destroy()
|
|
return fmt.Errorf("failed to install seccomp notify rules: %w", err)
|
|
}
|
|
}
|
|
|
|
// Map the stack into the sentry.
|
|
sentryAddr, errno := hostsyscall.RawSyscall6(
|
|
unix.SYS_MMAP,
|
|
0,
|
|
syscallThreadMessageSize,
|
|
unix.PROT_WRITE|unix.PROT_READ,
|
|
unix.MAP_SHARED|unix.MAP_FILE,
|
|
uintptr(t.subproc.memoryFile.FD()), uintptr(fr.Start))
|
|
if errno != 0 {
|
|
t.destroy()
|
|
return fmt.Errorf("mmap failed: %v", errno)
|
|
}
|
|
t.sentryAddr = sentryAddr
|
|
|
|
t.initRequestReplyAddresses(sentryAddr)
|
|
return nil
|
|
}
|
|
|
|
func (t *syscallThread) destroy() {
|
|
if t.sentryAddr != 0 {
|
|
errno := hostsyscall.RawSyscallErrno(
|
|
unix.SYS_MUNMAP,
|
|
t.sentryAddr,
|
|
syscallThreadMessageSize,
|
|
0)
|
|
if errno != 0 {
|
|
panic(fmt.Sprintf("mumap failed: %v", errno))
|
|
}
|
|
}
|
|
if t.stubAddr != 0 {
|
|
_, err := t.thread.syscallIgnoreInterrupt(&t.thread.initRegs, unix.SYS_MUNMAP,
|
|
arch.SyscallArgument{Value: t.stubAddr},
|
|
arch.SyscallArgument{Value: uintptr(syscallThreadMessageSize)})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("munmap failed: %v", err))
|
|
}
|
|
}
|
|
t.subproc.memoryFile.DecRef(t.stackRange)
|
|
t.subproc.sysmsgStackPool.Put(t.thread.sysmsgStackID)
|
|
}
|
|
|
|
func (t *syscallThread) installSeccompNotify() (*os.File, error) {
|
|
fd, err := t.thread.syscallIgnoreInterrupt(&t.thread.initRegs, seccomp.SYS_SECCOMP,
|
|
arch.SyscallArgument{Value: uintptr(linux.SECCOMP_SET_MODE_FILTER)},
|
|
arch.SyscallArgument{Value: uintptr(linux.SECCOMP_FILTER_FLAG_NEW_LISTENER)},
|
|
arch.SyscallArgument{Value: stubSyscallRules})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
errno := hostsyscall.RawSyscallErrno(unix.SYS_IOCTL, fd, linux.SECCOMP_IOCTL_NOTIF_SET_FLAGS, linux.SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP)
|
|
if errno != 0 {
|
|
t.thread.Debugf("failed to set SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP")
|
|
}
|
|
return os.NewFile(fd, "seccomp_notify"), nil
|
|
}
|
|
|
|
// mapMessageIntoStub maps the syscall message into the stub process address space.
|
|
func (t *syscallThread) mapMessageIntoStub() error {
|
|
// Map sentryMessage as read-only.
|
|
_, err := t.thread.syscallIgnoreInterrupt(&t.thread.initRegs, unix.SYS_MMAP,
|
|
arch.SyscallArgument{Value: t.stubAddr},
|
|
arch.SyscallArgument{Value: uintptr(hostarch.PageSize)},
|
|
arch.SyscallArgument{Value: uintptr(unix.PROT_READ)},
|
|
arch.SyscallArgument{Value: unix.MAP_SHARED | unix.MAP_FILE | unix.MAP_FIXED},
|
|
arch.SyscallArgument{Value: uintptr(t.subproc.memoryFile.FD())},
|
|
arch.SyscallArgument{Value: uintptr(t.stackRange.Start)})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Map stubMessage as read-write.
|
|
_, err = t.thread.syscallIgnoreInterrupt(&t.thread.initRegs, unix.SYS_MMAP,
|
|
arch.SyscallArgument{Value: t.stubAddr + syscallStubMessageOffset},
|
|
arch.SyscallArgument{Value: uintptr(hostarch.PageSize)},
|
|
arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)},
|
|
arch.SyscallArgument{Value: unix.MAP_SHARED | unix.MAP_FILE | unix.MAP_FIXED},
|
|
arch.SyscallArgument{Value: uintptr(t.subproc.memoryFile.FD())},
|
|
arch.SyscallArgument{Value: uintptr(t.stackRange.Start + hostarch.PageSize)})
|
|
return err
|
|
}
|
|
|
|
// attach attaches to the stub thread with ptrace and unlock signals.
|
|
func (t *syscallThread) attach() error {
|
|
if err := t.thread.attach(); err != nil {
|
|
return err
|
|
}
|
|
// We need to unblock signals, because the TRAP signal is used to run
|
|
// syscalls via ptrace.
|
|
t.unmaskAllSignalsAttached()
|
|
return nil
|
|
}
|
|
|
|
const maxErrno = 4095
|
|
|
|
func (t *syscallThread) syscall(sysno uintptr, args ...arch.SyscallArgument) (uintptr, error) {
|
|
if t.subproc.dead.Load() {
|
|
return 0, errDeadSubprocess
|
|
}
|
|
sentryMsg := t.sentryMessage
|
|
stubMsg := t.stubMessage
|
|
sentryMsg.sysno = uint64(sysno)
|
|
for i := 0; i < len(sentryMsg.args); i++ {
|
|
if i < len(args) {
|
|
sentryMsg.args[i] = uint64(args[i].Value)
|
|
} else {
|
|
sentryMsg.args[i] = 0
|
|
}
|
|
}
|
|
|
|
if t.seccompNotify != nil {
|
|
if err := t.notifySeccompThread(); err != nil {
|
|
t.thread.Warningf("%s", err)
|
|
return 0, errDeadSubprocess
|
|
}
|
|
if err := t.waitForSeccompNotify(); err != nil {
|
|
t.thread.Warningf("failed waiting for seccomp notify: %s", err)
|
|
return 0, errDeadSubprocess
|
|
}
|
|
} else {
|
|
// Notify the syscall thread about a new syscall request.
|
|
atomic.AddUint32(&sentryMsg.state, 1)
|
|
futexWakeUint32(&sentryMsg.state)
|
|
|
|
// Wait for reply.
|
|
//
|
|
// futex waits for sentryMsg.state that isn't changed, so it will
|
|
// returns only only when the other side will call FUTEX_WAKE.
|
|
futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
|
|
}
|
|
|
|
errno := -uintptr(stubMsg.ret)
|
|
if errno > 0 && errno < maxErrno {
|
|
return 0, fmt.Errorf("stub syscall (%x, %#v) failed with %w", sysno, args, unix.Errno(errno))
|
|
}
|
|
|
|
return uintptr(stubMsg.ret), nil
|
|
}
|
|
|
|
func (t *syscallThread) notifySeccompThread() error {
|
|
for {
|
|
errno := t.kickSeccompNotify()
|
|
switch errno {
|
|
case 0:
|
|
return nil
|
|
case unix.ENOENT:
|
|
// The notification we are responding to was interrupted before this
|
|
// response was delivered. The kernel will reissue a notification for the
|
|
// same blocked syscall, so wait for it and retry with the refreshed ID.
|
|
if err := t.waitForSeccompNotify(); err != nil {
|
|
return fmt.Errorf("failed resynchronizing seccomp notify: %w", err)
|
|
}
|
|
default:
|
|
t.thread.kill()
|
|
return fmt.Errorf("failed sending request to syscall thread: %w", errno)
|
|
}
|
|
}
|
|
}
|