4.1 KiB
raylib — CWE-407 Disclosure Brief
Project: raylib Disclosure date: 2026-03-27 Severity: HIGH Speedup: varies by defect Status: PATCHED
Finding
Two independent defects in raylib use linear search where O(1) hash structures are appropriate. The first (raylib-0001) performs O(G) glyph index lookup per codepoint on every text draw call, making text rendering O(G) per character. The second (raylib-0002) uses std::find-style linear deduplication when generating cellular noise images, producing O(n²) sequence deduplication.
The Defect(s)
| ID | Location | Pattern | Complexity |
|---|---|---|---|
| raylib-0001 | src/rtext.c |
GetGlyphIndex() O(G) linear scan per codepoint per text draw call |
O(G) per character, O(G×L) per string of length L |
| raylib-0002 | src/rshapes.c |
GenerateImageCellular() random-sequence dedup O(n²) linear find |
O(n²) per image generation |
Complexity Proof
raylib-0001: Let G = number of glyphs loaded in a Font (typically 95–512 for ASCII/extended sets), L = length of string being rendered.
GetGlyphIndex() searches for a codepoint by scanning the Font.glyphs array linearly:
for (int i = 0; i < font.glyphCount; i++) {
if (font.glyphs[i].value == codepoint) return i;
}
This is O(G) per call. DrawText() and related functions call GetGlyphIndex() once per character in the string:
L characters × O(G) scan = O(L×G) per DrawText call
With a pre-built unordered_map<int, int> from codepoint to glyph index, each lookup is O(1):
O(G) to build map + O(L) for rendering = O(G + L)
For G=256, L=100: defective = 25,600 ops per DrawText; fixed = 356 ops.
raylib-0002: Let n = number of seeds/points used in cellular noise generation.
GenerateImageCellular() generates n random seed positions and deduplicates them using a growing array with linear-scan membership check:
Insert seed 1: check list of 0 → O(0)
Insert seed 2: check list of 1 → O(1)
...
Insert seed n: check list of n-1 → O(n-1)
Total: 0+1+...+(n-1) = n(n-1)/2 = O(n²)
With a HashSet tracking inserted positions: O(n) total.
Impact
raylib-0001 affects any raylib application rendering text with non-trivial string length or font sizes, including game UIs, debug overlays, and text-heavy applications. Since GetGlyphIndex() is on the hot path of every DrawText call in the game loop (typically called 60+ times per second), the O(G) overhead per character is multiplied by frame rate and string length.
raylib-0002 affects applications using GenerateImageCellular() for procedural texture generation. While not a per-frame operation, large cellular images with many seed points experience quadratic generation time.
The Fix
raylib-0001: At LoadFont time, build a HashMap<int codepoint, int glyphIndex> alongside the glyphs array. In GetGlyphIndex(), perform a hash lookup instead of a linear scan.
raylib-0002: In GenerateImageCellular(), replace the dedup array with an unordered_set<uint64_t> (encoding x,y as a single 64-bit key). Check and insert in O(1).
Patch
// raylib-0001: rtext.c
+ // At LoadFont time:
+ // font.glyphMap = HashMap mapping codepoint -> index (built once)
int GetGlyphIndex(Font font, int codepoint) {
- for (int i = 0; i < font.glyphCount; i++) {
- if (font.glyphs[i].value == codepoint) return i;
- }
- return 0;
+ return font.glyphMap[codepoint]; // O(1) hash lookup
}
// raylib-0002: rshapes.c
- int *seeds = ...; int seedCount = 0;
- // ... linear dedup ...
- bool found = false;
- for (int j = 0; j < seedCount; j++)
- if (seeds[j] == pos) { found = true; break; }
- if (!found) seeds[seedCount++] = pos;
+ unordered_set<int> seedSet;
+ // O(1) insert-and-check:
+ if (seedSet.insert(pos).second) seeds[seedCount++] = pos;
What We Ask
Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.
This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com