Repository navigation
Conversation
…ites
esp32_handler_process_byte() copies the 16-bit payload_size from the MSPv2
header and then stores every payload byte into packet.payload[], which is
MSP_PORT_INBUF_SIZE (64) bytes. Nothing rejected a larger size, and the CRC
is only checked after the payload, so a single corrupted or malicious frame
on the backpack UART could write up to 64 KiB past the buffer into the
neighbouring statics before being discarded.
Drop the frame as soon as the header announces more than 64 bytes.
handle_osd() case 0x03 ("write string") also indexed elrs_osd_overlay
[HD_VMAX][HD_HMAX] with the row and column taken straight from the frame.
The length was clamped against col, but neither row nor col was checked,
and a payload shorter than 4 bytes read past the data received. Check the
size and both indices before writing.
settings_load() reads scan.channel, source.hdzero_band and
source.analog_channel from setting.ini without fully checking them:
- analog_channel above ANALOG_CHANNEL_NUM reset scan.channel instead
of analog_channel (copy-paste slip), and 0 was not rejected;
- scan.channel 0 was not rejected;
- hdzero_band was not checked at all.
These values index the channel name tables in channel2str():
analog_channel 0 reads analog_channel_name[-1], and an hdzero_band
other than 0 or 1 reads past hdzero_channel_name[2][].
Read the three of them through settings_get_long_bounded(), which
checks the long returned by ini_getl() before it is narrowed to the
field's type (257 stored in the uint8_t analog_channel would otherwise
pass as 1), and falls back to the default when out of range.
scan.channel is now read after hdzero_band, as HDZERO_CHANNEL_NUM
depends on it. channel2str() also guards its analog index like the
HDZero branch already does.
The function kept a pointer to entry->d_name, called closedir() and returned that pointer. d_name lives in the DIR's internal buffer: it is reused as readdir() refills it (so comparing against an earlier entry was already unreliable) and freed by closedir(). The caller then formatted a path from freed memory. It happened to work because the allocator had not reused the block yet. Copy the winning name into a caller-provided buffer before closing the directory.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three small memory-safety fixes found while reading the code. All three are compiled on every target; none changes behaviour on valid input.
1. MSPv2 parser: unbounded payload size (
src/core/elrs.c)esp32_handler_process_byte()copies the 16-bitpayload_sizefrom the MSPv2 header, then writes every payload byte intopacket.payload[], which isMSP_PORT_INBUF_SIZE(64) bytes. Nothing rejected a larger size, and the CRC is only checked after the payload, so one corrupted frame on the backpack UART can write up to 64 KiB past the buffer before being discarded.The fix drops the frame as soon as the header announces more than 64 bytes.
In the same file,
handle_osd()case0x03indexedelrs_osd_overlay[HD_VMAX][HD_HMAX]withrowandcoltaken straight from the frame; the length was clamped againstcolbut neither index was checked, and a payload shorter than 4 bytes read past the received data. Both are now checked.Other MSP handlers read a few payload bytes without checking
payload_size; with the 64-byte cap these reads stay inside the buffer (stale data, not an overflow) and are left alone here.2. Out-of-range channel settings (
src/core/settings.c,src/core/osd.c)settings_load()readsscan.channel,source.hdzero_bandandsource.analog_channelfromsetting.iniwithout fully checking them:analog_channelaboveANALOG_CHANNEL_NUMresetscan.channelinstead ofanalog_channel(copy-paste slip), and0was not rejected;scan.channel0was not rejected;hdzero_bandwas not checked at all.These values index the channel name tables in
channel2str():analog_channel = 0readsanalog_channel_name[-1], and anhdzero_bandother than 0 or 1 reads pasthdzero_channel_name[2][]; the pointer is then handed tosnprintf(). Each value is now checked on thelongreturned byini_getl(), before it is narrowed to the field's type (257in theuint8_tanalog_channelwould otherwise pass as1), and reset to its default when out of range;scan.channelis read afterhdzero_band, whichHDZERO_CHANNEL_NUMdepends on. In addition,channel2str()guards its analog index like the HDZero branch already does, returning"--"out of range. The analog path is reachable on Goggles 2 and BoxPro (the original Goggles hides the analog channel label).3.
page_version_find_latest_fw(): dirent name used afterclosedir()(src/ui/page_version.c)The function kept a pointer to
entry->d_name, calledclosedir(), and returned it.d_namelives in theDIR's internal buffer: it is reused asreaddir()refills it and freed byclosedir(), so the caller built a path from freed memory. It worked by luck (the block had not been reused yet). The name is now copied into a caller-provided buffer before the directory is closed.Testing
-DEMULATOR_BUILD=ON -DHDZ_GOGGLE2=ON) compiles without new warnings.clang-formatclean with the repository's.clang-format.-DHDZ_GOGGLE2=ON, Bootlin armv7 musl toolchain) compiles without new warnings.select()fix and our own UI patches, deployed from SD): the app boots, the ESP32 backpack comes up, its reader thread stays idle, Wi-Fi works, and the app sits at about half a core at idle. The new MSP size check dropped no frame during normal operation.page_version_find_latest_fw()plus an exact copy ofstr_compare_versions(), run on-device against aFIRMWARE/directory with 8 version-like subdirectories, 20000 iterations each followed by unrelated heap churn (128 allocations freed in between, to make the freedDIRbuffer available for reuse and any use-after-free visible). Each iteration asserts the result equals the known-correct answer ("10.0.0", computed ahead of time against the unmodifiedstr_compare_versions()), not just that it stays the same across iterations. Result: 20000/20000 correct, 0 mismatches.setting.inivalues (fix 2), on Goggles 2 hardware: checked two ways.settings_get_long_bounded()logic against the realminIniparser (cross-compiled, run on-device) readinganalog_channel=0,analog_channel=257,hdzero_band=2,scan.channel=0: all four clamp to their defaults (33,33,0,1).setting.inion a running unit, app killed and restarted (warm, no full reboot): boots normally, no crash, log shows the usual startup sequence (intro, Wi-Fi scan, MQTT heartbeat).