nuttx/drivers/misc/goldfish_pipe.c
Catalin Visinescu a30bbfee6b drivers/: Multiple Drivers Are Registered With World Writable Part 3
Summary

Permissions (Part 3)

Description:

In kernel builds, any unprivileged process running on the NuttX device can
open /dev/efuse and attempt to read/write fuse content. Reading the fuses
may provide valuable information to an attacker controlling the user process.
The write operation, in extreme cases where the fuse blocks are not locked,
may brick the device.

DISCLAIMER: I tried to be strict with the settings, better to relax them
later if it's needed.

This is part of https://github.com/apache/nuttx/issues/19410

Impact

See https://github.com/apache/nuttx/issues/19410

Testing

Compiles ok.

Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
2026-07-16 09:49:07 -03:00

689 lines
19 KiB
C

/****************************************************************************
* drivers/misc/goldfish_pipe.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/* Usage from the guest is simple:
*
* int fd = open("/dev/goldfish_pipe", O_RDWR);
* .... write() or read() through the pipe.
*
* Connect to a specific qemu service:
*
* // Do this immediately after opening the fd
* const char* msg = "<pipename>";
* if (write(fd, msg, strlen(msg) + 1) < 0) {
* ... could not connect to <pipename> service
* close(fd);
* }
*
* After this, simply read() and write() to communicate with the service.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <nuttx/arch.h>
#include <nuttx/fs/fs.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/spinlock.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* List of bitflags returned in status of GOLDFISH_PIPE_CMD_POLL command */
#define GOLDFISH_PIPE_POLL_IN (1 << 0)
#define GOLDFISH_PIPE_POLL_OUT (1 << 1)
#define GOLDFISH_PIPE_POLL_HUP (1 << 2)
/* Possible status values used to signal errors */
#define GOLDFISH_PIPE_ERROR_INVAL -1
#define GOLDFISH_PIPE_ERROR_AGAIN -2
#define GOLDFISH_PIPE_ERROR_NOMEM -3
#define GOLDFISH_PIPE_ERROR_IO -4
/* Bit-flags used to signal events from the emulator */
#define GOLDFISH_PIPE_WAKE_CLOSED (1 << 0) /* Emulator closed pipe */
#define GOLDFISH_PIPE_WAKE_READ (1 << 1) /* Pipe can now be read from */
#define GOLDFISH_PIPE_WAKE_WRITE (1 << 2) /* Pipe can now be written to */
#define GOLDFISH_PIPE_WAKE_UNLOCK_DMA (1 << 3) /* Unlock this pipe's DMA buffer */
#define GOLDFISH_PIPE_WAKE_UNLOCK_DMA_SHARED (1 << 4) /* Unlock DMA buffer of the pipe shared to this pipe */
/* Possible pipe closing reasons */
#define GOLDFISH_PIPE_CLOSE_GRACEFUL 0 /* Guest sent a close command */
#define GOLDFISH_PIPE_CLOSE_REBOOT 1 /* Guest rebooted, we're closing the pipes */
#define GOLDFISH_PIPE_CLOSE_LOAD_SNAPSHOT 2 /* Close old pipes on snapshot load */
#define GOLDFISH_PIPE_CLOSE_ERROR 3 /* Some unrecoverable error on the pipe */
/* Register offset */
#define GOLDFISH_PIPE_REG_CMD 0
#define GOLDFISH_PIPE_REG_SIGNAL_BUFFER_HIGH 4
#define GOLDFISH_PIPE_REG_SIGNAL_BUFFER 8
#define GOLDFISH_PIPE_REG_SIGNAL_BUFFER_COUNT 12
#define GOLDFISH_PIPE_REG_OPEN_BUFFER_HIGH 20
#define GOLDFISH_PIPE_REG_OPEN_BUFFER 24
#define GOLDFISH_PIPE_REG_VERSION 36
#define GOLDFISH_PIPE_REG_GET_SIGNALLED 48
/* Possible pipe command */
#define GOLDFISH_PIPE_CMD_OPEN 1
#define GOLDFISH_PIPE_CMD_CLOSE 2
#define GOLDFISH_PIPE_CMD_POLL 3
#define GOLDFISH_PIPE_CMD_WRITE 4
#define GOLDFISH_PIPE_CMD_WAKE_ON_WRITE 5
#define GOLDFISH_PIPE_CMD_READ 6
#define GOLDFISH_PIPE_CMD_WAKE_ON_READ 7
#define GOLDFISH_PIPE_CMD_WAKE_ON_DONE_IO 8
/* Version number */
#define GOLDFISH_PIPE_DRIVER_VERSION 4
#define GOLDFISH_PIPE_CURRENT_DEVICE_VERSION 2
#define GOLDFISH_PIPE_MAX_POLL_WAITERS 2
#define GOLDFISH_PIPE_MAX_COMMAND_BUFFERS 1
#define GOLDFISH_PIPE_MAX_SIGNALLED 16
#define GOLDFISH_PIPE_MAX_PIPES 32
#define goldfish_pipe_putreg32(v, x) (*(FAR volatile uint32_t *)(x) = (v))
#define goldfish_pipe_getreg32(x) (*(FAR volatile uint32_t *)(x))
/****************************************************************************
* Private Types
****************************************************************************/
/* A per-pipe command structure, shared with the host */
struct goldfish_pipe_command_s
{
int32_t cmd; /* PipeCmdCode, guest -> host */
int32_t id; /* Pipe id, guest -> host */
int32_t status; /* Command execution status, host -> guest */
int32_t reserved; /* To pad to 64-bit boundary */
struct /* Parameters for GOLDFISH_PIPE_CMD_[READ|WRITE] */
{
uint32_t buffers_count; /* Number of buffers, guest -> host */
int32_t consumed_size; /* Number of consumed bytes, host -> guest */
uint64_t ptrs[GOLDFISH_PIPE_MAX_COMMAND_BUFFERS]; /* Buffer pointers, guest -> host */
uint32_t sizes[GOLDFISH_PIPE_MAX_COMMAND_BUFFERS]; /* Buffer sizes, guest -> host */
}
rw_params;
};
/* A single signalled pipe information */
struct goldfish_pipe_signalled_s
{
uint32_t id;
uint32_t flags;
};
/* Parameters for the GOLDFISH_PIPE_CMD_OPEN command */
struct goldfish_pipe_open_param_s
{
uint64_t command_buffer_ptr;
uint32_t rw_params_max_count;
};
/* This data type models a given pipe instance */
struct goldfish_pipe_dev_s;
struct goldfish_pipe_s
{
uint32_t id;
bool closed;
struct goldfish_pipe_command_s command;
mutex_t lock;
sem_t wait_for_write;
sem_t wait_for_read;
FAR struct goldfish_pipe_dev_s *dev;
FAR struct pollfd *fds[GOLDFISH_PIPE_MAX_POLL_WAITERS];
};
struct goldfish_pipe_dev_s
{
spinlock_t lock;
FAR struct goldfish_pipe_s *pipes[GOLDFISH_PIPE_MAX_PIPES];
struct goldfish_pipe_open_param_s open_params;
struct goldfish_pipe_signalled_s
pipes_signalled[GOLDFISH_PIPE_MAX_SIGNALLED];
FAR uint8_t *base;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static ssize_t goldfish_pipe_read(FAR struct file *filp, FAR char *buffer,
size_t bufflen);
static ssize_t goldfish_pipe_write(FAR struct file *filp,
FAR const char *buffer, size_t bufflen);
static int goldfish_pipe_poll(FAR struct file *filp,
FAR struct pollfd *fds, bool setup);
static int goldfish_pipe_open(FAR struct file *filep);
static int goldfish_pipe_close(FAR struct file *filep);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_goldfish_pipe_fops =
{
.read = goldfish_pipe_read,
.write = goldfish_pipe_write,
.poll = goldfish_pipe_poll,
.open = goldfish_pipe_open,
.close = goldfish_pipe_close,
};
/****************************************************************************
* Private Functions
****************************************************************************/
static int goldfish_pipe_convert_status(int status)
{
switch (status)
{
case GOLDFISH_PIPE_ERROR_INVAL:
return -EINVAL;
case GOLDFISH_PIPE_ERROR_AGAIN:
return -EAGAIN;
case GOLDFISH_PIPE_ERROR_NOMEM:
return -ENOMEM;
case GOLDFISH_PIPE_ERROR_IO:
return -EIO;
default:
return status;
}
}
static int goldfish_pipe_command_locked(FAR struct goldfish_pipe_s *pipe,
int cmd)
{
pipe->command.cmd = cmd;
pipe->command.status = GOLDFISH_PIPE_ERROR_INVAL;
goldfish_pipe_putreg32(pipe->id, pipe->dev->base + GOLDFISH_PIPE_REG_CMD);
return goldfish_pipe_convert_status(pipe->command.status);
}
static int goldfish_pipe_command(FAR struct goldfish_pipe_s *pipe, int cmd)
{
int ret;
ret = nxmutex_lock(&pipe->lock);
if (ret < 0)
{
return ret;
}
ret = goldfish_pipe_command_locked(pipe, cmd);
nxmutex_unlock(&pipe->lock);
return ret;
}
static int goldfish_pipe_wait(FAR struct goldfish_pipe_s *pipe,
bool is_write)
{
int ret;
ret = goldfish_pipe_command(pipe, is_write ?
GOLDFISH_PIPE_CMD_WAKE_ON_WRITE :
GOLDFISH_PIPE_CMD_WAKE_ON_READ);
if (ret < 0)
{
return ret;
}
return nxsem_wait(is_write ? &pipe->wait_for_write : &pipe->wait_for_read);
}
static ssize_t goldfish_pipe_transfer_one(FAR struct goldfish_pipe_s *pipe,
FAR char *buffer, size_t buflen,
bool is_write)
{
ssize_t ret;
ret = nxmutex_lock(&pipe->lock);
if (ret < 0)
{
return ret;
}
pipe->command.rw_params.ptrs[0] = up_addrenv_va_to_pa(buffer);
pipe->command.rw_params.sizes[0] = buflen;
pipe->command.rw_params.buffers_count = 1;
ret = goldfish_pipe_command_locked(pipe,
is_write ? GOLDFISH_PIPE_CMD_WRITE : GOLDFISH_PIPE_CMD_READ);
if (ret >= 0)
{
ret = pipe->command.rw_params.consumed_size;
}
nxmutex_unlock(&pipe->lock);
return ret;
}
static ssize_t goldfish_pipe_transfer(FAR struct file *filp,
FAR char *buffer, size_t bufflen,
bool is_write)
{
FAR struct goldfish_pipe_s *pipe = filp->f_priv;
size_t count = 0;
int ret = 0;
while (bufflen > 0)
{
/* If the emulator already closed the pipe, no need to go further */
if (pipe->closed)
{
ret = -EIO;
break;
}
ret = goldfish_pipe_transfer_one(pipe, buffer, bufflen, is_write);
if (ret > 0)
{
buffer += ret;
bufflen -= ret;
count += ret;
continue;
}
if (ret != -EAGAIN || count || (filp->f_oflags & O_NONBLOCK))
{
break;
}
ret = goldfish_pipe_wait(pipe, is_write);
if (ret < 0)
{
break;
}
}
return count > 0 ? count : ret;
}
static ssize_t goldfish_pipe_read(FAR struct file *filp, FAR char *buffer,
size_t bufflen)
{
return goldfish_pipe_transfer(filp, buffer, bufflen, false);
}
static ssize_t goldfish_pipe_write(FAR struct file *filp,
FAR const char *buffer, size_t bufflen)
{
return goldfish_pipe_transfer(filp, (FAR char *)buffer, bufflen, true);
}
static int goldfish_pipe_poll(FAR struct file *filp,
FAR struct pollfd *fds, bool setup)
{
if (setup)
{
FAR struct goldfish_pipe_s *pipe = filp->f_priv;
FAR struct goldfish_pipe_dev_s *dev = pipe->dev;
pollevent_t eventset = 0;
irqstate_t flags;
int ret;
int i;
ret = goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_POLL);
if (ret < 0)
{
return ret;
}
flags = spin_lock_irqsave(&dev->lock);
/* This is a request to set up the poll. Find an available
* slot for the poll structure reference
*/
for (i = 0; i < GOLDFISH_PIPE_MAX_POLL_WAITERS; i++)
{
/* Find an available slot */
if (pipe->fds[i] == NULL)
{
/* Bind the poll structure and this slot */
pipe->fds[i] = fds;
fds->priv = &pipe->fds[i];
break;
}
}
spin_unlock_irqrestore(&dev->lock, flags);
if (i >= GOLDFISH_PIPE_MAX_POLL_WAITERS)
{
return -EBUSY;
}
if (ret & GOLDFISH_PIPE_POLL_IN)
{
eventset |= POLLIN;
}
if (ret & GOLDFISH_PIPE_POLL_OUT)
{
eventset |= POLLOUT;
}
if (ret & GOLDFISH_PIPE_POLL_HUP)
{
eventset |= POLLHUP;
}
if (pipe->closed)
{
eventset |= POLLERR;
}
if (eventset)
{
poll_notify(&fds, 1, eventset);
}
else
{
goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_WAKE_ON_READ);
}
}
else
{
/* This is a request to tear down the poll. */
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
if (slot == NULL)
{
return -EINVAL;
}
*slot = NULL;
fds->priv = NULL;
}
return 0;
}
static int goldfish_pipe_open(FAR struct file *filep)
{
FAR struct goldfish_pipe_dev_s *dev = filep->f_inode->i_private;
FAR struct goldfish_pipe_s *pipe;
irqstate_t flags;
int ret = -ENFILE;
int id;
pipe = kmm_zalloc(sizeof(*pipe));
if (pipe == NULL)
{
return -ENOMEM;
}
pipe->dev = dev;
nxmutex_init(&pipe->lock);
nxsem_init(&pipe->wait_for_read, 0, 0);
nxsem_init(&pipe->wait_for_write, 0, 0);
flags = spin_lock_irqsave(&dev->lock);
for (id = 0; id < GOLDFISH_PIPE_MAX_PIPES; id++)
{
if (dev->pipes[id] == NULL)
{
break;
}
}
if (id >= GOLDFISH_PIPE_MAX_PIPES)
{
goto out;
}
pipe->id = id;
pipe->command.id = id;
/* Now tell the emulator we're opening a new pipe. */
dev->open_params.command_buffer_ptr = up_addrenv_va_to_pa(&pipe->command);
dev->open_params.rw_params_max_count = GOLDFISH_PIPE_MAX_COMMAND_BUFFERS;
ret = goldfish_pipe_command_locked(pipe, GOLDFISH_PIPE_CMD_OPEN);
if (ret < 0)
{
goto out;
}
dev->pipes[id] = pipe;
spin_unlock_irqrestore(&dev->lock, flags);
filep->f_priv = pipe;
return 0;
out:
spin_unlock_irqrestore(&dev->lock, flags);
nxsem_destroy(&pipe->wait_for_write);
nxsem_destroy(&pipe->wait_for_read);
nxmutex_destroy(&pipe->lock);
kmm_free(pipe);
return ret;
}
static int goldfish_pipe_close(FAR struct file *filp)
{
FAR struct goldfish_pipe_s *pipe = filp->f_priv;
FAR struct goldfish_pipe_dev_s *dev = pipe->dev;
irqstate_t flags;
goldfish_pipe_command(pipe, GOLDFISH_PIPE_CMD_CLOSE);
flags = spin_lock_irqsave(&dev->lock);
dev->pipes[pipe->id] = NULL;
spin_unlock_irqrestore(&dev->lock, flags);
filp->f_priv = NULL;
nxsem_destroy(&pipe->wait_for_write);
nxsem_destroy(&pipe->wait_for_read);
nxmutex_destroy(&pipe->lock);
kmm_free(pipe);
return 0;
}
static void goldfish_pipe_wake(FAR struct goldfish_pipe_s *pipe,
bool is_write)
{
FAR sem_t *sem = is_write ? &pipe->wait_for_write : &pipe->wait_for_read;
int sval;
if (nxsem_get_value(sem, &sval) >= 0)
{
while (sval++ <= 0)
{
nxsem_post(sem);
}
}
}
static int goldfish_pipe_interrupt(int irq, FAR void *context, FAR void *arg)
{
FAR struct goldfish_pipe_dev_s *dev = arg;
irqstate_t irqflags;
uint32_t count;
uint32_t i;
irqflags = spin_lock_irqsave(&dev->lock);
count = goldfish_pipe_getreg32(dev->base +
GOLDFISH_PIPE_REG_GET_SIGNALLED);
if (count > GOLDFISH_PIPE_MAX_SIGNALLED)
{
count = GOLDFISH_PIPE_MAX_SIGNALLED;
}
for (i = 0; i < count; i++)
{
uint32_t id = dev->pipes_signalled[i].id;
uint32_t flags = dev->pipes_signalled[i].flags;
FAR struct goldfish_pipe_s *pipe = dev->pipes[id];
pollevent_t eventset = 0;
if (pipe == NULL)
{
continue;
}
if (flags & GOLDFISH_PIPE_WAKE_READ)
{
eventset |= POLLIN;
}
if (flags & GOLDFISH_PIPE_WAKE_WRITE)
{
eventset |= POLLOUT;
}
if (flags & GOLDFISH_PIPE_WAKE_CLOSED)
{
eventset |= POLLERR;
dev->pipes[id]->closed = true;
}
if (eventset & (POLLIN | POLLERR))
{
goldfish_pipe_wake(pipe, false);
}
if (eventset & (POLLOUT | POLLERR))
{
goldfish_pipe_wake(pipe, true);
}
if (eventset)
{
poll_notify(pipe->fds, GOLDFISH_PIPE_MAX_POLL_WAITERS, eventset);
}
}
spin_unlock_irqrestore(&dev->lock, irqflags);
return 0;
}
static void goldfish_pipe_write_addr(FAR void *addr,
FAR void *portl, FAR void *porth)
{
uintptr_t paddr = up_addrenv_va_to_pa(addr);
goldfish_pipe_putreg32((paddr >> 16) >> 16, porth);
goldfish_pipe_putreg32(paddr, portl);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: goldfish_pipe_register
*
* Description:
* register /dev/goldfish_pipe device
*
****************************************************************************/
int goldfish_pipe_register(FAR void *base, int irq)
{
FAR struct goldfish_pipe_dev_s *dev;
uint32_t version;
int ret = -ENOTSUP;
/* Allocate and initialize a new device structure instance */
dev = (FAR struct goldfish_pipe_dev_s *)kmm_zalloc(sizeof(*dev));
if (dev == NULL)
{
return -ENOMEM;
}
spin_lock_init(&dev->lock);
dev->base = (FAR uint8_t *)base;
/* Exchange the versions with the host device */
goldfish_pipe_putreg32(GOLDFISH_PIPE_DRIVER_VERSION,
dev->base + GOLDFISH_PIPE_REG_VERSION);
version = goldfish_pipe_getreg32(dev->base + GOLDFISH_PIPE_REG_VERSION);
if (version < GOLDFISH_PIPE_CURRENT_DEVICE_VERSION)
{
goto out;
}
ret = irq_attach(irq, goldfish_pipe_interrupt, dev);
if (ret < 0)
{
goto out;
}
up_enable_irq(irq);
/* Send the buffer addresses to the host */
goldfish_pipe_write_addr(&dev->pipes_signalled,
dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER,
dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER_HIGH);
goldfish_pipe_putreg32(GOLDFISH_PIPE_MAX_SIGNALLED,
dev->base + GOLDFISH_PIPE_REG_SIGNAL_BUFFER_COUNT);
goldfish_pipe_write_addr(&dev->open_params,
dev->base + GOLDFISH_PIPE_REG_OPEN_BUFFER,
dev->base + GOLDFISH_PIPE_REG_OPEN_BUFFER_HIGH);
/* Register the pipe device */
return register_driver("/dev/goldfish_pipe",
&g_goldfish_pipe_fops, 0600, dev);
out:
kmm_free(dev);
return ret;
}