nuttx/drivers/virtio/virtio-blk.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

640 lines
19 KiB
C

/****************************************************************************
* drivers/virtio/virtio-blk.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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/debug.h>
#include <errno.h>
#include <stdio.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/semaphore.h>
#include <nuttx/spinlock.h>
#include <nuttx/virtio/virtio.h>
#include <nuttx/init.h>
#include "virtio-blk.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define VIRTIO_BLK_REQ_HEADER_SIZE sizeof(struct virtio_blk_req_s)
#define VIRTIO_BLK_RESP_HEADER_SIZE sizeof(struct virtio_blk_resp_s)
/* Block feature bits */
#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */
#define VIRTIO_BLK_F_BLK_SIZE 6 /* Block size of disk is available */
#define VIRTIO_BLK_F_FLUSH 9 /* Cache flush command support */
/* Block request type */
#define VIRTIO_BLK_T_IN 0 /* READ */
#define VIRTIO_BLK_T_OUT 1 /* WRITE */
#define VIRTIO_BLK_T_FLUSH 4 /* FLUSH */
/* Block request return status */
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
/* Block device sector size */
#define VIRTIO_BLK_SECTOR_BITS 9
#define VIRTIO_BLK_SECTOR_SIZE (1UL << VIRTIO_BLK_SECTOR_BITS)
/****************************************************************************
* Private Types
****************************************************************************/
/* Block request out header */
begin_packed_struct struct virtio_blk_req_s
{
uint32_t type;
uint32_t reserved;
uint64_t sector;
} end_packed_struct;
/* Block request in header */
begin_packed_struct struct virtio_blk_resp_s
{
uint8_t status;
} end_packed_struct;
begin_packed_struct struct virtio_blk_config_s
{
uint64_t capacity;
uint32_t size_max;
uint32_t seg_max;
uint16_t cylinders; /* block geometry */
uint8_t heads; /* block geometry */
uint8_t sectors; /* block geometry */
uint32_t blk_size;
uint8_t physical_block_exp;
uint8_t alignment_offset;
uint16_t min_io_size;
uint32_t opt_io_size;
uint8_t writeback;
uint8_t unused0;
uint16_t num_queues;
uint32_t max_discard_sectors;
uint32_t max_discard_seg;
uint32_t discard_sector_alignment;
uint32_t max_write_zeroes_sectors;
uint32_t max_write_zeroes_seg;
uint8_t write_zeroes_may_unmap;
uint8_t unused1[3];
uint32_t max_secure_erase_sectors;
uint32_t max_secure_erase_seg;
uint32_t secure_erase_sector_alignment;
} end_packed_struct;
struct virtio_blk_priv_s
{
FAR struct virtio_device *vdev; /* Virtio device */
spinlock_t lock; /* Lock */
uint64_t nsectors; /* Sectore numbers */
uint32_t block_size; /* Block size */
char name[NAME_MAX]; /* Device name */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* BLK block_operations functions and they helper function */
static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv,
FAR void *buffer, blkcnt_t startsector,
unsigned int nsectors, bool write);
static int virtio_blk_open(FAR struct inode *inode);
static int virtio_blk_close(FAR struct inode *inode);
static ssize_t virtio_blk_read(FAR struct inode *inode,
FAR unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors);
static ssize_t virtio_blk_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors);
static int virtio_blk_geometry(FAR struct inode *inode,
FAR struct geometry *geometry);
static int virtio_blk_ioctl(FAR struct inode *inode, int cmd,
unsigned long arg);
static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv);
/* Other functions */
static int virtio_blk_init(FAR struct virtio_blk_priv_s *priv,
FAR struct virtio_device *vdev);
static void virtio_blk_uninit(FAR struct virtio_blk_priv_s *priv);
static void virtio_blk_done(FAR struct virtqueue *vq);
static int virtio_blk_probe(FAR struct virtio_device *vdev);
static void virtio_blk_remove(FAR struct virtio_device *vdev);
/****************************************************************************
* Private Data
****************************************************************************/
static struct virtio_driver g_virtio_blk_driver =
{
LIST_INITIAL_VALUE(g_virtio_blk_driver.node), /* node */
VIRTIO_ID_BLOCK, /* device id */
virtio_blk_probe, /* probe */
virtio_blk_remove, /* remove */
};
static const struct block_operations g_virtio_blk_bops =
{
virtio_blk_open, /* open */
virtio_blk_close, /* close */
virtio_blk_read, /* read */
virtio_blk_write, /* write */
virtio_blk_geometry, /* geometry */
virtio_blk_ioctl /* ioctl */
};
static int g_virtio_blk_idx = 0;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: virtio_blk_wait_complete
*
* Description:
* Wait the virtio block request complete
*
****************************************************************************/
static void virtio_blk_wait_complete(FAR struct virtqueue *vq,
FAR sem_t *respsem)
{
FAR struct virtio_blk_priv_s *priv = vq->vq_dev->priv;
FAR sem_t *sem;
if (up_interrupt_context() || OSINIT_IS_PANIC())
{
for (; ; )
{
sem = virtqueue_get_buffer_lock(vq, NULL, NULL, &priv->lock);
if (sem == respsem)
{
break;
}
else if (sem != NULL)
{
nxsem_post(sem);
}
}
}
else
{
nxsem_wait_uninterruptible(respsem);
}
}
/****************************************************************************
* Name: virtio_blk_rdwr
*
* Description:
* Common function for read and write
*
****************************************************************************/
static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv,
FAR void *buffer, blkcnt_t startsector,
unsigned int nsectors, bool write)
{
FAR struct virtio_device *vdev = priv->vdev;
FAR struct virtqueue *vq = vdev->vrings_info[0].vq;
FAR struct virtqueue_buf vb[3];
struct virtio_blk_resp_s resp;
struct virtio_blk_req_s req;
irqstate_t flags;
sem_t respsem;
ssize_t ret;
int readnum;
nxsem_init(&respsem, 0, 0);
/* Build the block request */
req.type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
req.reserved = 0;
req.sector = startsector * priv->block_size >> VIRTIO_BLK_SECTOR_BITS;
resp.status = VIRTIO_BLK_S_IOERR;
/* Fill the virtqueue buffer:
* Buffer 0: the block out header;
* Buffer 1: the read/write buffer;
* Buffer 2: the block in header, return the status.
*/
vb[0].buf = &req;
vb[0].len = VIRTIO_BLK_REQ_HEADER_SIZE;
vb[1].buf = buffer;
vb[1].len = nsectors * priv->block_size;
vb[2].buf = &resp;
vb[2].len = VIRTIO_BLK_RESP_HEADER_SIZE;
readnum = write ? 2 : 1;
if (up_interrupt_context())
{
virtqueue_disable_cb_lock(vq, &priv->lock);
}
flags = spin_lock_irqsave(&priv->lock);
ret = virtqueue_add_buffer(vq, vb, readnum, 3 - readnum, &respsem);
if (ret < 0)
{
spin_unlock_irqrestore(&priv->lock, flags);
vrterr("virtqueue_add_buffer failed, ret=%zd\n", ret);
goto err;
}
virtqueue_kick(vq);
spin_unlock_irqrestore(&priv->lock, flags);
/* Wait for the request completion */
virtio_blk_wait_complete(vq, &respsem);
if (resp.status != VIRTIO_BLK_S_OK)
{
vrterr("%s Error\n", write ? "Write" : "Read");
ret = -EIO;
}
err:
if (up_interrupt_context())
{
virtqueue_enable_cb_lock(vq, &priv->lock);
}
return ret >= 0 ? nsectors : ret;
}
/****************************************************************************
* Name: virtio_blk_open
*
* Description: Open the block device
*
****************************************************************************/
static int virtio_blk_open(FAR struct inode *inode)
{
DEBUGASSERT(inode->i_private);
return OK;
}
/****************************************************************************
* Name: virtio_blk_close
*
* Description: close the block device
*
****************************************************************************/
static int virtio_blk_close(FAR struct inode *inode)
{
DEBUGASSERT(inode->i_private);
return OK;
}
/****************************************************************************
* Name: virtio_blk_read
*
* Description:
* Read the specified number of sectors from the read-ahead buffer or from
* the physical device.
*
****************************************************************************/
static ssize_t virtio_blk_read(FAR struct inode *inode,
FAR unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors)
{
FAR struct virtio_blk_priv_s *priv;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
return virtio_blk_rdwr(priv, buffer, startsector, nsectors, false);
}
/****************************************************************************
* Name: virtio_blk_write
*
* Description:
* Write the specified number of sectors to the write buffer or to the
* physical device.
*
****************************************************************************/
static ssize_t virtio_blk_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t startsector, unsigned int nsectors)
{
FAR struct virtio_blk_priv_s *priv;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
if (virtio_has_feature(priv->vdev, VIRTIO_BLK_F_RO))
{
return -EPERM;
}
return virtio_blk_rdwr(priv, (FAR void *)buffer, startsector, nsectors,
true);
}
/****************************************************************************
* Name: virtio_blk_geometry
*
* Description: Return device geometry
*
****************************************************************************/
static int virtio_blk_geometry(FAR struct inode *inode,
FAR struct geometry *geometry)
{
FAR struct virtio_blk_priv_s *priv;
int ret = -EINVAL;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
if (geometry)
{
geometry->geo_available = true;
geometry->geo_mediachanged = false;
geometry->geo_writeenabled = true;
geometry->geo_nsectors = priv->nsectors;
geometry->geo_sectorsize = priv->block_size;
ret = OK;
}
return ret;
}
/****************************************************************************
* Name: virtio_blk_ioctl
****************************************************************************/
static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv)
{
FAR struct virtio_device *vdev = priv->vdev;
FAR struct virtqueue *vq = vdev->vrings_info[0].vq;
FAR struct virtqueue_buf vb[2];
struct virtio_blk_resp_s resp;
struct virtio_blk_req_s req;
irqstate_t flags;
sem_t respsem;
int ret;
nxsem_init(&respsem, 0, 0);
/* Build the block request */
req.type = VIRTIO_BLK_T_FLUSH;
req.reserved = 0;
req.sector = 0;
resp.status = VIRTIO_BLK_S_IOERR;
vb[0].buf = &req;
vb[0].len = VIRTIO_BLK_REQ_HEADER_SIZE;
vb[1].buf = &resp;
vb[1].len = VIRTIO_BLK_RESP_HEADER_SIZE;
flags = spin_lock_irqsave(&priv->lock);
ret = virtqueue_add_buffer(vq, vb, 1, 1, &respsem);
if (ret < 0)
{
spin_unlock_irqrestore(&priv->lock, flags);
return ret;
}
virtqueue_kick(vq);
spin_unlock_irqrestore(&priv->lock, flags);
/* Wait for the request completion */
nxsem_wait_uninterruptible(&respsem);
if (resp.status != VIRTIO_BLK_S_OK)
{
vrterr("Flush Error\n");
ret = -EIO;
}
return ret;
}
/****************************************************************************
* Name: virtio_blk_ioctl
****************************************************************************/
static int virtio_blk_ioctl(FAR struct inode *inode, int cmd,
unsigned long arg)
{
FAR struct virtio_blk_priv_s *priv;
int ret = -ENOTTY;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
switch (cmd)
{
case BIOC_FLUSH:
if (virtio_has_feature(priv->vdev, VIRTIO_BLK_F_FLUSH))
{
ret = virtio_blk_flush(priv);
}
break;
}
return ret;
}
/****************************************************************************
* Name: virtio_blk_done
****************************************************************************/
static void virtio_blk_done(FAR struct virtqueue *vq)
{
FAR struct virtio_blk_priv_s *priv = vq->vq_dev->priv;
FAR sem_t *respsem;
for (; ; )
{
respsem = virtqueue_get_buffer_lock(vq, NULL, NULL, &priv->lock);
if (respsem == NULL)
{
break;
}
nxsem_post(respsem);
}
}
/****************************************************************************
* Name: virtio_blk_init
****************************************************************************/
static int virtio_blk_init(FAR struct virtio_blk_priv_s *priv,
FAR struct virtio_device *vdev)
{
FAR const char *vqname[1];
vq_callback callback[1];
int ret;
priv->vdev = vdev;
vdev->priv = priv;
spin_lock_init(&priv->lock);
/* Initialize the virtio device */
virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER);
virtio_negotiate_features(vdev, (1UL << VIRTIO_BLK_F_RO) |
(1UL << VIRTIO_BLK_F_BLK_SIZE) |
(1UL << VIRTIO_BLK_F_FLUSH), NULL);
virtio_set_status(vdev, VIRTIO_CONFIG_FEATURES_OK);
vqname[0] = "virtio_blk_vq";
callback[0] = virtio_blk_done;
ret = virtio_create_virtqueues(vdev, 0, 1, vqname, callback, NULL);
if (ret < 0)
{
vrterr("virtio_device_create_virtqueue failed, ret=%d\n", ret);
return ret;
}
virtio_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
virtqueue_enable_cb(vdev->vrings_info[0].vq);
return ret;
}
/****************************************************************************
* Name: virtio_blk_uninit
****************************************************************************/
static void virtio_blk_uninit(FAR struct virtio_blk_priv_s *priv)
{
FAR struct virtio_device *vdev = priv->vdev;
virtio_reset_device(vdev);
virtio_delete_virtqueues(vdev);
}
/****************************************************************************
* Name: virtio_blk_probe
****************************************************************************/
static int virtio_blk_probe(FAR struct virtio_device *vdev)
{
FAR struct virtio_blk_priv_s *priv;
int ret;
/* Alloc the virtio block driver private data */
priv = kmm_zalloc(sizeof(*priv));
if (priv == NULL)
{
vrterr("No enough memory\n");
return -ENOMEM;
}
/* Init the virtio block driver */
ret = virtio_blk_init(priv, vdev);
if (ret < 0)
{
vrterr("virtio_blk_init failed, ret=%d\n", ret);
goto err_with_priv;
}
/* Read the block config and save the capacity to nsectors */
virtio_read_config_member(priv->vdev, struct virtio_blk_config_s, capacity,
&priv->nsectors);
vrtinfo("Virio blk capacity=%" PRIu64 " sectors\n", priv->nsectors);
if (virtio_has_feature(vdev, VIRTIO_BLK_F_BLK_SIZE))
{
virtio_read_config_member(priv->vdev, struct virtio_blk_config_s,
blk_size, &priv->block_size);
vrtinfo("Virio blk block_size=%" PRIu32 "\n", priv->block_size);
}
else
{
priv->block_size = VIRTIO_BLK_SECTOR_SIZE;
}
/* Register block driver */
snprintf(priv->name, NAME_MAX, "/dev/virtblk%d", g_virtio_blk_idx);
ret = register_blockdriver(priv->name, &g_virtio_blk_bops, 0600, priv);
if (ret < 0)
{
vrterr("Register block driver failed, ret=%d\n", ret);
goto err_with_init;
}
g_virtio_blk_idx++;
return ret;
err_with_init:
virtio_blk_uninit(priv);
err_with_priv:
kmm_free(priv);
return ret;
}
/****************************************************************************
* Name: virtio_blk_remove
****************************************************************************/
static void virtio_blk_remove(FAR struct virtio_device *vdev)
{
FAR struct virtio_blk_priv_s *priv = vdev->priv;
unregister_driver(priv->name);
virtio_blk_uninit(priv);
kmm_free(priv);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: virtio_register_blk_driver
****************************************************************************/
int virtio_register_blk_driver(void)
{
return virtio_register_driver(&g_virtio_blk_driver);
}