Files

456 lines
13 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 mq provides an implementation for POSIX message queues.
package mq
import (
"bytes"
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/waiter"
)
// AccessType is the access type passed to mq_open.
type AccessType int
// Possible access types.
const (
ReadOnly AccessType = iota
WriteOnly
ReadWrite
)
// MaxName is the maximum size for a queue name.
const MaxName = 255
const (
maxPriority = linux.MQ_PRIO_MAX - 1 // Highest possible message priority.
maxQueuesDefault = linux.DFLT_QUEUESMAX // Default max number of queues.
maxMsgDefault = linux.DFLT_MSG // Default max number of messages per queue.
maxMsgMin = linux.MIN_MSGMAX // Min value for max number of messages per queue.
maxMsgLimit = linux.DFLT_MSGMAX // Limit for max number of messages per queue.
maxMsgHardLimit = linux.HARD_MSGMAX // Hard limit for max number of messages per queue.
msgSizeDefault = linux.DFLT_MSGSIZE // Default max message size.
msgSizeMin = linux.MIN_MSGSIZEMAX // Min value for max message size.
msgSizeLimit = linux.DFLT_MSGSIZEMAX // Limit for max message size.
msgSizeHardLimit = linux.HARD_MSGSIZEMAX // Hard limit for max message size.
)
// Registry is a POSIX message queue registry.
//
// Unlike SysV utilities, Registry is not map-based. It uses a provided
// RegistryImpl backed by a virtual filesystem to implement registry operations.
//
// +stateify savable
type Registry struct {
// userNS is the user namespace containing this registry. Immutable.
userNS *auth.UserNamespace
// mu protects all fields below.
mu sync.Mutex `state:"nosave"`
// impl is an implementation of several message queue utilities needed by
// the registry. impl should be provided by mqfs.
impl RegistryImpl
}
// RegistryImpl defines utilities needed by a Registry to provide actual
// registry implementation. It works mainly as an abstraction layer used by
// Registry to avoid dealing directly with the filesystem. RegistryImpl should
// be implemented by mqfs and provided to Registry at initialization.
type RegistryImpl interface {
// Get searches for a queue with the given name, if it exists, the queue is
// used to create a new FD, return it and return true. If the queue doesn't
// exist, return false and no error. An error is returned if creation fails.
Get(ctx context.Context, name string, access AccessType, block bool, flags uint32) (*vfs.FileDescription, bool, error)
// New creates a new inode and file description using the given queue,
// inserts the inode into the filesystem tree using the given name, and
// returns the file description. An error is returned if creation fails, or
// if the name already exists.
New(ctx context.Context, name string, q *Queue, access AccessType, block bool, perm linux.FileMode, flags uint32) (*vfs.FileDescription, error)
// Unlink removes the queue with given name from the registry, and returns
// an error if the name doesn't exist.
Unlink(ctx context.Context, name string) error
// Destroy destroys the registry.
Destroy(context.Context)
}
// NewRegistry returns a new, initialized message queue registry. NewRegistry
// should be called when a new message queue filesystem is created, once per
// IPCNamespace.
func NewRegistry(userNS *auth.UserNamespace, impl RegistryImpl) *Registry {
return &Registry{
userNS: userNS,
impl: impl,
}
}
// OpenOpts holds the options passed to FindOrCreate.
type OpenOpts struct {
Name string
Access AccessType
Create bool
Exclusive bool
Block bool
}
// FindOrCreate creates a new POSIX message queue or opens an existing queue.
// See mq_open(2).
func (r *Registry) FindOrCreate(ctx context.Context, opts OpenOpts, mode linux.FileMode, attr *linux.MqAttr) (*vfs.FileDescription, error) {
// mq_overview(7) mentions that: "Each message queue is identified by a name
// of the form '/somename'", but the mq_open(3) man pages mention:
// "The mq_open() library function is implemented on top of a system call
// of the same name. The library function performs the check that the
// name starts with a slash (/), giving the EINVAL error if it does not.
// The kernel system call expects name to contain no preceding slash, so
// the C library function passes name without the preceding slash (i.e.,
// name+1) to the system call."
// So we don't need to check it.
if len(opts.Name) == 0 {
return nil, linuxerr.ENOENT
}
if len(opts.Name) > MaxName {
return nil, linuxerr.ENAMETOOLONG
}
if strings.ContainsRune(opts.Name, '/') {
return nil, linuxerr.EACCES
}
if opts.Name == "." || opts.Name == ".." {
return nil, linuxerr.EINVAL
}
// Construct status flags.
var flags uint32
if opts.Block {
flags = linux.O_NONBLOCK
}
switch opts.Access {
case ReadOnly:
flags = flags | linux.O_RDONLY
case WriteOnly:
flags = flags | linux.O_WRONLY
case ReadWrite:
flags = flags | linux.O_RDWR
}
r.mu.Lock()
defer r.mu.Unlock()
fd, ok, err := r.impl.Get(ctx, opts.Name, opts.Access, opts.Block, flags)
if err != nil {
return nil, err
}
if ok {
if opts.Create && opts.Exclusive {
// "Both O_CREAT and O_EXCL were specified in oflag, but a queue
// with this name already exists."
fd.DecRef(ctx)
return nil, linuxerr.EEXIST
}
return fd, nil
}
if !opts.Create {
// "The O_CREAT flag was not specified in oflag, and no queue with this name
// exists."
return nil, linuxerr.ENOENT
}
q, err := r.newQueueLocked(auth.CredentialsFromContext(ctx), mode, attr)
if err != nil {
return nil, err
}
return r.impl.New(ctx, opts.Name, q, opts.Access, opts.Block, mode.Permissions(), flags)
}
// newQueueLocked creates a new queue using the given attributes. If attr is nil
// return a queue with default values, otherwise use attr to create a new queue,
// and return an error if attributes are invalid.
func (r *Registry) newQueueLocked(creds *auth.Credentials, mode linux.FileMode, attr *linux.MqAttr) (*Queue, error) {
if attr == nil {
return &Queue{
ownerUID: creds.EffectiveKUID,
ownerGID: creds.EffectiveKGID,
mode: mode,
maxMessageCount: int64(maxMsgDefault),
maxMessageSize: uint64(msgSizeDefault),
}, nil
}
// "O_CREAT was specified in oflag, and attr was not NULL, but
// attr->mq_maxmsg or attr->mq_msqsize was invalid. Both of these fields
// these fields must be greater than zero. In a process that is
// unprivileged (does not have the CAP_SYS_RESOURCE capability),
// attr->mq_maxmsg must be less than or equal to the msg_max limit, and
// attr->mq_msgsize must be less than or equal to the msgsize_max limit.
// In addition, even in a privileged process, attr->mq_maxmsg cannot
// exceed the HARD_MAX limit." - man mq_open(3).
if attr.MqMaxmsg <= 0 || attr.MqMsgsize <= 0 {
return nil, linuxerr.EINVAL
}
if attr.MqMaxmsg > maxMsgHardLimit || (!creds.HasCapabilityIn(linux.CAP_SYS_RESOURCE, r.userNS) && (attr.MqMaxmsg > maxMsgLimit || attr.MqMsgsize > msgSizeLimit)) {
return nil, linuxerr.EINVAL
}
return &Queue{
ownerUID: creds.EffectiveKUID,
ownerGID: creds.EffectiveKGID,
mode: mode,
maxMessageCount: attr.MqMaxmsg,
maxMessageSize: uint64(attr.MqMsgsize),
}, nil
}
// Remove removes the queue with the given name from the registry. See
// mq_unlink(2).
func (r *Registry) Remove(ctx context.Context, name string) error {
if len(name) > MaxName {
return linuxerr.ENAMETOOLONG
}
r.mu.Lock()
defer r.mu.Unlock()
return r.impl.Unlink(ctx, name)
}
// Destroy destroys the registry and releases all held references.
func (r *Registry) Destroy(ctx context.Context) {
r.mu.Lock()
defer r.mu.Unlock()
r.impl.Destroy(ctx)
}
// Impl returns RegistryImpl inside r.
func (r *Registry) Impl() RegistryImpl {
return r.impl
}
// Queue represents a POSIX message queue.
//
// +stateify savable
type Queue struct {
// ownerUID is the registry's owner's UID. Immutable.
ownerUID auth.KUID
// ownerGID is the registry's owner's GID. Immutable.
ownerGID auth.KGID
// mode is the registry's access permissions. Immutable.
mode linux.FileMode
// mu protects all the fields below.
mu sync.Mutex `state:"nosave"`
// queue is the queue of waiters.
queue waiter.Queue
// messages is a list of messages currently in the queue.
messages msgList
// subscriber represents a task registered to receive async notification
// from this queue.
subscriber *Subscriber
// messageCount is the number of messages currently in the queue.
messageCount int64
// maxMessageCount is the maximum number of messages that the queue can
// hold.
maxMessageCount int64
// maxMessageSize is the maximum size of a message held by the queue.
maxMessageSize uint64
// byteCount is the number of bytes of data in all messages in the queue.
byteCount uint64
}
// View is a view into a message queue. Views should only be used in file
// descriptions, but not inodes, because we use inodes to retrieve the actual
// queue, and only FDs are responsible for providing user functionality.
type View interface {
// TODO: Add Send and Receive when mq_timedsend(2) and mq_timedreceive(2)
// are implemented.
// Flush checks if the calling process has attached a notification request
// to this queue, if yes, then the request is removed, and another process
// can attach a request.
Flush(ctx context.Context)
waiter.Waitable
}
// ReaderWriter provides a send and receive view into a queue.
//
// +stateify savable
type ReaderWriter struct {
*Queue
block bool
}
// Reader provides a send-only view into a queue.
//
// +stateify savable
type Reader struct {
*Queue
block bool
}
// Writer provides a receive-only view into a queue.
//
// +stateify savable
type Writer struct {
*Queue
block bool
}
// NewView creates a new view into a queue and returns it.
func NewView(q *Queue, access AccessType, block bool) (View, error) {
switch access {
case ReadWrite:
return ReaderWriter{Queue: q, block: block}, nil
case WriteOnly:
return Writer{Queue: q, block: block}, nil
case ReadOnly:
return Reader{Queue: q, block: block}, nil
default:
// This case can't happen, due to O_RDONLY flag being 0 and O_WRONLY
// being 1, so one of them must be true.
return nil, linuxerr.EINVAL
}
}
// Message holds a message exchanged through a Queue via mq_timedsend(2) and
// mq_timedreceive(2), and additional info relating to the message.
//
// +stateify savable
type Message struct {
msgEntry
// Text is the message's sent content.
Text string
// Size is the message's size in bytes.
Size uint64
// Priority is the message's priority.
Priority uint32
}
// Subscriber represents a task registered for async notification from a Queue.
//
// +stateify savable
type Subscriber struct {
// TODO: Add fields when mq_notify(2) is implemented.
// pid is the PID of the registered task.
pid int32
}
// Generate implements vfs.DynamicBytesSource.Generate. Queue is used as a
// DynamicBytesSource for mqfs's queueInode.
func (q *Queue) Generate(ctx context.Context, buf *bytes.Buffer) error {
q.mu.Lock()
defer q.mu.Unlock()
var (
pid int32
method int
sigNumber int
)
if q.subscriber != nil {
pid = q.subscriber.pid
// TODO: add method and sigNumber when mq_notify(2) is implemented.
}
fmt.Fprintf(buf, "QSIZE:%-10d NOTIFY:%-5d SIGNO:%-5d NOTIFY_PID:%-6d\n",
q.byteCount, method, sigNumber, pid)
return nil
}
// Flush implements View.Flush.
func (q *Queue) Flush(ctx context.Context) {
q.mu.Lock()
defer q.mu.Unlock()
pid, ok := auth.ThreadGroupIDFromContext(ctx)
if ok {
if q.subscriber != nil && pid == q.subscriber.pid {
q.subscriber = nil
}
}
}
// Readiness implements Waitable.Readiness.
func (q *Queue) Readiness(mask waiter.EventMask) waiter.EventMask {
q.mu.Lock()
defer q.mu.Unlock()
events := waiter.EventMask(0)
if q.messageCount > 0 {
events |= waiter.ReadableEvents
}
if q.messageCount < q.maxMessageCount {
events |= waiter.WritableEvents
}
return events & mask
}
// EventRegister implements Waitable.EventRegister.
func (q *Queue) EventRegister(e *waiter.Entry) error {
q.mu.Lock()
defer q.mu.Unlock()
q.queue.EventRegister(e)
return nil
}
// EventUnregister implements Waitable.EventUnregister.
func (q *Queue) EventUnregister(e *waiter.Entry) {
q.mu.Lock()
defer q.mu.Unlock()
q.queue.EventUnregister(e)
}
// HasPermissions returns true if the given credentials meet the access
// permissions required by the queue.
func (q *Queue) HasPermissions(creds *auth.Credentials, req vfs.AccessTypes) bool {
perms := uint16(q.mode.Permissions())
if q.ownerUID == creds.EffectiveKUID {
perms >>= 6
} else if creds.InGroup(q.ownerGID) {
perms >>= 3
}
return uint16(req)&perms == uint16(req)
}