Fix out-of-bounds write in gguf_q4_0/q4_1_to_float second nibble loop#31
Open
gigioneggiando wants to merge 1 commit into
Open
Fix out-of-bounds write in gguf_q4_0/q4_1_to_float second nibble loop#31gigioneggiando wants to merge 1 commit into
gigioneggiando wants to merge 1 commit into
Conversation
Both dequantizers process each quantization block with two separate 16-iteration loops (lower nibbles, then upper nibbles). The break on (++i == count) only exits whichever loop is currently running. When count is reached inside the FIRST loop, that loop breaks correctly, but the SECOND loop then still runs unconditionally and writes up to 16 more floats past the end of dst, because i is already == count and its own (++i == count) check can never fire again. A caller that sizes dst as count * sizeof(float) (as gguf_tensor_to_float does) is overrun whenever count lands inside a block's first half. With an attacker-controlled tensor dimension this is a heap out-of-bounds write reachable by dequantizing/inspecting a crafted Q4_0/Q4_1 tensor. Stop the block loop as soon as i == count, before entering the upper loop. This is also strictly more correct for any legitimate count that is not a multiple of the 32-weight block.
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.
Problem
gguf_q4_0_to_float()andgguf_q4_1_to_float()dequantize each block with two separate 16-iteration loops (lower nibbles, then upper nibbles). Thebreakonif (++i == count)only exits whichever loop is currently running:If
countis reached partway through the first loop, that loop breaks correctly, but the second loop still runs unconditionally and writes up to 16 more floats past the end ofdst, becauseiis already== countand its own++i == countcheck can never fire again.Callers size the destination as
count * sizeof(float)(this is exactly whatgguf_tensor_to_float()does:malloc(tensor->num_weights * sizeof(float))). So whenevercountlands inside a block's first half, the destination is overrun. Sincecountis derived from an attacker-controlled tensor dimension in a downloaded.gguffile, this is a heap out-of-bounds write reachable by dequantizing/inspecting a crafted Q4_0/Q4_1 tensor (e.g.inspect-tensor,compare, or anygguflibcaller).Reproduction
Built release (
-DNDEBUG) under AddressSanitizer, driving the realgguf_q4_0_to_floatwithcount = 1and amalloc(4)destination:Fix
Stop the block loop as soon as
i == count, before entering the upper-nibble loop. No integer-overflow trickery is involved and no build flag is required to trigger the bug, so this is a plain logic fix. It is also strictly more correct for any legitimatecountthat is not a multiple of the 32-weight block (the old code would write past the caller's buffer there too).Verified with the same ASan harness after the patch: the call returns cleanly, no out-of-bounds write.
I ran this by a small local audit of the GGUF read path against untrusted model files; happy to share more detail privately if useful. No CVE or public advisory from my side.