fix(dma): guard dmaDescriptors[] indexing on F4/F7 against invalid identifiers - #1344
Conversation
…entifiers Ports dmaIdentifierIsValid() from PR emuflight#1325 (H7 fix for issue emuflight#1205) to dma_stm32f4xx.c and dma_stm32f7xx.c. dmaGetOwner, dmaGetResourceIndex, dmaGetRefByIdentifier, dmaGetDescriptorByIdentifier, dmaSetHandler, and dmaInit indexed dmaDescriptors[] via DMA_IDENTIFIER_TO_INDEX() with no bounds check; dmaAllocate/dmaEnable only checked identifier != DMA_NONE, not the upper bound. identifier == DMA_NONE underflows the index, identifier > DMA_LAST_HANDLER overflows it — undefined behavior on any caller that passes an invalid identifier. BF 4.5-maintenance has zero bounds validation on the equivalent path — EF-original defensive hardening, not a BF port. Closes emuflight#1341. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughF4 and F7 DMA drivers add identifier validation before indexing ChangesDMA identifier hardening
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Comments should state the invariant, not a ticket number. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@coderabbitai , analyze the changes in this PR -- are they safe for merge and do not introduce problems, regression, inefficiency? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== PR metadata and changed files ==='
gh pr view 1344 --repo emuflight/EmuFlight --json number,title,baseRefName,headRefName,commits,statusCheckRollup \
--jq '{number,title,base:.baseRefName,head:.headRefName,commits:(.commits|length),checks:[.statusCheckRollup[]? | {name,conclusion,status}]}'
git diff --find-renames origin/"$(gh pr view 1344 --repo emuflight/EmuFlight --json baseRefName --jq .baseRefName)"...HEAD -- \
src/main/drivers/dma_stm32f4xx.c src/main/drivers/dma_stm32f7xx.c
echo '=== Driver outlines ==='
ast-grep outline src/main/drivers/dma_stm32f4xx.c --items all
ast-grep outline src/main/drivers/dma_stm32f7xx.c --items all
fd -a '^dma_stm32h7xx\.c$|^dma\.h$' src/main
echo '=== Identifier definitions and descriptor declarations ==='
rg -n -C 5 'DMA_NONE|DMA_LAST_HANDLER|dmaIdentifier_e|dmaDescriptors' src/main/drivers src/main | head -n 360
echo '=== All descriptor indexing and affected API call sites ==='
rg -n -C 3 'dmaDescriptors\s*\[|dma(SetHandler|GetOwner|GetResourceIndex|GetRefByIdentifier|GetDescriptorByIdentifier|Allocate|Enable|Init)\s*\(' \
src/main --glob '*.{c,h}'Length of output: 88865 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== Direct index macro uses outside DMA drivers ==='
rg -n -C 4 'DMA_IDENTIFIER_TO_INDEX\s*\(' src/main --glob '*.{c,h}' \
-g '!src/main/drivers/dma_stm32f4xx.c' \
-g '!src/main/drivers/dma_stm32f7xx.c' \
-g '!src/main/drivers/dma_stm32h7xx.c' \
-g '!src/main/drivers/dma.c' \
-g '!src/main/drivers/dma.h' || true
echo '=== Identifier getter / descriptor getter consumers (with enclosing context) ==='
rg -n -C 10 '\bdmaGet(RefByIdentifier|DescriptorByIdentifier|Owner|ResourceIndex)\s*\(' \
src/main --glob '*.{c,h}'
echo '=== Relevant F4/F7 consumer configuration paths ==='
for f in \
src/main/drivers/bus_spi.c \
src/main/drivers/sdcard.c \
src/main/drivers/sdcard_sdio_baremetal.c \
src/main/drivers/serial_uart_stm32f4xx.c \
src/main/drivers/serial_uart_stm32f7xx.c \
src/main/drivers/adc_stm32f4xx.c \
src/main/drivers/adc_stm32f7xx.c \
src/main/drivers/pwm_output_dshot.c \
src/main/drivers/pwm_output_dshot_hal.c \
src/main/drivers/light_ws2811strip_stdperiph.c \
src/main/drivers/light_ws2811strip_hal.c \
src/main/drivers/transponder_ir_io_stdperiph.c \
src/main/drivers/transponder_ir_io_hal.c; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n -C 12 '(dmaGet(RefByIdentifier|DescriptorByIdentifier)|dmaInit|dmaSetHandler|dmaEnable|dmaAllocate)' "$f" || true
fi
done
echo '=== PR test changes and status update ==='
git diff --check origin/master...HEAD
git diff --stat origin/master...HEAD
git diff --name-only origin/master...HEAD | rg -n '(test|unit|dma)' || true
gh pr view 1344 --repo emuflight/EmuFlight --json statusCheckRollup \
--jq '[.statusCheckRollup[]? | {name,conclusion,status}]'Length of output: 48309
One minor follow-up opportunity—not merge-blocking—is to add focused tests for |
Adds dma_bounds_unittest covering the identifier-range guard duplicated in dma_stm32f4xx.c/dma_stm32f7xx.c/dma_stm32h7xx.c: DMA_NONE and DMA_LAST_HANDLER + 1 must be rejected, the first and last valid handler values must be accepted. Compiles dma.h directly with STM32F4 defined via the test's _DEFINES entry, since dmaIdentifierIsValid() is a private static inline function duplicated per MCU family and not exposed through any shared header. Adds a DMA_Stream_TypeDef opaque stub to the test platform.h, following the same pattern already used for DMA_TypeDef/DMA_Channel_TypeDef, so the F4/F7/H7 branch of dmaChannelDescriptor_t can compile on host. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@coderabbitai, analyze the dma_bounds_unittest.cc added in commit e81649a. It covers the DMA_NONE / DMA_LAST_HANDLER + 1 boundary case you flagged as a non-blocking follow-up. Note it tests dma.h's enum contract (DMA_NONE, DMA_LAST_HANDLER, DMA_IDENTIFIER_TO_INDEX) with STM32F4 defined, not a direct call into dmaIdentifierIsValid() itself, since that function is a private static inline duplicated per MCU family in each .c file and not exposed via any shared header. Is this adequate coverage of the behavior you asked for, or is there a way to reach the real function without a disproportionate HAL/CMSIS stubbing effort? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== Commit and changed files ==='
git show --stat --oneline e81649adc
git show --format=fuller --no-ext-diff --find-renames e81649adc -- \
':!src/main/drivers/dma_stm32f4xx.c' ':!src/main/drivers/dma_stm32f7xx.c'
echo '=== Locate and map the boundary test ==='
fd -a '^dma_bounds_unittest\.cc$' .
test_file="$(fd -a '^dma_bounds_unittest\.cc$' . | head -n 1 || true)"
if [ -n "$test_file" ]; then
ast-grep outline "$test_file" --items all
cat -n "$test_file"
fi
echo '=== F4/F7 helper implementations and all descriptor accesses ==='
for driver in src/main/drivers/dma_stm32f4xx.c src/main/drivers/dma_stm32f7xx.c; do
if [ -f "$driver" ]; then
echo "--- $driver ---"
ast-grep outline "$driver" --items all --match 'dmaIdentifierIsValid|dmaAllocate|dmaEnable|dmaInit|dmaSetHandler|dmaGetOwner|dmaGetResourceIndex|dmaGetRefByIdentifier|dmaGetDescriptorByIdentifier'
rg -n -C 7 'dmaIdentifierIsValid|dmaDescriptors\s*\[' "$driver"
fi
done
echo '=== Enum/macro contract and test registration references ==='
rg -n -C 5 'DMA_NONE|DMA_LAST_HANDLER|DMA_IDENTIFIER_TO_INDEX' src/main test . \
-g '*.{h,c,cc,cpp,CMakeLists.txt,cmake,make,mk}' 2>/dev/null | head -n 500
rg -n -C 4 'dma_bounds_unittest|dma_bounds' . \
-g '*.{CMakeLists.txt,cmake,make,mk,cc,cpp,h}' 2>/dev/null || trueLength of output: 54040
The test correctly establishes the F4/F7/H7 enum boundary invariant:
That matches the exact predicate currently present in both production implementations:
It also compiles The limitation is that
However, I do not think expanding this PR to test those production functions is worthwhile. Reaching them directly means compiling the F4/F7 driver translation units and supplying sufficient RCC/NVIC/HAL/CMSIS symbols, register objects, and DMA descriptor dependencies. That is substantial scaffolding for code whose relevant logic is two comparisons, and it would be more fragile than the behavior being tested. The cleaner alternatives would be either:
Both add coupling or production/test-surface complexity without meaningful additional value here. So: the new test resolves my suggested follow-up adequately. The source review already confirms every newly protected F4/F7 ✏️ Learnings added
|
AI Generated [pull-request]
Summary
dmaIdentifierIsValid()from PR fix(h7): bounds-validate dmaIdentifier_e before dmaDescriptors[] indexing #1325 (H7 fix for issue fix(h7): dma_stm32h7xx.c — bounds-validate dmaIdentifier_e before indexing dmaDescriptors[] #1205) todma_stm32f4xx.canddma_stm32f7xx.cdmaGetOwner,dmaGetResourceIndex,dmaGetRefByIdentifier,dmaGetDescriptorByIdentifier,dmaSetHandler, anddmaInitindexeddmaDescriptors[]viaDMA_IDENTIFIER_TO_INDEX()with no bounds check;dmaAllocate/dmaEnableonly checkedidentifier != DMA_NONE, not the upper boundidentifier == DMA_NONEunderflows the index,identifier > DMA_LAST_HANDLERoverflows it — undefined behavior/potential hardfault on any caller that passes an invalid identifierCloses #1341.
Test plan
make clean_test && make test— all suites pass, 0 failuresmake HELIOSPRING TUNERCF405 SKYSTARSF405AIO PYRODRONEF7 FOXEERF722V4 FOXEERF405 APEXF7 TMOTORF7— 8 succeeded, 0 failed, no new warnings--base upstream/master): 0 findingsSummary by CodeRabbit