fix: 2 nullderef / size_t-wrap bugs found by static analysis#217
Open
blackden wants to merge 2 commits into
Open
fix: 2 nullderef / size_t-wrap bugs found by static analysis#217blackden wants to merge 2 commits into
blackden wants to merge 2 commits into
Conversation
Com_StringContains computed:
int len = strlen( str1 ) - strlen( str2 );
When str2 is longer than str1 (a normal lookup case — searching for a
long pattern in a short cvar / filename / map name), the size_t
subtraction underflows to a huge unsigned value before the implicit
truncation to int. On LP64 (Apple Silicon) the truncated value is
indeterminate and the loop `i <= len` runs with a garbage bound,
reading str1[j] past the end of the string and feeding garbage into
the equality comparison.
The substring search semantically can't match when the needle is
longer than the haystack, so add an explicit early-return guard
before the subtraction. Both lengths are now also cached so strlen
isn't called twice.
Clang's static analyzer flagged five downstream sites in this and
Com_Filter (common.c:632/636/658/726/730 - core.uninitialized.Assign /
Branch / UndefinedBinaryOperatorResult / CallAndMessage). All five
stem from this one root cause. The bug is inherited from upstream
ioquake3.
Three non-doppler chunk-advance sites in snd_mix.c read the next chunk
and immediately dereferenced it:
chunk = chunk->next;
samples = chunk->sndChunk;
sndBuffer->next is explicitly NULL-terminated at allocation time
(snd_mem.c:73), so on end-of-sound boundary crossing the second line
dereferences a null pointer.
The doppler variants in the same file already guard with the engine's
established wrap-on-exhaustion idiom:
chunk = chunk->next;
if ( !chunk ) {
chunk = sc->soundData;
}
Apply the same idiom to the non-doppler paths:
- S_SetVoiceAmplitudeFrom16 (snd_mix.c:286)
- S_PaintChannelFrom16_scalar (snd_mix.c:530, non-doppler branch)
- S_PaintChannelFromMuLaw (snd_mix.c:705, non-doppler branch)
This is a SDL/legacy mixer-only path; the default macOS audio backend
is OpenAL (snd_openal.c) where this code does not run. Bug inherited
from upstream ioquake3.
Found by clang static analyzer (core.NullDereference at snd_mix.c:290,
521, 700).
4 tasks
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.
Summary
Two narrow, well-isolated bugs surfaced by
cppcheck+clang scan-buildwhile auditing the engine. Both pre-date RealRTCW (inherited from id Tech 3 / RTCW SDK), affect all platforms, and are reproducible on x86_64 and arm64.Commit 1 —
qcommon: guardCom_StringContainsagainststr2longer thanstr1Com_StringContainscomputeslen = strlen(str1) - strlen(str2)asint. On LP64 the subtraction is done insize_t; whenstr2is longer thanstr1the result wraps to a huge positive number, which is then truncated to a large positiveint. The function then runsfor (i = 0; i <= len; i++)readingstr1[i + j]far past the buffer end.Fix: compute as
size_t, early-returnNULLwhenstr2is longer.Detected as:
scan-buildreported 5 downstream OOB-read warnings inCom_Filter(which callsCom_StringContains); after the fix → 2 unrelated warnings remain.Commit 2 —
client: wrapchunk->nextNULL tosoundDatain SDL mixer paint pathsIn
S_PaintChannelFrom16_scalar,SetVoiceAmplitudeFrom16, andS_PaintChannelFromMuLaw, when the mix advances past the end of the current sndBuffer chunk, the code doeschunk = chunk->next; samples = chunk->sndChunk;unconditionally. For non-looping sounds at the very end,chunk->nextis NULL — null-deref.The doppler-shift path (just below in the same file) already handles this with
if (!chunk) chunk = sc->soundData;. Mirror that pattern in the three non-doppler sites.Detected as:
scan-buildreported 3 nullderef warnings on these exact lines; after the fix → 0.Test plan
make ARCH=arm64 USE_INTERNAL_LIBS=0cppcheckclean on touched filesscan-build: 5→2 warnings aroundCom_Filter, 3→0 insnd_mix.c-fsanitize=address,undefined): playthrough of campaign level 1 with\find <very-long-string>from console + 60s of legacy-mixer sound (s_useOpenAL 0; snd_restart) — no new sanitizer hits in either modified file\find ...over a long needle returns cleanly, no hangNotes
These are upstream-clean fixes — they touch only
code/qcommon/common.candcode/client/snd_mix.c, no platform-specific code. ioquake3 and other Q3-family forks carry identical code and would benefit from the same patches.🤖 Generated with Claude Code