]> git.webhop.me Git - bs-cst-neutrino-hd.git/commitdiff
add kernel 4.10.12 & 4.4.35 dvb-core-section-callback-uaf-guard.patch
authorMarkham <markham001@gmx.de>
Fri, 1 May 2026 13:26:13 +0000 (15:26 +0200)
committerMarkham <markham001@gmx.de>
Fri, 1 May 2026 13:26:13 +0000 (15:26 +0200)
dvb-core: guard dmxdev callbacks against stale dispatch

- Some out-of-tree demux drivers can deliver section and TS callbacks after release_filter() / release_ts_feed() has returned, with priv pointers cleared or slots already recycled. dvb-core assumes synchronous teardown and may dereference stale state.
- Add defensive validation in both callback paths: use READ_ONCE() for priv/dev loads, verify state and back-pointers under dev->lock, and drop callbacks that no longer belong to the active slot/feed.
- Also reject NULL src in dvb_dmxdev_buffer_write() to avoid passing it to dvb_ringbuffer_write() from a bad dispatcher.
- This fixes crashes seen on hd51 during CA zap storms, first in section_callback() and then in ts_callback().# Please enter the commit message for your changes. Lines starting

archive-patches/armbox/hd5x/linux-4.10.12-dvb-core-section-callback-uaf-guard.patch [new file with mode: 0644]
archive-patches/armbox/hd6x/linux-4.4.35-dvb-core-section-callback-uaf-guard.patch [new file with mode: 0644]
make/kernel-arm.mk

