From 42a9ba52f894a299a9bf8d1bc90ee18cdd62236a Mon Sep 17 00:00:00 2001 From: Markham Date: Fri, 1 May 2026 15:26:13 +0200 Subject: [PATCH] add kernel 4.10.12 & 4.4.35 dvb-core-section-callback-uaf-guard.patch 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 --- ...-dvb-core-section-callback-uaf-guard.patch | 158 ++++++++++++++++++ ...-dvb-core-section-callback-uaf-guard.patch | 150 +++++++++++++++++ make/kernel-arm.mk | 6 +- 3 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 archive-patches/armbox/hd5x/linux-4.10.12-dvb-core-section-callback-uaf-guard.patch create mode 100644 archive-patches/armbox/hd6x/linux-4.4.35-dvb-core-section-callback-uaf-guard.patch 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 index 0000000..c3be499 --- /dev/null +++ b/archive-patches/armbox/hd5x/linux-4.10.12-dvb-core-section-callback-uaf-guard.patch @@ -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 index 0000000..e7cef1d --- /dev/null +++ b/archive-patches/armbox/hd6x/linux-4.4.35-dvb-core-section-callback-uaf-guard.patch @@ -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; + } diff --git a/make/kernel-arm.mk b/make/kernel-arm.mk index 4da8348..5d014d3 100644 --- a/make/kernel-arm.mk +++ b/make/kernel-arm.mk @@ -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)) -- 2.39.5