// Copyright 2019 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 flipcall import ( "fmt" "math/bits" "os" "syscall" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/memutil" ) var ( pageSize = os.Getpagesize() pageMask = pageSize - 1 ) func init() { if bits.OnesCount(uint(pageSize)) != 1 { // This is depended on by roundUpToPage(). panic(fmt.Sprintf("system page size (%d) is not a power of 2", pageSize)) } if uintptr(pageSize) < packetHeaderBytes { // This is required since Endpoint.Init() imposes a minimum packet // window size of 1 page. panic(fmt.Sprintf("system page size (%d) is less than packet header size (%d)", pageSize, packetHeaderBytes)) } } // PacketWindowDescriptor represents a packet window, a range of pages in a // shared memory file that is used to exchange packets between partner // Endpoints. type PacketWindowDescriptor struct { // FD is the file descriptor representing the shared memory file. FD int // Offset is the offset into the shared memory file at which the packet // window begins. Offset int64 // Length is the size of the packet window in bytes. Length int } // PacketWindowLengthForDataCap returns the minimum packet window size required // to accommodate datagrams of the given size in bytes. func PacketWindowLengthForDataCap(dataCap uint32) int { return roundUpToPage(int(dataCap) + int(packetHeaderBytes)) } func roundUpToPage(x int) int { return (x + pageMask) &^ pageMask } // A PacketWindowAllocator owns a shared memory file, and allocates packet // windows from it. type PacketWindowAllocator struct { fd int nextAlloc int64 fileSize int64 } // Init must be called on zero-value PacketWindowAllocators before first use. // If it succeeds, Destroy() must be called once the PacketWindowAllocator is // no longer in use. func (pwa *PacketWindowAllocator) Init() error { fd, err := memutil.CreateMemFD("flipcall_packet_windows", linux.MFD_CLOEXEC|linux.MFD_ALLOW_SEALING) if err != nil { return fmt.Errorf("failed to create memfd: %v", err) } // Apply F_SEAL_SHRINK to prevent either party from causing SIGBUS in the // other by truncating the file, and F_SEAL_SEAL to prevent either party // from applying F_SEAL_GROW or F_SEAL_WRITE. if _, _, e := syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd), linux.F_ADD_SEALS, linux.F_SEAL_SHRINK|linux.F_SEAL_SEAL); e != 0 { syscall.Close(fd) return fmt.Errorf("failed to apply memfd seals: %v", e) } pwa.fd = fd return nil } // NewPacketWindowAllocator is a convenience function that returns an // initialized PacketWindowAllocator allocated on the heap. func NewPacketWindowAllocator() (*PacketWindowAllocator, error) { var pwa PacketWindowAllocator if err := pwa.Init(); err != nil { return nil, err } return &pwa, nil } // Destroy releases resources owned by pwa. This invalidates file descriptors // previously returned by pwa.FD() and pwd.Allocate(). func (pwa *PacketWindowAllocator) Destroy() { syscall.Close(pwa.fd) } // FD represents the file descriptor of the shared memory file backing pwa. func (pwa *PacketWindowAllocator) FD() int { return pwa.fd } // Allocate allocates a new packet window of at least the given size and // returns a PacketWindowDescriptor representing it. // // Preconditions: size > 0. func (pwa *PacketWindowAllocator) Allocate(size int) (PacketWindowDescriptor, error) { if size <= 0 { return PacketWindowDescriptor{}, fmt.Errorf("invalid size: %d", size) } // Page-align size to ensure that pwa.nextAlloc remains page-aligned. size = roundUpToPage(size) if size <= 0 { return PacketWindowDescriptor{}, fmt.Errorf("size %d overflows after rounding up to page size", size) } end := pwa.nextAlloc + int64(size) // overflow checked by ensureFileSize if err := pwa.ensureFileSize(end); err != nil { return PacketWindowDescriptor{}, err } start := pwa.nextAlloc pwa.nextAlloc = end return PacketWindowDescriptor{ FD: pwa.fd, Offset: start, Length: size, }, nil } func (pwa *PacketWindowAllocator) ensureFileSize(min int64) error { if min <= 0 { return fmt.Errorf("file size would overflow") } if pwa.fileSize >= min { return nil } newSize := 2 * pwa.fileSize if newSize == 0 { newSize = int64(pageSize) } for newSize < min { newNewSize := newSize * 2 if newNewSize <= 0 { return fmt.Errorf("file size would overflow") } newSize = newNewSize } if err := syscall.Ftruncate(pwa.fd, newSize); err != nil { return fmt.Errorf("ftruncate failed: %v", err) } pwa.fileSize = newSize return nil }