# UNDF: UNDF-2026-000000684 --- a/libavcodec/gif.c +++ b/libavcodec/gif.c @@ -67,18 +67,53 @@ static void shrink_palette(const uint32_t *src, uint8_t *map, uint32_t *dst, size_t *palette_count) { - size_t colors_seen = 0; - - for (size_t i = 0; i < AVPALETTE_COUNT; i++) { - int seen = 0; - for (size_t c = 0; c < colors_seen; c++) { - if (src[i] == dst[c]) { - seen = 1; - break; - } - } - if (!seen) { - dst[colors_seen] = src[i]; - map[i] = colors_seen; - colors_seen++; - } - } - - *palette_count = colors_seen; + /* + * CWE-407 fix: replace O(P²) nested scan with an open-addressing hash + * table over the 256-entry colour space. + * + * Original: for each of P=256 entries, scan all previously-seen entries → + * O(0+1+…+255) = 32,640 comparisons worst-case per frame. + * + * Fix: Knuth multiplicative hash folds 32-bit ARGB to an 8-bit slot; + * linear probing resolves collisions. Total work: O(P) = 256 hash ops. + * Speedup: ~127× worst-case (all 256 colours unique). + * + * Sentinel: 0xFFFFFFFF (fully-opaque white BGRA). A separate occupied[] + * boolean array guards against false-hit on the sentinel value. + */ + uint32_t seen_color[AVPALETTE_COUNT]; + uint8_t seen_slot[AVPALETTE_COUNT]; + uint8_t occupied[AVPALETTE_COUNT]; + size_t colors_seen = 0; + + memset(occupied, 0, sizeof(occupied)); + + for (size_t i = 0; i < AVPALETTE_COUNT; i++) { + uint32_t color = src[i]; + /* Knuth multiplicative hash → 8-bit bucket index */ + size_t h = (size_t)((color * 2654435761UL) >> 24) & 0xFF; + + /* Linear-probe open-addressing lookup */ + while (occupied[h] && seen_color[h] != color) + h = (h + 1) & 0xFF; + + if (occupied[h]) { + /* colour already in hash table: reuse its dst slot */ + map[i] = seen_slot[h]; + } else { + /* new colour: insert into hash table and dst[] */ + occupied[h] = 1; + seen_color[h] = color; + seen_slot[h] = (uint8_t)colors_seen; + dst[colors_seen] = color; + map[i] = (uint8_t)colors_seen; + colors_seen++; + } + } + + *palette_count = colors_seen; }