nuttx/drivers/serial/uart_bth4.c
Catalin Visinescu 081e4c478a drivers/: Multiple Drivers Are Registered With World Writable - Part 2
Permissions (Part 2)

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

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

Compiles ok.

Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
2026-07-15 15:27:28 +08:00

489 lines
12 KiB
C

/****************************************************************************
* drivers/serial/uart_bth4.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/irq.h>
#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/circbuf.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <nuttx/wireless/bluetooth/bt_hci.h>
#include <nuttx/wireless/bluetooth/bt_uart.h>
#include <nuttx/wireless/bluetooth/bt_driver.h>
/****************************************************************************
* Private Types
****************************************************************************/
union bt_hdr_u
{
struct bt_hci_cmd_hdr_s cmd;
struct bt_hci_acl_hdr_s acl;
struct bt_hci_evt_hdr_s evt;
struct bt_hci_iso_hdr_s iso;
};
struct uart_bth4_s
{
FAR struct bt_driver_s *drv;
FAR struct circbuf_s circbuf;
sem_t recvsem;
uint8_t sendbuf[CONFIG_UART_BTH4_TXBUFSIZE];
size_t sendlen;
mutex_t sendlock;
mutex_t openlock;
uint8_t refcnt;
FAR struct pollfd *fds[CONFIG_UART_BTH4_NPOLLWAITERS];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int uart_bth4_open(FAR struct file *filep);
static int uart_bth4_close(FAR struct file *filep);
static ssize_t uart_bth4_read(FAR struct file *filep,
FAR char *buffer, size_t buflen);
static ssize_t uart_bth4_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int uart_bth4_ioctl(FAR struct file *filep,
int cmd, unsigned long arg);
static int uart_bth4_poll(FAR struct file *filep,
FAR struct pollfd *fds, bool setup);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_uart_bth4_ops =
{
uart_bth4_open, /* open */
uart_bth4_close, /* close */
uart_bth4_read, /* read */
uart_bth4_write, /* write */
NULL, /* seek */
uart_bth4_ioctl, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
uart_bth4_poll /* poll */
};
/****************************************************************************
* Private Functions
****************************************************************************/
static inline void uart_bth4_post(FAR sem_t *sem)
{
int semcount;
nxsem_get_value(sem, &semcount);
if (semcount < 1)
{
nxsem_post(sem);
}
}
static void uart_bth4_pollnotify(FAR struct uart_bth4_s *dev,
pollevent_t eventset)
{
poll_notify(dev->fds, CONFIG_UART_BTH4_NPOLLWAITERS, eventset);
if ((eventset & POLLIN) != 0)
{
uart_bth4_post(&dev->recvsem);
}
}
static int uart_bth4_receive(FAR struct bt_driver_s *drv,
enum bt_buf_type_e type,
FAR void *buffer, size_t buflen)
{
FAR struct uart_bth4_s *dev = drv->priv;
int ret = buflen;
irqstate_t flags;
uint8_t htype;
flags = enter_critical_section();
if (circbuf_space(&dev->circbuf) >=
buflen + H4_HEADER_SIZE)
{
if (type == BT_EVT)
{
htype = H4_EVT;
}
else if (type == BT_ACL_IN)
{
htype = H4_ACL;
}
else if (type == BT_ISO_IN)
{
htype = H4_ISO;
}
else
{
ret = -EINVAL;
}
if (ret >= 0)
{
circbuf_write(&dev->circbuf, &htype, H4_HEADER_SIZE);
circbuf_write(&dev->circbuf, buffer, buflen);
uart_bth4_pollnotify(dev, POLLIN);
}
}
else
{
ret = -ENOMEM;
}
leave_critical_section(flags);
return ret;
}
static int uart_bth4_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct uart_bth4_s *dev = inode->i_private;
int ret;
uint8_t tmp;
ret = nxmutex_lock(&dev->openlock);
if (ret < 0)
{
return ret;
}
tmp = dev->refcnt + 1;
if (tmp == 0)
{
nxmutex_unlock(&dev->openlock);
return -EMFILE;
}
if (tmp == 1)
{
ret = dev->drv->open(dev->drv);
if (ret < 0)
{
nxmutex_unlock(&dev->openlock);
return ret;
}
dev->sendlen = 0;
}
dev->refcnt = tmp;
nxmutex_unlock(&dev->openlock);
return OK;
}
static int uart_bth4_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct uart_bth4_s *dev = inode->i_private;
nxmutex_lock(&dev->openlock);
if (dev->refcnt > 1)
{
dev->refcnt--;
nxmutex_unlock(&dev->openlock);
return OK;
}
dev->refcnt = 0;
dev->drv->close(dev->drv);
uart_bth4_pollnotify(dev, POLLIN | POLLOUT);
nxmutex_unlock(&dev->openlock);
return OK;
}
static ssize_t uart_bth4_read(FAR struct file *filep,
FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR struct uart_bth4_s *dev = inode->i_private;
irqstate_t flags;
ssize_t nread;
flags = enter_critical_section();
for (; ; )
{
nread = circbuf_read(&dev->circbuf, buffer, buflen);
if (nread != 0 || (filep->f_oflags & O_NONBLOCK))
{
break;
}
while (circbuf_is_empty(&dev->circbuf))
{
nxsem_wait_uninterruptible(&dev->recvsem);
}
}
leave_critical_section(flags);
return nread;
}
static ssize_t uart_bth4_write(FAR struct file *filep,
FAR const char *buffer,
size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR struct uart_bth4_s *dev = inode->i_private;
FAR union bt_hdr_u *hdr;
enum bt_buf_type_e type;
size_t reserved;
uint8_t *data;
size_t pktlen;
size_t hdrlen;
int ret;
ret = nxmutex_lock(&dev->sendlock);
if (ret < 0)
{
return ret;
}
if (dev->drv->head_reserve < H4_HEADER_SIZE)
{
reserved = H4_HEADER_SIZE;
}
else
{
reserved = dev->drv->head_reserve;
}
data = dev->sendbuf + reserved;
if (dev->sendlen + buflen > CONFIG_UART_BTH4_TXBUFSIZE - reserved)
{
ret = -E2BIG;
goto err;
}
memcpy(data - H4_HEADER_SIZE + dev->sendlen,
buffer, buflen);
dev->sendlen += buflen;
hdr = (FAR union bt_hdr_u *)data;
for (; ; )
{
switch (*(data - H4_HEADER_SIZE))
{
case H4_CMD:
hdrlen = sizeof(struct bt_hci_cmd_hdr_s);
pktlen = hdr->cmd.param_len;
type = BT_CMD;
break;
case H4_ACL:
hdrlen = sizeof(struct bt_hci_acl_hdr_s);
pktlen = hdr->acl.len;
type = BT_ACL_OUT;
break;
case H4_ISO:
hdrlen = sizeof(struct bt_hci_iso_hdr_s);
pktlen = hdr->iso.len;
type = BT_ISO_OUT;
break;
default:
ret = -EINVAL;
goto err;
}
/* Reassembly is incomplete ? */
hdrlen += H4_HEADER_SIZE;
if (dev->sendlen < hdrlen)
{
goto out;
}
pktlen += hdrlen;
if (dev->sendlen < pktlen)
{
goto out;
}
/* Got the full packet, send out */
ret = dev->drv->send(dev->drv, type,
data, pktlen - H4_HEADER_SIZE);
if (ret < 0)
{
goto err;
}
dev->sendlen -= pktlen;
if (dev->sendlen == 0)
{
goto out;
}
memmove(data - H4_HEADER_SIZE,
dev->sendbuf + pktlen, dev->sendlen);
}
err:
dev->sendlen = 0;
out:
nxmutex_unlock(&dev->sendlock);
return ret < 0 ? ret : buflen;
}
static int uart_bth4_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct uart_bth4_s *dev = inode->i_private;
if (!dev->drv->ioctl)
{
return -ENOTTY;
}
return dev->drv->ioctl(dev->drv, cmd, arg);
}
static int uart_bth4_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode = filep->f_inode;
FAR struct uart_bth4_s *dev = inode->i_private;
pollevent_t eventset = 0;
irqstate_t flags;
int ret = 0;
int i;
flags = enter_critical_section();
if (setup)
{
for (i = 0; i < CONFIG_UART_BTH4_NPOLLWAITERS; i++)
{
/* Find an available slot */
if (!dev->fds[i])
{
/* Bind the poll structure and this slot */
dev->fds[i] = fds;
fds->priv = &dev->fds[i];
break;
}
}
if (i >= CONFIG_UART_BTH4_NPOLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
}
if (!circbuf_is_empty(&dev->circbuf))
{
eventset |= POLLIN;
}
eventset |= POLLOUT;
poll_notify(&fds, 1, eventset);
}
else if (fds->priv != NULL)
{
for (i = 0; i < CONFIG_UART_BTH4_NPOLLWAITERS; i++)
{
if (fds == dev->fds[i])
{
dev->fds[i] = NULL;
fds->priv = NULL;
break;
}
}
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int uart_bth4_register(FAR const char *path, FAR struct bt_driver_s *drv)
{
FAR struct uart_bth4_s *dev;
int ret;
dev = (FAR struct uart_bth4_s *)
kmm_zalloc(sizeof(struct uart_bth4_s));
if (dev == NULL)
{
return -ENOMEM;
}
ret = circbuf_init(&dev->circbuf, NULL,
CONFIG_UART_BTH4_RXBUFSIZE);
if (ret < 0)
{
kmm_free(dev);
return -ENOMEM;
}
dev->drv = drv;
drv->receive = uart_bth4_receive;
drv->priv = dev;
nxmutex_init(&dev->sendlock);
nxmutex_init(&dev->openlock);
nxsem_init(&dev->recvsem, 0, 0);
ret = register_driver(path, &g_uart_bth4_ops, 0600, dev);
if (ret < 0)
{
nxmutex_destroy(&dev->sendlock);
nxmutex_destroy(&dev->openlock);
nxsem_destroy(&dev->recvsem);
circbuf_uninit(&dev->circbuf);
kmm_free(dev);
}
return ret;
}