summaryrefslogtreecommitdiffstats
path: root/drivers/misc/mei/dma-ring.c
diff options
context:
space:
mode:
authorAli Ahmet Memis <ali@iusegentoo.com>2026-08-21 01:45:27 +0000
committerStafford Horne <shorne@gmail.com>2026-08-29 07:32:26 +0100
commit78004e9a87f240df03e2f73120d291763c32e0a7 (patch)
tree137c22dd795222931b9d2bf2501d37cd766741aa /drivers/misc/mei/dma-ring.c
downloadlinux-stable-78004e9a87f240df03e2f73120d291763c32e0a7.tar.gz
linux-stable-78004e9a87f240df03e2f73120d291763c32e0a7.zip
openrisc: fix arbitrary kernel memory access via or1k_atomic syscallgrafted
sys_or1k_atomic() (syscall 244 in the "or1k" ABI) takes two user pointers, v1 and v2, and swaps the words they point to in hand-written assembly. l.lwz r29,0(r4) l.lwz r27,0(r5) l.sw 0(r4),r27 l.sw 0(r5),r29 The pointers are not checked with access_ok(). The four memory accesses also have no exception table entries. A caller passes a kernel address as either pointer, and the syscall reads from and writes to it directly. This gives an unprivileged process a kernel read/write primitive. It overwrites kernel data such as the sys_call_table, gaining code execution in kernel context. Check both pointers before entering the critical section. Add fixups for the four memory accesses so faults on valid but unmapped user addresses return -EFAULT. [shorne@gmail.com: fix comment style] Fixes: 9d02a4283e9c ("OpenRISC: Boot code") Cc: stable@vger.kernel.org Signed-off-by: Ali Ahmet Memis <ali@iusegentoo.com> Signed-off-by: Stafford Horne <shorne@gmail.com>
Diffstat (limited to 'drivers/misc/mei/dma-ring.c')
-rw-r--r--drivers/misc/mei/dma-ring.c273
1 files changed, 273 insertions, 0 deletions
diff --git a/drivers/misc/mei/dma-ring.c b/drivers/misc/mei/dma-ring.c
new file mode 100644
index 000000000..6277c4a5b
--- /dev/null
+++ b/drivers/misc/mei/dma-ring.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(c) 2016-2018 Intel Corporation. All rights reserved.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/mei.h>
+
+#include "mei_dev.h"
+
+/**
+ * mei_dmam_dscr_alloc() - allocate a managed coherent buffer
+ * for the dma descriptor
+ * @dev: mei_device
+ * @dscr: dma descriptor
+ *
+ * Return:
+ * * 0 - on success or zero allocation request
+ * * -EINVAL - if size is not power of 2
+ * * -ENOMEM - of allocation has failed
+ */
+static int mei_dmam_dscr_alloc(struct mei_device *dev,
+ struct mei_dma_dscr *dscr)
+{
+ if (!dscr->size)
+ return 0;
+
+ if (WARN_ON(!is_power_of_2(dscr->size)))
+ return -EINVAL;
+
+ if (dscr->vaddr)
+ return 0;
+
+ dscr->vaddr = dmam_alloc_coherent(dev->parent, dscr->size, &dscr->daddr,
+ GFP_KERNEL);
+ if (!dscr->vaddr)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * mei_dmam_dscr_free() - free a managed coherent buffer
+ * from the dma descriptor
+ * @dev: mei_device
+ * @dscr: dma descriptor
+ */
+static void mei_dmam_dscr_free(struct mei_device *dev,
+ struct mei_dma_dscr *dscr)
+{
+ if (!dscr->vaddr)
+ return;
+
+ dmam_free_coherent(dev->parent, dscr->size, dscr->vaddr, dscr->daddr);
+ dscr->vaddr = NULL;
+}
+
+/**
+ * mei_dmam_ring_free() - free dma ring buffers
+ * @dev: mei device
+ */
+void mei_dmam_ring_free(struct mei_device *dev)
+{
+ int i;
+
+ for (i = 0; i < DMA_DSCR_NUM; i++)
+ mei_dmam_dscr_free(dev, &dev->dr_dscr[i]);
+}
+
+/**
+ * mei_dmam_ring_alloc() - allocate dma ring buffers
+ * @dev: mei device
+ *
+ * Return: -ENOMEM on allocation failure 0 otherwise
+ */
+int mei_dmam_ring_alloc(struct mei_device *dev)
+{
+ int i;
+
+ for (i = 0; i < DMA_DSCR_NUM; i++)
+ if (mei_dmam_dscr_alloc(dev, &dev->dr_dscr[i]))
+ goto err;
+
+ return 0;
+
+err:
+ mei_dmam_ring_free(dev);
+ return -ENOMEM;
+}
+
+/**
+ * mei_dma_ring_is_allocated() - check if dma ring is allocated
+ * @dev: mei device
+ *
+ * Return: true if dma ring is allocated
+ */
+bool mei_dma_ring_is_allocated(struct mei_device *dev)
+{
+ return !!dev->dr_dscr[DMA_DSCR_HOST].vaddr;
+}
+
+static inline
+struct hbm_dma_ring_ctrl *mei_dma_ring_ctrl(struct mei_device *dev)
+{
+ return (struct hbm_dma_ring_ctrl *)dev->dr_dscr[DMA_DSCR_CTRL].vaddr;
+}
+
+/**
+ * mei_dma_ring_reset() - reset the dma control block
+ * @dev: mei device
+ */
+void mei_dma_ring_reset(struct mei_device *dev)
+{
+ struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
+
+ if (!ctrl)
+ return;
+
+ memset(ctrl, 0, sizeof(*ctrl));
+}
+
+/**
+ * mei_dma_copy_from() - copy from dma ring into buffer
+ * @dev: mei device
+ * @buf: data buffer
+ * @offset: offset in slots.
+ * @n: number of slots to copy.
+ *
+ * Return: number of bytes copied
+ */
+static size_t mei_dma_copy_from(struct mei_device *dev, unsigned char *buf,
+ u32 offset, u32 n)
+{
+ unsigned char *dbuf = dev->dr_dscr[DMA_DSCR_DEVICE].vaddr;
+
+ size_t b_offset = offset << 2;
+ size_t b_n = n << 2;
+
+ memcpy(buf, dbuf + b_offset, b_n);
+
+ return b_n;
+}
+
+/**
+ * mei_dma_copy_to() - copy to a buffer to the dma ring
+ * @dev: mei device
+ * @buf: data buffer
+ * @offset: offset in slots.
+ * @n: number of slots to copy.
+ *
+ * Return: number of bytes copied
+ */
+static size_t mei_dma_copy_to(struct mei_device *dev, unsigned char *buf,
+ u32 offset, u32 n)
+{
+ unsigned char *hbuf = dev->dr_dscr[DMA_DSCR_HOST].vaddr;
+
+ size_t b_offset = offset << 2;
+ size_t b_n = n << 2;
+
+ memcpy(hbuf + b_offset, buf, b_n);
+
+ return b_n;
+}
+
+/**
+ * mei_dma_ring_read() - read data from the ring
+ * @dev: mei device
+ * @buf: buffer to read into: may be NULL in case of dropping the data.
+ * @len: length to read.
+ */
+void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len)
+{
+ struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
+ u32 dbuf_depth;
+ u32 rd_idx, rem, slots;
+
+ if (WARN_ON(!ctrl))
+ return;
+
+ dev_dbg(&dev->dev, "reading from dma %u bytes\n", len);
+
+ if (!len)
+ return;
+
+ dbuf_depth = dev->dr_dscr[DMA_DSCR_DEVICE].size >> 2;
+ rd_idx = READ_ONCE(ctrl->dbuf_rd_idx) & (dbuf_depth - 1);
+ slots = mei_data2slots(len);
+
+ /* if buf is NULL we drop the packet by advancing the pointer.*/
+ if (!buf)
+ goto out;
+
+ if (rd_idx + slots > dbuf_depth) {
+ buf += mei_dma_copy_from(dev, buf, rd_idx, dbuf_depth - rd_idx);
+ rem = slots - (dbuf_depth - rd_idx);
+ rd_idx = 0;
+ } else {
+ rem = slots;
+ }
+
+ mei_dma_copy_from(dev, buf, rd_idx, rem);
+out:
+ WRITE_ONCE(ctrl->dbuf_rd_idx, ctrl->dbuf_rd_idx + slots);
+}
+
+static inline u32 mei_dma_ring_hbuf_depth(struct mei_device *dev)
+{
+ return dev->dr_dscr[DMA_DSCR_HOST].size >> 2;
+}
+
+/**
+ * mei_dma_ring_empty_slots() - calaculate number of empty slots in dma ring
+ * @dev: mei_device
+ *
+ * Return: number of empty slots
+ */
+u32 mei_dma_ring_empty_slots(struct mei_device *dev)
+{
+ struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
+ u32 wr_idx, rd_idx, hbuf_depth, empty;
+
+ if (!mei_dma_ring_is_allocated(dev))
+ return 0;
+
+ if (WARN_ON(!ctrl))
+ return 0;
+
+ /* easier to work in slots */
+ hbuf_depth = mei_dma_ring_hbuf_depth(dev);
+ rd_idx = READ_ONCE(ctrl->hbuf_rd_idx);
+ wr_idx = READ_ONCE(ctrl->hbuf_wr_idx);
+
+ if (rd_idx > wr_idx)
+ empty = rd_idx - wr_idx;
+ else
+ empty = hbuf_depth - (wr_idx - rd_idx);
+
+ return empty;
+}
+
+/**
+ * mei_dma_ring_write - write data to dma ring host buffer
+ *
+ * @dev: mei_device
+ * @buf: data will be written
+ * @len: data length
+ */
+void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len)
+{
+ struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
+ u32 hbuf_depth;
+ u32 wr_idx, rem, slots;
+
+ if (WARN_ON(!ctrl))
+ return;
+
+ dev_dbg(&dev->dev, "writing to dma %u bytes\n", len);
+ hbuf_depth = mei_dma_ring_hbuf_depth(dev);
+ wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1);
+ slots = mei_data2slots(len);
+
+ if (wr_idx + slots > hbuf_depth) {
+ buf += mei_dma_copy_to(dev, buf, wr_idx, hbuf_depth - wr_idx);
+ rem = slots - (hbuf_depth - wr_idx);
+ wr_idx = 0;
+ } else {
+ rem = slots;
+ }
+
+ mei_dma_copy_to(dev, buf, wr_idx, rem);
+
+ WRITE_ONCE(ctrl->hbuf_wr_idx, ctrl->hbuf_wr_idx + slots);
+}