summaryrefslogtreecommitdiffstats
path: root/drivers/accessibility/speakup/selection.c
diff options
context:
space:
mode:
authorMuhammad Bilal <meatuni001@gmail.com>2026-07-28 17:54:55 +0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-09-01 12:15:16 +0200
commitff917923f4fb9c83717ba135ee47d7e4c1567bb7 (patch)
treec38fd8fcf58e1937b17fdbc6518b6f8b797fc15a /drivers/accessibility/speakup/selection.c
downloadlinux-stable-ff917923f4fb9c83717ba135ee47d7e4c1567bb7.tar.gz
linux-stable-ff917923f4fb9c83717ba135ee47d7e4c1567bb7.zip
staging: rtl8723bs: fix OOB read in rtw_action_frame_parse()grafted
rtw_action_frame_parse() takes a frame_len parameter but never actually checks it before indexing into the frame body: const u8 *frame_body = frame + sizeof(struct ieee80211_hdr_3addr); ... c = frame_body[0]; ... a = frame_body[1]; frame_body already points 24 bytes (sizeof(struct ieee80211_hdr_3addr)) into frame, so reading frame_body[0] and frame_body[1] requires frame_len >= 26. A management action frame shorter than that (e.g. exactly 24 bytes, the minimum a malicious peer can send) causes a 1-2 byte out-of-bounds read. This is reachable from rtw_cfg80211_monitor_if_xmit_entry() and cfg80211_rtw_mgmt_tx() in ioctl_cfg80211.c, both of which pass attacker/user-influenced frame buffers and lengths straight through. Add the missing length check before frame_body is dereferenced. Fixes: 554c0a3abf216 ("staging: Add rtl8723bs sdio wifi driver") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal <meatuni001@gmail.com> Link: https://patch.msgid.link/20260728125456.32359-3-meatuni001@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/accessibility/speakup/selection.c')
-rw-r--r--drivers/accessibility/speakup/selection.c141
1 files changed, 141 insertions, 0 deletions
diff --git a/drivers/accessibility/speakup/selection.c b/drivers/accessibility/speakup/selection.c
new file mode 100644
index 000000000..1713ce4e0
--- /dev/null
+++ b/drivers/accessibility/speakup/selection.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/slab.h> /* for kmalloc */
+#include <linux/consolemap.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/device.h> /* for dev_warn */
+#include <linux/selection.h>
+#include <linux/workqueue.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/atomic.h>
+#include <linux/console.h>
+
+#include "speakup.h"
+
+u16 spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */
+struct vc_data *spk_sel_cons;
+
+struct speakup_selection_work {
+ struct work_struct work;
+ struct tiocl_selection sel;
+ struct tty_struct *tty;
+};
+
+static void __speakup_set_selection(struct work_struct *work)
+{
+ struct speakup_selection_work *ssw =
+ container_of(work, struct speakup_selection_work, work);
+
+ struct tty_struct *tty;
+ struct tiocl_selection sel;
+
+ sel = ssw->sel;
+
+ /* this ensures we copy sel before releasing the lock below */
+ rmb();
+
+ /* release the lock by setting tty of the struct to NULL */
+ tty = xchg(&ssw->tty, NULL);
+
+ if (spk_sel_cons != vc_cons[fg_console].d) {
+ spk_sel_cons = vc_cons[fg_console].d;
+ pr_warn("Selection: mark console not the same as cut\n");
+ goto unref;
+ }
+
+ console_lock();
+ clear_selection();
+ console_unlock();
+
+ set_selection_kernel(&sel, tty);
+
+unref:
+ tty_kref_put(tty);
+}
+
+static struct speakup_selection_work speakup_sel_work = {
+ .work = __WORK_INITIALIZER(speakup_sel_work.work,
+ __speakup_set_selection)
+};
+
+int speakup_set_selection(struct tty_struct *tty)
+{
+ /* we get kref here first in order to avoid a subtle race when
+ * cancelling selection work. getting kref first establishes the
+ * invariant that if speakup_sel_work.tty is not NULL when
+ * speakup_cancel_selection() is called, it must be the case that a put
+ * kref is pending.
+ */
+ tty_kref_get(tty);
+ if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) {
+ tty_kref_put(tty);
+ return -EBUSY;
+ }
+ /* now we have the 'lock' by setting tty member of
+ * speakup_selection_work. wmb() ensures that writes to
+ * speakup_sel_work don't happen before cmpxchg() above.
+ */
+ wmb();
+
+ speakup_sel_work.sel.xs = spk_xs + 1;
+ speakup_sel_work.sel.ys = spk_ys + 1;
+ speakup_sel_work.sel.xe = spk_xe + 1;
+ speakup_sel_work.sel.ye = spk_ye + 1;
+ speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR;
+
+ schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work);
+
+ return 0;
+}
+
+void speakup_cancel_selection(void)
+{
+ struct tty_struct *tty;
+
+ cancel_work_sync(&speakup_sel_work.work);
+ /* setting to null so that if work fails to run and we cancel it,
+ * we can run it again without getting EBUSY forever from there on.
+ * we need to use xchg here to avoid race with speakup_set_selection()
+ */
+ tty = xchg(&speakup_sel_work.tty, NULL);
+ if (tty)
+ tty_kref_put(tty);
+}
+
+static void __speakup_paste_selection(struct work_struct *work)
+{
+ struct speakup_selection_work *ssw =
+ container_of(work, struct speakup_selection_work, work);
+ struct tty_struct *tty = xchg(&ssw->tty, NULL);
+
+ paste_selection(tty);
+ tty_kref_put(tty);
+}
+
+static struct speakup_selection_work speakup_paste_work = {
+ .work = __WORK_INITIALIZER(speakup_paste_work.work,
+ __speakup_paste_selection)
+};
+
+int speakup_paste_selection(struct tty_struct *tty)
+{
+ tty_kref_get(tty);
+ if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {
+ tty_kref_put(tty);
+ return -EBUSY;
+ }
+
+ schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);
+ return 0;
+}
+
+void speakup_cancel_paste(void)
+{
+ struct tty_struct *tty;
+
+ cancel_work_sync(&speakup_paste_work.work);
+ tty = xchg(&speakup_paste_work.tty, NULL);
+ if (tty)
+ tty_kref_put(tty);
+}