diff --git a/archive-patches/armbox/hd5x/linux-4.10.12-dvb-core-section-callback-uaf-guard.patch b/archive-patches/armbox/hd5x/linux-4.10.12-dvb-core-section-callback-uaf-guard.patch
new file mode 100644 (file)
index 0000000..c3be499
--- /dev/null
@@ -0,0 +1,158 @@
+diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c
+index 2abcb413..82827fab 100644
+--- a/drivers/media/dvb-core/dmxdev.c
++++ b/drivers/media/dvb-core/dmxdev.c
+@@ -51,6 +51,12 @@ static int dvb_dmxdev_buffer_write(struct dvb_ringbuffer *buf,
+       if (!len)
+               return 0;
++      /* Out-of-tree dispatchers can pass NULL src with non-zero len
++       * when they split a section across two buffers and the second
++       * half was never populated. Treat as a no-op rather than feed
++       * NULL into the ringbuffer memcpy. */
++      if (!src)
++              return 0;
+       if (!buf->data)
+               return 0;
+@@ -364,16 +370,40 @@ static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len,
+                                      const u8 *buffer2, size_t buffer2_len,
+                                      struct dmx_section_filter *filter)
+ {
+-      struct dmxdev_filter *dmxdevfilter = filter->priv;
++      struct dmxdev_filter *dmxdevfilter;
++      struct dmxdev *dev;
+       int ret;
+-      if (dmxdevfilter->buffer.error) {
+-              wake_up(&dmxdevfilter->buffer.queue);
++      /* filter itself is never expected NULL, but belt and braces, the
++       * out-of-tree dispatcher has surprised us before. */
++      if (!filter)
++              return 0;
++
++      /* READ_ONCE forces the deref to stay where we wrote it. Without it
++       * -Os hoists the load of filter->priv over the NULL check, turning
++       * the guard into a no-op in the generated code. */
++      dmxdevfilter = READ_ONCE(filter->priv);
++      if (!dmxdevfilter)
++              return 0;
++
++      dev = READ_ONCE(dmxdevfilter->dev);
++      if (!dev)
++              return 0;
++
++      spin_lock(&dev->lock);
++
++      /* Back-pointer check under lock. If an out-of-tree driver has
++       * recycled the dmx_section_filter slot between our READ_ONCE and
++       * the lock acquire, filter.sec no longer points back at this
++       * filter and the dispatch belongs to a freed/replaced session. */
++      if (dmxdevfilter->state != DMXDEV_STATE_GO ||
++          dmxdevfilter->filter.sec != filter) {
++              spin_unlock(&dev->lock);
+               return 0;
+       }
+-      spin_lock(&dmxdevfilter->dev->lock);
+-      if (dmxdevfilter->state != DMXDEV_STATE_GO) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++      if (dmxdevfilter->buffer.error) {
++              spin_unlock(&dev->lock);
++              wake_up(&dmxdevfilter->buffer.queue);
+               return 0;
+       }
+       del_timer(&dmxdevfilter->timer);
+@@ -388,7 +418,7 @@ static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len,
+               dmxdevfilter->buffer.error = ret;
+       if (dmxdevfilter->params.sec.flags & DMX_ONESHOT)
+               dmxdevfilter->state = DMXDEV_STATE_DONE;
+-      spin_unlock(&dmxdevfilter->dev->lock);
++      spin_unlock(&dev->lock);
+       wake_up(&dmxdevfilter->buffer.queue);
+       return 0;
+ }
+@@ -397,13 +427,54 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len,
+                                 const u8 *buffer2, size_t buffer2_len,
+                                 struct dmx_ts_feed *feed)
+ {
+-      struct dmxdev_filter *dmxdevfilter = feed->priv;
++      struct dmxdev_filter *dmxdevfilter;
++      struct dmxdev *dev;
+       struct dvb_ringbuffer *buffer;
+       int ret;
+-      spin_lock(&dmxdevfilter->dev->lock);
++      /* Same guard pattern as dvb_dmxdev_section_callback. Out-of-tree
++       * dispatchers can deliver a callback after release_ts_feed has
++       * returned, with feed->priv cleared or the filter slot recycled. */
++      if (!feed)
++              return 0;
++
++      dmxdevfilter = READ_ONCE(feed->priv);
++      if (!dmxdevfilter)
++              return 0;
++
++      dev = READ_ONCE(dmxdevfilter->dev);
++      if (!dev)
++              return 0;
++
++      spin_lock(&dev->lock);
++
++      if (dmxdevfilter->state != DMXDEV_STATE_GO) {
++              spin_unlock(&dev->lock);
++              return 0;
++      }
++
++      /* Walk the feed list under lock and confirm one of our dmxdev_feed
++       * entries still points back at this dmx_ts_feed. filter_stop sets
++       * f->ts = NULL per entry after release_ts_feed, so a late dispatch
++       * from a recycled slot finds no match and is dropped. */
++      {
++              struct dmxdev_feed *f;
++              bool match = false;
++
++              list_for_each_entry(f, &dmxdevfilter->feed.ts, next) {
++                      if (READ_ONCE(f->ts) == feed) {
++                              match = true;
++                              break;
++                      }
++              }
++              if (!match) {
++                      spin_unlock(&dev->lock);
++                      return 0;
++              }
++      }
++
+       if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++              spin_unlock(&dev->lock);
+               return 0;
+       }
+@@ -411,14 +482,14 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len,
+           || dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP)
+               buffer = &dmxdevfilter->buffer;
+       else
+-              buffer = &dmxdevfilter->dev->dvr_buffer;
++              buffer = &dev->dvr_buffer;
+       if (buffer->error) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++              spin_unlock(&dev->lock);
+               wake_up(&buffer->queue);
+               return 0;
+       }
+       if (dvb_ringbuffer_free(buffer) < (buffer1_len + buffer2_len)) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++              spin_unlock(&dev->lock);
+               wake_up(&buffer->queue);
+               return dvb_ringbuffer_free(buffer);
+       }
+@@ -427,7 +498,7 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len,
+               ret = dvb_dmxdev_buffer_write(buffer, buffer2, buffer2_len);
+       if (ret < 0)
+               buffer->error = ret;
+-      spin_unlock(&dmxdevfilter->dev->lock);
++      spin_unlock(&dev->lock);
+       wake_up(&buffer->queue);
+       return 0;
+ }
diff --git a/archive-patches/armbox/hd6x/linux-4.4.35-dvb-core-section-callback-uaf-guard.patch b/archive-patches/armbox/hd6x/linux-4.4.35-dvb-core-section-callback-uaf-guard.patch
new file mode 100644 (file)
index 0000000..e7cef1d
--- /dev/null
@@ -0,0 +1,150 @@
+--- a/drivers/media/dvb-core/dmxdev.c
++++ b/drivers/media/dvb-core/dmxdev.c
+@@ -44,6 +44,12 @@
+       ssize_t free;
+       if (!len)
++              return 0;
++      /* Out-of-tree dispatchers can pass NULL src with non-zero len
++       * when they split a section across two buffers and the second
++       * half was never populated. Treat as a no-op rather than feed
++       * NULL into the ringbuffer memcpy. */
++      if (!src)
+               return 0;
+       if (!buf->data)
+               return 0;
+@@ -358,16 +364,40 @@
+                                      const u8 *buffer2, size_t buffer2_len,
+                                      struct dmx_section_filter *filter)
+ {
+-      struct dmxdev_filter *dmxdevfilter = filter->priv;
++      struct dmxdev_filter *dmxdevfilter;
++      struct dmxdev *dev;
+       int ret;
+-      if (dmxdevfilter->buffer.error) {
+-              wake_up(&dmxdevfilter->buffer.queue);
++      /* filter itself is never expected NULL, but belt and braces, the
++       * out-of-tree dispatcher has surprised us before. */
++      if (!filter)
+               return 0;
++
++      /* READ_ONCE forces the deref to stay where we wrote it. Without it
++       * -Os hoists the load of filter->priv over the NULL check, turning
++       * the guard into a no-op in the generated code. */
++      dmxdevfilter = READ_ONCE(filter->priv);
++      if (!dmxdevfilter)
++              return 0;
++
++      dev = READ_ONCE(dmxdevfilter->dev);
++      if (!dev)
++              return 0;
++
++      spin_lock(&dev->lock);
++
++      /* Back-pointer check under lock. If an out-of-tree driver has
++       * recycled the dmx_section_filter slot between our READ_ONCE and
++       * the lock acquire, filter.sec no longer points back at this
++       * filter and the dispatch belongs to a freed/replaced session. */
++      if (dmxdevfilter->state != DMXDEV_STATE_GO ||
++          dmxdevfilter->filter.sec != filter) {
++              spin_unlock(&dev->lock);
++              return 0;
+       }
+-      spin_lock(&dmxdevfilter->dev->lock);
+-      if (dmxdevfilter->state != DMXDEV_STATE_GO) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++      if (dmxdevfilter->buffer.error) {
++              spin_unlock(&dev->lock);
++              wake_up(&dmxdevfilter->buffer.queue);
+               return 0;
+       }
+       del_timer(&dmxdevfilter->timer);
+@@ -382,7 +412,7 @@
+               dmxdevfilter->buffer.error = ret;
+       if (dmxdevfilter->params.sec.flags & DMX_ONESHOT)
+               dmxdevfilter->state = DMXDEV_STATE_DONE;
+-      spin_unlock(&dmxdevfilter->dev->lock);
++      spin_unlock(&dev->lock);
+       wake_up(&dmxdevfilter->buffer.queue);
+       return 0;
+ }
+@@ -391,13 +421,54 @@
+                                 const u8 *buffer2, size_t buffer2_len,
+                                 struct dmx_ts_feed *feed)
+ {
+-      struct dmxdev_filter *dmxdevfilter = feed->priv;
++      struct dmxdev_filter *dmxdevfilter;
++      struct dmxdev *dev;
+       struct dvb_ringbuffer *buffer;
+       int ret;
+-      spin_lock(&dmxdevfilter->dev->lock);
++      /* Same guard pattern as dvb_dmxdev_section_callback. Out-of-tree
++       * dispatchers can deliver a callback after release_ts_feed has
++       * returned, with feed->priv cleared or the filter slot recycled. */
++      if (!feed)
++              return 0;
++
++      dmxdevfilter = READ_ONCE(feed->priv);
++      if (!dmxdevfilter)
++              return 0;
++
++      dev = READ_ONCE(dmxdevfilter->dev);
++      if (!dev)
++              return 0;
++
++      spin_lock(&dev->lock);
++
++      if (dmxdevfilter->state != DMXDEV_STATE_GO) {
++              spin_unlock(&dev->lock);
++              return 0;
++      }
++
++      /* Walk the feed list under lock and confirm one of our dmxdev_feed
++       * entries still points back at this dmx_ts_feed. filter_stop sets
++       * f->ts = NULL per entry after release_ts_feed, so a late dispatch
++       * from a recycled slot finds no match and is dropped. */
++      {
++              struct dmxdev_feed *f;
++              bool match = false;
++
++              list_for_each_entry(f, &dmxdevfilter->feed.ts, next) {
++                      if (READ_ONCE(f->ts) == feed) {
++                              match = true;
++                              break;
++                      }
++              }
++              if (!match) {
++                      spin_unlock(&dev->lock);
++                      return 0;
++              }
++      }
++
+       if (dmxdevfilter->params.pes.output == DMX_OUT_DECODER) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++              spin_unlock(&dev->lock);
+               return 0;
+       }
+@@ -405,9 +476,9 @@
+           || dmxdevfilter->params.pes.output == DMX_OUT_TSDEMUX_TAP)
+               buffer = &dmxdevfilter->buffer;
+       else
+-              buffer = &dmxdevfilter->dev->dvr_buffer;
++              buffer = &dev->dvr_buffer;
+       if (buffer->error) {
+-              spin_unlock(&dmxdevfilter->dev->lock);
++              spin_unlock(&dev->lock);
+               wake_up(&buffer->queue);
+               return 0;
+       }
+@@ -416,7 +487,7 @@
+               ret = dvb_dmxdev_buffer_write(buffer, buffer2, buffer2_len);
+       if (ret < 0)
+               buffer->error = ret;
+-      spin_unlock(&dmxdevfilter->dev->lock);
++      spin_unlock(&dev->lock);
+       wake_up(&buffer->queue);
+       return 0;
+ }
index 4da8348ce6889b076728f6b15e1a6fd83a8fca47..5d014d3b395ba27859b11a0dd9a2e84696d4e48a 100644 (file)
@@ -21,7 +21,8 @@ KERNEL_PATCHES = \
                armbox/$(BOXSERIES)/4_10_blacklist_mmc0.patch \
                armbox/$(BOXSERIES)/4_10_dvbs2x.patch \
                armbox/$(BOXSERIES)/4_10_reserve_dvb_adapter_0.patch \
-               armbox/$(BOXSERIES)/4_10_t230c2.patch
+               armbox/$(BOXSERIES)/4_10_t230c2.patch \
+               armbox/$(BOXSERIES)/linux-4.10.12-dvb-core-section-callback-uaf-guard.patch
 
 endif
 
@@ -43,7 +44,8 @@ KERNEL_PATCHES = \
                armbox/$(BOXSERIES)/0013-modules_mark__inittest__exittest_as__maybe_unused.patch \
                armbox/$(BOXSERIES)/0014-includelinuxmodule_h_copy__init__exit_attrs_to_initcleanup_module.patch \
                armbox/$(BOXSERIES)/0015-Backport_minimal_compiler_attributes_h_to_support_GCC_9.patch \
-               armbox/$(BOXSERIES)/0016-mn88472_reset_stream_ID_reg_if_no_PLP_given.patch
+               armbox/$(BOXSERIES)/0016-mn88472_reset_stream_ID_reg_if_no_PLP_given.patch \
+               armbox/$(BOXSERIES)/linux-4.4.35-dvb-core-section-callback-uaf-guard.patch
 endif
 
 ifeq ($(BOXMODEL),$(filter $(BOXMODEL), hd51 bre2ze4k h7 e4hdultra))