From e873c74132f0c5f1452816cd9bb26208f0bba1e1 Mon Sep 17 00:00:00 2001 From: Shivank Garg Date: Sat, 22 Aug 2026 19:22:05 +0000 Subject: dmaengine: fix use-after-free in dma_chan_put() and dma_release_channel() When dma_device_put() drops the last reference on chan->device->ref, dma_device_release() runs and may free the dma_device along with its channels. dma_chan_put() then still reads chan->device->owner via dma_chan_to_owner() for the trailing module_put(). KASAN catches it: slab-use-after-free in dma_chan_put+0x3e6/0x4c0 Read of size 8 by task insmod/6319 Freed by task 6319: kfree+0x225/0x470 dma_chan_put+0x395/0x4c0 dmaengine_put+0xf8/0x160 Cache the module owner in dma_chan_put() before the put so the trailing module_put() does not need chan->device. Fixes: 8ad342a86359 ("dmaengine: Add reference counting to dma_device struct") Suggested-by: Sashiko Link: https://sashiko.dev/#/patchset/20260518-dmaengine-kref-fix-v1-1-4d6125048fb7@amd.com Reviewed-by: Frank Li Reviewed-by: Logan Gunthorpe Signed-off-by: Shivank Garg Link: https://patch.msgid.link/20260822-dmaengine-kref-fix-v5-3-d4a4ee47d927@amd.com Signed-off-by: Vinod Koul --- drivers/accessibility/speakup/devsynth.c | 178 +++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 drivers/accessibility/speakup/devsynth.c (limited to 'drivers/accessibility/speakup/devsynth.c') diff --git a/drivers/accessibility/speakup/devsynth.c b/drivers/accessibility/speakup/devsynth.c new file mode 100644 index 000000000..e3d909bd0 --- /dev/null +++ b/drivers/accessibility/speakup/devsynth.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include /* for misc_register, and MISC_DYNAMIC_MINOR */ +#include +#include + +#include "speakup.h" +#include "spk_priv.h" + +static int synth_registered, synthu_registered; +static int dev_opened; + +/* Latin1 version */ +static ssize_t speakup_file_write(struct file *fp, const char __user *buffer, + size_t nbytes, loff_t *ppos) +{ + size_t count = nbytes; + const char __user *ptr = buffer; + size_t bytes; + unsigned long flags; + u_char buf[256]; + + if (!synth) + return -ENODEV; + while (count > 0) { + bytes = min(count, sizeof(buf)); + if (copy_from_user(buf, ptr, bytes)) + return -EFAULT; + count -= bytes; + ptr += bytes; + spin_lock_irqsave(&speakup_info.spinlock, flags); + synth_write(buf, bytes); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + } + return (ssize_t)nbytes; +} + +/* UTF-8 version */ +static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer, + size_t nbytes, loff_t *ppos) +{ + size_t count = nbytes, consumed, want; + const char __user *ptr = buffer; + size_t bytes; + unsigned long flags; + unsigned char buf[256]; + u16 ubuf[256]; + size_t in, out; + + if (!synth) + return -ENODEV; + + want = 1; + while (count >= want) { + /* Copy some UTF-8 piece from userland */ + bytes = min(count, sizeof(buf)); + if (copy_from_user(buf, ptr, bytes)) + return -EFAULT; + + /* Convert to u16 */ + for (in = 0, out = 0; in < bytes; in += consumed) { + s32 value; + + value = synth_utf8_get(buf + in, bytes - in, &consumed, &want); + if (value == -1) { + /* Invalid or incomplete */ + + if (want > bytes - in) + /* We don't have it all yet, stop here + * and wait for the rest + */ + bytes = in; + + continue; + } + + if (value < 0x10000) + ubuf[out++] = value; + } + + count -= bytes; + ptr += bytes; + + /* And speak this up */ + if (out) { + spin_lock_irqsave(&speakup_info.spinlock, flags); + for (in = 0; in < out; in++) + synth_buffer_add(ubuf[in]); + synth_start(); + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + } + } + + return (ssize_t)(nbytes - count); +} + +static ssize_t speakup_file_read(struct file *fp, char __user *buf, + size_t nbytes, loff_t *ppos) +{ + return 0; +} + +static int speakup_file_open(struct inode *ip, struct file *fp) +{ + if (!synth) + return -ENODEV; + if (xchg(&dev_opened, 1)) + return -EBUSY; + return 0; +} + +static int speakup_file_release(struct inode *ip, struct file *fp) +{ + dev_opened = 0; + return 0; +} + +static const struct file_operations synth_fops = { + .read = speakup_file_read, + .write = speakup_file_write, + .open = speakup_file_open, + .release = speakup_file_release, +}; + +static const struct file_operations synthu_fops = { + .read = speakup_file_read, + .write = speakup_file_writeu, + .open = speakup_file_open, + .release = speakup_file_release, +}; + +static struct miscdevice synth_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "synth", + .fops = &synth_fops, +}; + +static struct miscdevice synthu_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "synthu", + .fops = &synthu_fops, +}; + +void speakup_register_devsynth(void) +{ + if (!synth_registered) { + if (misc_register(&synth_device)) { + pr_warn("Couldn't initialize miscdevice /dev/synth.\n"); + } else { + pr_info("initialized device: /dev/synth, node (MAJOR %d, MINOR %d)\n", + MISC_MAJOR, synth_device.minor); + synth_registered = 1; + } + } + if (!synthu_registered) { + if (misc_register(&synthu_device)) { + pr_warn("Couldn't initialize miscdevice /dev/synthu.\n"); + } else { + pr_info("initialized device: /dev/synthu, node (MAJOR %d, MINOR %d)\n", + MISC_MAJOR, synthu_device.minor); + synthu_registered = 1; + } + } +} + +void speakup_unregister_devsynth(void) +{ + if (synth_registered) { + pr_info("speakup: unregistering synth device /dev/synth\n"); + misc_deregister(&synth_device); + synth_registered = 0; + } + if (synthu_registered) { + pr_info("speakup: unregistering synth device /dev/synthu\n"); + misc_deregister(&synthu_device); + synthu_registered = 0; + } +} -- cgit v1.3.1