Files
Ayush RanjanandAlessio Ricci Toniolo 83f9a0d16e Add RDMA uverbs device proxy
Add pkg/sentry/devices/rdmaproxy, a passthrough proxy for
/dev/infiniband/uverbs* that lets a sandboxed application drive RDMA verbs
against the host's ib_uverbs devices.

The proxy forwards the modern RDMA_VERBS_IOCTL to a host uverbs FD. Every
request is validated against an explicit per-(object, method) schema, and each
attribute is translated strictly by its schema-declared ABI type rather than by
probing guest memory: input pointers are copied into sentry buffers, output
buffers are staged and copied back, object handles are passed through, and file
descriptors are translated between the sandbox and the host. Interpreting an
attribute's direction from the fixed kernel ABI (additive-only, stable since
Linux 4.20) instead of from the guest's memory is what prevents a malicious
guest from coercing the proxy into an out-of-bounds copy.

DMA buffers referenced by memory-region registrations and by the vendor
driver-private CQ/QP data are pinned and mirrored into the sentry's address
space so the host kernel can pin_user_pages on them. The mlx5 (ConnectX)
driver-private layout lives in a vendor plug-in, pkg/sentry/devices/rdmaproxy/
cxproxy, registered with the core at init time.

A new MemoryManager.FindVMARange helper bounds a driver-private DMA buffer whose
backing region the guest describes by start address only.

Co-authored-by: Alessio Ricci Toniolo <[email protected]>
2026-08-02 23:31:51 +00:00

117 lines
4.3 KiB
Go

// Copyright 2024 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 cxproxy implements the rdmaproxy.Driver plug-in for Mellanox/NVIDIA
// ConnectX adapters (mlx5_core / mlx5_ib). It mirrors the CQ/QP work-queue and
// doorbell DMA buffers referenced by the mlx5 driver-private UHW payload.
//
// To make this driver available, Init() must be called.
package cxproxy
import (
"gvisor.dev/gvisor/pkg/abi/ib"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/devices/rdmaproxy"
"gvisor.dev/gvisor/pkg/sentry/kernel"
)
// driverName matches the DRIVER= field of
// /sys/class/infiniband/<ibdev>/device/uevent for ConnectX adapters bound to
// the upstream mlx5 stack. runsc looks this up via rdmaproxy.LookupDriver and
// attaches the resulting driver to the corresponding uverbs device.
const driverName = "mlx5_core"
// cxDriver is the rdmaproxy.Driver implementation for ConnectX adapters.
type cxDriver struct{}
// Name implements rdmaproxy.Driver.Name.
func (cxDriver) Name() string { return driverName }
// PrepareCreateDMA implements rdmaproxy.Driver.PrepareCreateDMA. It mirrors the
// work-queue buffer (buf_addr) and doorbell page (db_addr) referenced by the
// mlx5 driver-private payload, rewriting both fields to their sentry-side
// mappings before the ioctl is forwarded to the host kernel.
//
// mlx5 supplies no explicit buffer length in the payload, so the work-queue
// buffer is mirrored from buf_addr to the end of its VMA (the buffer is a
// single mlx5-allocated mapping); rdmaproxy caps the pinned length. The
// doorbell is a small record within one page, so a single page is mirrored.
func (cxDriver) PrepareCreateDMA(t *kernel.Task, uhwIn []byte) (*rdmaproxy.PinnedDMABufs, error) {
var prefix ib.Mlx5CreatePrefix
if len(uhwIn) < prefix.SizeBytes() {
return nil, nil
}
prefix.UnmarshalBytes(uhwIn)
var bufs rdmaproxy.PinnedDMABufs
var cu cleanup.Cleanup
defer cu.Clean()
if prefix.BufAddr != 0 {
vmaRange, err := t.MemoryManager().FindVMARange(hostarch.Addr(prefix.BufAddr))
if err != nil {
return nil, err
}
length := uint64(vmaRange.End) - prefix.BufAddr
mp, sentryVA, err := rdmaproxy.MirrorAppPages(t, prefix.BufAddr, length)
if err != nil {
return nil, err
}
bufs.Buf = mp
cu.Add(func() { mp.Release(t) })
prefix.BufAddr = uint64(sentryVA)
}
if prefix.DBAddr != 0 {
mp, sentryVA, err := rdmaproxy.MirrorAppPages(t, prefix.DBAddr, hostarch.PageSize)
if err != nil {
return nil, err
}
bufs.DB = mp
cu.Add(func() { mp.Release(t) })
prefix.DBAddr = uint64(sentryVA)
}
cu.Release()
prefix.MarshalBytes(uhwIn)
return &bufs, nil
}
// Schemas implements rdmaproxy.Driver.Schemas. It models the mlx5 UAR (User
// Access Region — the CQ/QP doorbell page) object: alloc returns an mmap
// offset that the app maps through the uverbs FD via the generic passthrough,
// so no address translation is needed here.
func (cxDriver) Schemas() map[uint32]*rdmaproxy.MethodSchema {
return map[uint32]*rdmaproxy.MethodSchema{
rdmaproxy.SchemaKey(ib.MLX5_IB_OBJECT_UAR, ib.MLX5_IB_METHOD_UAR_OBJ_ALLOC): {
Attrs: map[uint16]rdmaproxy.AttrType{
ib.MLX5_IB_ATTR_UAR_OBJ_ALLOC_HANDLE: rdmaproxy.AttrIdr,
ib.MLX5_IB_ATTR_UAR_OBJ_ALLOC_TYPE: rdmaproxy.AttrPtrIn,
ib.MLX5_IB_ATTR_UAR_OBJ_ALLOC_MMAP_OFFSET: rdmaproxy.AttrPtrOut,
ib.MLX5_IB_ATTR_UAR_OBJ_ALLOC_MMAP_LENGTH: rdmaproxy.AttrPtrOut,
ib.MLX5_IB_ATTR_UAR_OBJ_ALLOC_PAGE_ID: rdmaproxy.AttrPtrOut,
},
},
rdmaproxy.SchemaKey(ib.MLX5_IB_OBJECT_UAR, ib.MLX5_IB_METHOD_UAR_OBJ_DESTROY): {
Attrs: map[uint16]rdmaproxy.AttrType{
ib.MLX5_IB_ATTR_UAR_OBJ_DESTROY_HANDLE: rdmaproxy.AttrIdr,
},
},
}
}
// Init registers the ConnectX (mlx5) driver plug-in with the rdmaproxy core.
func Init() { rdmaproxy.RegisterDriver(cxDriver{}) }