diff --git a/virtcontainers/utils/utils_linux.go b/virtcontainers/utils/utils_linux.go new file mode 100644 index 0000000000..5d73c2ad1f --- /dev/null +++ b/virtcontainers/utils/utils_linux.go @@ -0,0 +1,89 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package utils + +import ( + "crypto/rand" + "fmt" + "math/big" + "os" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +// from +// VHOST_VSOCK_SET_GUEST_CID = _IOW(VHOST_VIRTIO, 0x60, __u64) +const ioctlVhostVsockSetGuestCid = 0x4008AF60 + +var ioctlFunc = ioctl + +var maxUInt uint32 = 1<<32 - 1 + +func ioctl(fd uintptr, request int, arg1 uint32) error { + if _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + fd, + uintptr(request), + uintptr(unsafe.Pointer(&arg1)), + ); errno != 0 { + return os.NewSyscallError("ioctl", fmt.Errorf("%d", int(errno))) + } + + return nil +} + +// FindContextID finds a unique context ID by generating a random number between 3 and max unsigned int (maxUint). +// Using the ioctl VHOST_VSOCK_SET_GUEST_CID, findContextID asks to the kernel if the given +// context ID (N) is available, when the context ID is not available, incrementing by 1 findContextID +// iterates from N to maxUint until an available context ID is found, otherwise decrementing by 1 +// findContextID iterates from N to 3 until an available context ID is found, this is the last chance +// to find a context ID available. +// On success vhost file and a context ID greater or equal than 3 are returned, otherwise 0 and an error are returned. +// vhost file can be used to send vhost file decriptor to QEMU. It's the caller's responsibility to +// close vhost file descriptor. +// +// Benefits of using random context IDs: +// - Reduce the probability of a *DoS attack*, since other processes don't know whatis the initial context ID +// used by findContextID to find a context ID available +// +func FindContextID() (*os.File, uint32, error) { + // context IDs 0x0, 0x1 and 0x2 are reserved, 0x3 is the first context ID usable. + var firstContextID uint32 = 0x3 + var contextID = firstContextID + + // Generate a random number + n, err := rand.Int(rand.Reader, big.NewInt(int64(maxUInt))) + if err == nil && n.Int64() >= int64(firstContextID) { + contextID = uint32(n.Int64()) + } + + // Open vhost-vsock device to check what context ID is available. + // This file descriptor holds/locks the context ID and it should be + // inherited by QEMU process. + vsockFd, err := os.OpenFile(VHostVSockDevicePath, syscall.O_RDWR, 0666) + if err != nil { + return nil, 0, err + } + + // Looking for the first available context ID. + for cid := contextID; cid <= maxUInt; cid++ { + if err := ioctlFunc(vsockFd.Fd(), ioctlVhostVsockSetGuestCid, cid); err == nil { + return vsockFd, cid, nil + } + } + + // Last chance to get a free context ID. + for cid := contextID - 1; cid >= firstContextID; cid-- { + if err := ioctlFunc(vsockFd.Fd(), ioctlVhostVsockSetGuestCid, cid); err == nil { + return vsockFd, cid, nil + } + } + + vsockFd.Close() + return nil, 0, fmt.Errorf("Could not get a unique context ID for the vsock") +} diff --git a/virtcontainers/utils/utils_linux_test.go b/virtcontainers/utils/utils_linux_test.go new file mode 100644 index 0000000000..758f9609dc --- /dev/null +++ b/virtcontainers/utils/utils_linux_test.go @@ -0,0 +1,35 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package utils + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindContextID(t *testing.T) { + assert := assert.New(t) + + ioctlFunc = func(fd uintptr, request int, arg1 uint32) error { + return errors.New("ioctl") + } + + orgVHostVSockDevicePath := VHostVSockDevicePath + orgMaxUInt := maxUInt + defer func() { + VHostVSockDevicePath = orgVHostVSockDevicePath + maxUInt = orgMaxUInt + }() + VHostVSockDevicePath = "/dev/null" + maxUInt = uint32(1000000) + + f, cid, err := FindContextID() + assert.Nil(f) + assert.Zero(cid) + assert.Error(err) +}