mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #6171 from sudo-sturbia:msgqueue/syscalls2
PiperOrigin-RevId: 388497055
This commit is contained in:
@@ -119,14 +119,21 @@ type Queue struct {
|
||||
type Message struct {
|
||||
msgEntry
|
||||
|
||||
// mType is an integer representing the type of the sent message.
|
||||
mType int64
|
||||
// Type is an integer representing the type of the sent message.
|
||||
Type int64
|
||||
|
||||
// mText is an untyped block of memory.
|
||||
mText []byte
|
||||
// Text is an untyped block of memory.
|
||||
Text []byte
|
||||
|
||||
// mSize is the size of mText.
|
||||
mSize uint64
|
||||
// Size is the size of Text.
|
||||
Size uint64
|
||||
}
|
||||
|
||||
// Blocker is used for blocking Queue.Send, and Queue.Receive calls that serves
|
||||
// as an abstracted version of kernel.Task. kernel.Task is not directly used to
|
||||
// prevent circular dependencies.
|
||||
type Blocker interface {
|
||||
Block(C <-chan struct{}) error
|
||||
}
|
||||
|
||||
// FindOrCreate creates a new message queue or returns an existing one. See
|
||||
@@ -186,6 +193,265 @@ func (r *Registry) Remove(id ipc.ID, creds *auth.Credentials) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindByID returns the queue with the specified ID and an error if the ID
|
||||
// doesn't exist.
|
||||
func (r *Registry) FindByID(id ipc.ID) (*Queue, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
mech := r.reg.FindByID(id)
|
||||
if mech == nil {
|
||||
return nil, linuxerr.EINVAL
|
||||
}
|
||||
return mech.(*Queue), nil
|
||||
}
|
||||
|
||||
// Send appends a message to the message queue, and returns an error if sending
|
||||
// fails. See msgsnd(2).
|
||||
func (q *Queue) Send(ctx context.Context, m Message, b Blocker, wait bool, pid int32) (err error) {
|
||||
// Try to perform a non-blocking send using queue.append. If EWOULDBLOCK
|
||||
// is returned, start the blocking procedure. Otherwise, return normally.
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
if err := q.append(ctx, m, creds, pid); err != linuxerr.EWOULDBLOCK {
|
||||
return err
|
||||
}
|
||||
|
||||
if !wait {
|
||||
return linuxerr.EAGAIN
|
||||
}
|
||||
|
||||
e, ch := waiter.NewChannelEntry(nil)
|
||||
q.senders.EventRegister(&e, waiter.EventOut)
|
||||
|
||||
for {
|
||||
if err = q.append(ctx, m, creds, pid); err != linuxerr.EWOULDBLOCK {
|
||||
break
|
||||
}
|
||||
b.Block(ch)
|
||||
}
|
||||
|
||||
q.senders.EventUnregister(&e)
|
||||
return err
|
||||
}
|
||||
|
||||
// append appends a message to the queue's message list and notifies waiting
|
||||
// receivers that a message has been inserted. It returns an error if adding
|
||||
// the message would cause the queue to exceed its maximum capacity, which can
|
||||
// be used as a signal to block the task. Other errors should be returned as is.
|
||||
func (q *Queue) append(ctx context.Context, m Message, creds *auth.Credentials, pid int32) error {
|
||||
if m.Type <= 0 {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
if !q.obj.CheckPermissions(creds, fs.PermMask{Write: true}) {
|
||||
// The calling process does not have write permission on the message
|
||||
// queue, and does not have the CAP_IPC_OWNER capability in the user
|
||||
// namespace that governs its IPC namespace.
|
||||
return linuxerr.EACCES
|
||||
}
|
||||
|
||||
// Queue was removed while the process was waiting.
|
||||
if q.dead {
|
||||
return linuxerr.EIDRM
|
||||
}
|
||||
|
||||
// Check if sufficient space is available (the queue isn't full.) From
|
||||
// the man pages:
|
||||
//
|
||||
// "A message queue is considered to be full if either of the following
|
||||
// conditions is true:
|
||||
//
|
||||
// • Adding a new message to the queue would cause the total number
|
||||
// of bytes in the queue to exceed the queue's maximum size (the
|
||||
// msg_qbytes field).
|
||||
//
|
||||
// • Adding another message to the queue would cause the total
|
||||
// number of messages in the queue to exceed the queue's maximum
|
||||
// size (the msg_qbytes field). This check is necessary to
|
||||
// prevent an unlimited number of zero-length messages being
|
||||
// placed on the queue. Although such messages contain no data,
|
||||
// they nevertheless consume (locked) kernel memory."
|
||||
//
|
||||
// The msg_qbytes field in our implementation is q.maxBytes.
|
||||
if m.Size+q.byteCount > q.maxBytes || q.messageCount+1 > q.maxBytes {
|
||||
return linuxerr.EWOULDBLOCK
|
||||
}
|
||||
|
||||
// Copy the message into the queue.
|
||||
q.messages.PushBack(&m)
|
||||
|
||||
q.byteCount += m.Size
|
||||
q.messageCount++
|
||||
q.sendPID = pid
|
||||
q.sendTime = ktime.NowFromContext(ctx)
|
||||
|
||||
// Notify receivers about the new message.
|
||||
q.receivers.Notify(waiter.EventIn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Receive removes a message from the queue and returns it. See msgrcv(2).
|
||||
func (q *Queue) Receive(ctx context.Context, b Blocker, mType int64, maxSize int64, wait, truncate, except bool, pid int32) (msg *Message, err error) {
|
||||
if maxSize < 0 || maxSize > maxMessageBytes {
|
||||
return nil, linuxerr.EINVAL
|
||||
}
|
||||
max := uint64(maxSize)
|
||||
|
||||
// Try to perform a non-blocking receive using queue.pop. If EWOULDBLOCK
|
||||
// is returned, start the blocking procedure. Otherwise, return normally.
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
if msg, err := q.pop(ctx, creds, mType, max, truncate, except, pid); err != linuxerr.EWOULDBLOCK {
|
||||
return msg, err
|
||||
}
|
||||
|
||||
if !wait {
|
||||
return nil, linuxerr.ENOMSG
|
||||
}
|
||||
|
||||
e, ch := waiter.NewChannelEntry(nil)
|
||||
q.receivers.EventRegister(&e, waiter.EventIn)
|
||||
|
||||
for {
|
||||
if msg, err = q.pop(ctx, creds, mType, max, truncate, except, pid); err != linuxerr.EWOULDBLOCK {
|
||||
break
|
||||
}
|
||||
b.Block(ch)
|
||||
}
|
||||
q.receivers.EventUnregister(&e)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// pop pops the first message from the queue that matches the given type. It
|
||||
// returns an error for all the cases specified in msgrcv(2). If the queue is
|
||||
// empty or no message of the specified type is available, a EWOULDBLOCK error
|
||||
// is returned, which can then be used as a signal to block the process or fail.
|
||||
func (q *Queue) pop(ctx context.Context, creds *auth.Credentials, mType int64, maxSize uint64, truncate, except bool, pid int32) (msg *Message, _ error) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
if !q.obj.CheckPermissions(creds, fs.PermMask{Read: true}) {
|
||||
// The calling process does not have read permission on the message
|
||||
// queue, and does not have the CAP_IPC_OWNER capability in the user
|
||||
// namespace that governs its IPC namespace.
|
||||
return nil, linuxerr.EACCES
|
||||
}
|
||||
|
||||
// Queue was removed while the process was waiting.
|
||||
if q.dead {
|
||||
return nil, linuxerr.EIDRM
|
||||
}
|
||||
|
||||
if q.messages.Empty() {
|
||||
return nil, linuxerr.EWOULDBLOCK
|
||||
}
|
||||
|
||||
// Get a message from the queue.
|
||||
switch {
|
||||
case mType == 0:
|
||||
msg = q.messages.Front()
|
||||
case mType > 0:
|
||||
msg = q.msgOfType(mType, except)
|
||||
case mType < 0:
|
||||
msg = q.msgOfTypeLessThan(-1 * mType)
|
||||
}
|
||||
|
||||
// If no message exists, return a blocking singal.
|
||||
if msg == nil {
|
||||
return nil, linuxerr.EWOULDBLOCK
|
||||
}
|
||||
|
||||
// Check message's size is acceptable.
|
||||
if maxSize < msg.Size {
|
||||
if !truncate {
|
||||
return nil, linuxerr.E2BIG
|
||||
}
|
||||
msg.Size = maxSize
|
||||
msg.Text = msg.Text[:maxSize+1]
|
||||
}
|
||||
|
||||
q.messages.Remove(msg)
|
||||
|
||||
q.byteCount -= msg.Size
|
||||
q.messageCount--
|
||||
q.receivePID = pid
|
||||
q.receiveTime = ktime.NowFromContext(ctx)
|
||||
|
||||
// Notify senders about available space.
|
||||
q.senders.Notify(waiter.EventOut)
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// Copy copies a message from the queue without deleting it. If no message
|
||||
// exists, an error is returned. See msgrcv(MSG_COPY).
|
||||
func (q *Queue) Copy(mType int64) (*Message, error) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
if mType < 0 || q.messages.Empty() {
|
||||
return nil, linuxerr.ENOMSG
|
||||
}
|
||||
|
||||
msg := q.msgAtIndex(mType)
|
||||
if msg == nil {
|
||||
return nil, linuxerr.ENOMSG
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// msgOfType returns the first message with the specified type, nil if no
|
||||
// message is found. If except is true, the first message of a type not equal
|
||||
// to mType will be returned.
|
||||
//
|
||||
// Precondition: caller must hold q.mu.
|
||||
func (q *Queue) msgOfType(mType int64, except bool) *Message {
|
||||
if except {
|
||||
for msg := q.messages.Front(); msg != nil; msg = msg.Next() {
|
||||
if msg.Type != mType {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for msg := q.messages.Front(); msg != nil; msg = msg.Next() {
|
||||
if msg.Type == mType {
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// msgOfTypeLessThan return the the first message with the lowest type less
|
||||
// than or equal to mType, nil if no such message exists.
|
||||
//
|
||||
// Precondition: caller must hold q.mu.
|
||||
func (q *Queue) msgOfTypeLessThan(mType int64) (m *Message) {
|
||||
min := mType
|
||||
for msg := q.messages.Front(); msg != nil; msg = msg.Next() {
|
||||
if msg.Type <= mType && msg.Type < min {
|
||||
m = msg
|
||||
min = msg.Type
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// msgAtIndex returns a pointer to a message at given index, nil if non exits.
|
||||
//
|
||||
// Precondition: caller must hold q.mu.
|
||||
func (q *Queue) msgAtIndex(mType int64) *Message {
|
||||
msg := q.messages.Front()
|
||||
for ; mType != 0 && msg != nil; mType-- {
|
||||
msg = msg.Next()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Lock implements ipc.Mechanism.Lock.
|
||||
func (q *Queue) Lock() {
|
||||
q.mu.Lock()
|
||||
|
||||
@@ -86,6 +86,7 @@ go_library(
|
||||
"//pkg/sentry/kernel/eventfd",
|
||||
"//pkg/sentry/kernel/fasync",
|
||||
"//pkg/sentry/kernel/ipc",
|
||||
"//pkg/sentry/kernel/msgqueue",
|
||||
"//pkg/sentry/kernel/pipe",
|
||||
"//pkg/sentry/kernel/sched",
|
||||
"//pkg/sentry/kernel/shm",
|
||||
|
||||
@@ -122,8 +122,8 @@ var AMD64 = &kernel.SyscallTable{
|
||||
66: syscalls.Supported("semctl", Semctl),
|
||||
67: syscalls.Supported("shmdt", Shmdt),
|
||||
68: syscalls.Supported("msgget", Msgget),
|
||||
69: syscalls.ErrorWithEvent("msgsnd", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
70: syscalls.ErrorWithEvent("msgrcv", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
69: syscalls.Supported("msgsnd", Msgsnd),
|
||||
70: syscalls.Supported("msgrcv", Msgrcv),
|
||||
71: syscalls.PartiallySupported("msgctl", Msgctl, "Only supports IPC_RMID option.", []string{"gvisor.dev/issue/135"}),
|
||||
72: syscalls.PartiallySupported("fcntl", Fcntl, "Not all options are supported.", nil),
|
||||
73: syscalls.PartiallySupported("flock", Flock, "Locks are held within the sandbox only.", nil),
|
||||
@@ -618,8 +618,8 @@ var ARM64 = &kernel.SyscallTable{
|
||||
185: syscalls.ErrorWithEvent("mq_getsetattr", syserror.ENOSYS, "", []string{"gvisor.dev/issue/136"}), // TODO(b/29354921)
|
||||
186: syscalls.Supported("msgget", Msgget),
|
||||
187: syscalls.PartiallySupported("msgctl", Msgctl, "Only supports IPC_RMID option.", []string{"gvisor.dev/issue/135"}),
|
||||
188: syscalls.ErrorWithEvent("msgrcv", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
189: syscalls.ErrorWithEvent("msgsnd", linuxerr.ENOSYS, "", []string{"gvisor.dev/issue/135"}), // TODO(b/29354921)
|
||||
188: syscalls.Supported("msgrcv", Msgrcv),
|
||||
189: syscalls.Supported("msgsnd", Msgsnd),
|
||||
190: syscalls.Supported("semget", Semget),
|
||||
191: syscalls.Supported("semctl", Semctl),
|
||||
192: syscalls.Supported("semtimedop", Semtimedop),
|
||||
|
||||
@@ -17,10 +17,12 @@ package linux
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/ipc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/msgqueue"
|
||||
)
|
||||
|
||||
// Msgget implements msgget(2).
|
||||
@@ -41,6 +43,89 @@ func Msgget(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
|
||||
return uintptr(queue.ID()), nil, nil
|
||||
}
|
||||
|
||||
// Msgsnd implements msgsnd(2).
|
||||
func Msgsnd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
id := ipc.ID(args[0].Int())
|
||||
msgAddr := args[1].Pointer()
|
||||
size := args[2].Int64()
|
||||
flag := args[3].Int()
|
||||
|
||||
if size < 0 || size > linux.MSGMAX {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
wait := flag&linux.IPC_NOWAIT != linux.IPC_NOWAIT
|
||||
pid := int32(t.ThreadGroup().ID())
|
||||
|
||||
buf := linux.MsgBuf{
|
||||
Text: make([]byte, size),
|
||||
}
|
||||
if _, err := buf.CopyIn(t, msgAddr); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
queue, err := t.IPCNamespace().MsgqueueRegistry().FindByID(id)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
msg := msgqueue.Message{
|
||||
Type: int64(buf.Type),
|
||||
Text: buf.Text,
|
||||
Size: uint64(size),
|
||||
}
|
||||
return 0, nil, queue.Send(t, msg, t, wait, pid)
|
||||
}
|
||||
|
||||
// Msgrcv implements msgrcv(2).
|
||||
func Msgrcv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
id := ipc.ID(args[0].Int())
|
||||
msgAddr := args[1].Pointer()
|
||||
size := args[2].Int64()
|
||||
mType := args[3].Int64()
|
||||
flag := args[4].Int()
|
||||
|
||||
wait := flag&linux.IPC_NOWAIT != linux.IPC_NOWAIT
|
||||
except := flag&linux.MSG_EXCEPT == linux.MSG_EXCEPT
|
||||
truncate := flag&linux.MSG_NOERROR == linux.MSG_NOERROR
|
||||
|
||||
msgCopy := flag&linux.MSG_COPY == linux.MSG_COPY
|
||||
|
||||
msg, err := receive(t, id, mType, size, msgCopy, wait, truncate, except)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
buf := linux.MsgBuf{
|
||||
Type: primitive.Int64(msg.Type),
|
||||
Text: msg.Text,
|
||||
}
|
||||
if _, err := buf.CopyOut(t, msgAddr); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return uintptr(msg.Size), nil, nil
|
||||
}
|
||||
|
||||
// receive returns a message from the queue with the given ID. If msgCopy is
|
||||
// true, a message is copied from the queue without being removed. Otherwise,
|
||||
// a message is removed from the queue and returned.
|
||||
func receive(t *kernel.Task, id ipc.ID, mType int64, maxSize int64, msgCopy, wait, truncate, except bool) (*msgqueue.Message, error) {
|
||||
pid := int32(t.ThreadGroup().ID())
|
||||
|
||||
queue, err := t.IPCNamespace().MsgqueueRegistry().FindByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msgCopy {
|
||||
if wait || except {
|
||||
return nil, linuxerr.EINVAL
|
||||
}
|
||||
return queue.Copy(mType)
|
||||
}
|
||||
return queue.Receive(t, t, mType, maxSize, wait, truncate, except, pid)
|
||||
}
|
||||
|
||||
// Msgctl implements msgctl(2).
|
||||
func Msgctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
id := ipc.ID(args[0].Int())
|
||||
|
||||
@@ -4172,9 +4172,11 @@ cc_binary(
|
||||
srcs = ["msgqueue.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
"//test/util:capability_util",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user