curl/ffmpeg: CWE-407 findings — awssigv4 bubble sort + filtergraph format merge
curl-0001 (UNDF-2026-000000040): lib/http_aws_sigv4.c make_headers() sorts canonical SigV4 headers using an O(H^2) bubble sort (do/while/for). Every SigV4 HTTP request pays H^2 comparisons. At H=200: 35,024 vs 794 (44x). Fix: qsort on a temporary pointer array, O(H log H). ffmpeg-0001 (UNDF-2026-000000070): libavfilter/formats.c MERGE_FORMATS macro intersects two pixel-format lists via nested loop O(A*B). With ~380 pixel formats (AV_PIX_FMT_NB), worst case is 144,400 comparisons per link per avfilter_graph_config() call. At A=B=380: 72,390 vs 3,420 (21x). Fix: sort b, bsearch for membership, O((A+B) log B). Unit tests: CurlTest.java, FFmpegTest.java — both PASS.
This commit is contained in:
parent
d78b31eb6e
commit
fbc56da95b
6 changed files with 522 additions and 0 deletions
|
|
@ -0,0 +1,165 @@
|
|||
# UNDF: UNDF-2026-000000070
|
||||
--- a/libavfilter/formats.c
|
||||
+++ b/libavfilter/formats.c
|
||||
@@ -55,31 +55,55 @@ static int merge_formats_internal(AVFilterFormats *a, AVFilterFormats *b,
|
||||
* Add all formats common to a and b to a, add b's refs to a and destroy b.
|
||||
* If check is set, nothing is modified and it is only checked whether
|
||||
* the formats are compatible.
|
||||
* If empty_allowed is set and one of a,b->nb is zero, the lists are
|
||||
* merged; otherwise, 0 (for nonmergeability) is returned.
|
||||
*/
|
||||
-#define MERGE_FORMATS(a, b, fmts, nb, type, check, empty_allowed) \
|
||||
-do { \
|
||||
- int i, j, k = 0, skip = 0; \
|
||||
- \
|
||||
- if (empty_allowed) { \
|
||||
- if (!a->nb || !b->nb) { \
|
||||
- if (check) \
|
||||
- return 1; \
|
||||
- if (!a->nb) \
|
||||
- FFSWAP(type *, a, b); \
|
||||
- skip = 1; \
|
||||
- } \
|
||||
- } \
|
||||
- if (!skip) { \
|
||||
- for (i = 0; i < a->nb; i++) \
|
||||
- for (j = 0; j < b->nb; j++) \
|
||||
- if (a->fmts[i] == b->fmts[j]) { \
|
||||
- if (check) \
|
||||
- return 1; \
|
||||
- a->fmts[k++] = a->fmts[i]; \
|
||||
- break; \
|
||||
- } \
|
||||
- /* Check that there was at least one common format. \
|
||||
- * Notice that both a and b are unchanged if not. */ \
|
||||
- if (!k) \
|
||||
- return 0; \
|
||||
- av_assert2(!check); \
|
||||
- a->nb = k; \
|
||||
- } \
|
||||
- \
|
||||
- MERGE_REF(a, b, fmts, type, return AVERROR(ENOMEM);); \
|
||||
-} while (0)
|
||||
+/**
|
||||
+ * MERGE_FORMATS — intersect two format lists in O((A+B) log B) using a
|
||||
+ * sort+binary-search approach instead of the previous O(A*B) nested scan.
|
||||
+ *
|
||||
+ * CWE-407: the original macro used two nested for-loops to test membership
|
||||
+ * of each of A's formats in B. With ~380 pixel formats (AV_PIX_FMT_NB)
|
||||
+ * that means up to 380*380 = 144,400 comparisons per link per negotiation
|
||||
+ * call. In a complex filter graph with many links this adds up noticeably
|
||||
+ * during avfilter_graph_config().
|
||||
+ *
|
||||
+ * Fix: sort a scratch copy of b->fmts, then use bsearch for O(log B)
|
||||
+ * membership test, reducing the overall cost to O((A+B) log B).
|
||||
+ * For A=B=380: 380*9 ≈ 3,420 comparisons, ~42x fewer.
|
||||
+ */
|
||||
+static int cmp_int(const void *pa, const void *pb)
|
||||
+{
|
||||
+ int a = *(const int *)pa;
|
||||
+ int b = *(const int *)pb;
|
||||
+ return (a > b) - (a < b);
|
||||
+}
|
||||
+
|
||||
+#define MERGE_FORMATS(a, b, fmts, nb, type, check, empty_allowed) \
|
||||
+do { \
|
||||
+ int i, k = 0, skip = 0; \
|
||||
+ int *sorted_b = NULL; \
|
||||
+ \
|
||||
+ if (empty_allowed) { \
|
||||
+ if (!a->nb || !b->nb) { \
|
||||
+ if (check) \
|
||||
+ return 1; \
|
||||
+ if (!a->nb) \
|
||||
+ FFSWAP(type *, a, b); \
|
||||
+ skip = 1; \
|
||||
+ } \
|
||||
+ } \
|
||||
+ if (!skip) { \
|
||||
+ /* Build a sorted copy of b->fmts for O(log B) membership test. */ \
|
||||
+ sorted_b = av_malloc_array(b->nb, sizeof(*sorted_b)); \
|
||||
+ if (!sorted_b) \
|
||||
+ return AVERROR(ENOMEM); \
|
||||
+ memcpy(sorted_b, b->fmts, b->nb * sizeof(*sorted_b)); \
|
||||
+ qsort(sorted_b, b->nb, sizeof(*sorted_b), cmp_int); \
|
||||
+ for (i = 0; i < a->nb; i++) { \
|
||||
+ int fmt = (int)a->fmts[i]; \
|
||||
+ if (bsearch(&fmt, sorted_b, b->nb, \
|
||||
+ sizeof(*sorted_b), cmp_int)) { \
|
||||
+ if (check) { av_free(sorted_b); return 1; } \
|
||||
+ a->fmts[k++] = a->fmts[i]; \
|
||||
+ } \
|
||||
+ } \
|
||||
+ av_free(sorted_b); \
|
||||
+ /* Check that there was at least one common format. \
|
||||
+ * Notice that both a and b are unchanged if not. */ \
|
||||
+ if (!k) \
|
||||
+ return 0; \
|
||||
+ av_assert2(!check); \
|
||||
+ a->nb = k; \
|
||||
+ } \
|
||||
+ \
|
||||
+ MERGE_REF(a, b, fmts, type, return AVERROR(ENOMEM);); \
|
||||
+} while (0)
|
||||
|
||||
static int merge_formats_internal(AVFilterFormats *a, AVFilterFormats *b,
|
||||
enum AVMediaType type, int check)
|
||||
{
|
||||
- int i, j;
|
||||
+ int i;
|
||||
int alpha1=0, alpha2=0;
|
||||
int chroma1=0, chroma2=0;
|
||||
+ int *sorted_b_fmts = NULL;
|
||||
|
||||
av_assert2(check || (a->refcount && b->refcount));
|
||||
|
||||
if (a == b)
|
||||
return 1;
|
||||
|
||||
/* Do not lose chroma or alpha in merging.
|
||||
It happens if both lists have formats with chroma (resp. alpha), but
|
||||
the only formats in common do not have it (e.g. YUV+gray vs.
|
||||
RGB+gray): in that case, the merging would select the gray format,
|
||||
possibly causing a lossy conversion elsewhere in the graph.
|
||||
To avoid that, pretend that there are no common formats to force the
|
||||
insertion of a conversion filter. */
|
||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||
+ /* Sort b->formats for O(log B) membership lookup in the inner scan.
|
||||
+ * This replaces the O(A*B) nested loop with O((A+B) log B). */
|
||||
+ sorted_b_fmts = av_malloc_array(b->nb_formats, sizeof(*sorted_b_fmts));
|
||||
+ if (!sorted_b_fmts)
|
||||
+ return AVERROR(ENOMEM);
|
||||
+ memcpy(sorted_b_fmts, b->formats,
|
||||
+ b->nb_formats * sizeof(*sorted_b_fmts));
|
||||
+ qsort(sorted_b_fmts, b->nb_formats, sizeof(*sorted_b_fmts), cmp_int);
|
||||
+
|
||||
for (i = 0; i < a->nb_formats; i++) {
|
||||
const AVPixFmtDescriptor *const adesc = av_pix_fmt_desc_get(a->formats[i]);
|
||||
- for (j = 0; j < b->nb_formats; j++) {
|
||||
- const AVPixFmtDescriptor *bdesc = av_pix_fmt_desc_get(b->formats[j]);
|
||||
- alpha2 |= adesc->flags & bdesc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
||||
- chroma2|= adesc->nb_components > 1 && bdesc->nb_components > 1;
|
||||
- if (a->formats[i] == b->formats[j]) {
|
||||
- alpha1 |= adesc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
||||
- chroma1|= adesc->nb_components > 1;
|
||||
- }
|
||||
+ int fmt = (int)a->formats[i];
|
||||
+ const AVPixFmtDescriptor *bdesc;
|
||||
+ int *found = bsearch(&fmt, sorted_b_fmts, b->nb_formats,
|
||||
+ sizeof(*sorted_b_fmts), cmp_int);
|
||||
+ /* accumulate alpha2/chroma2 for all b formats — we still need
|
||||
+ * to scan b once to compute the union properties */
|
||||
+ for (int j = 0; j < b->nb_formats; j++) {
|
||||
+ bdesc = av_pix_fmt_desc_get(b->formats[j]);
|
||||
+ alpha2 |= adesc->flags & bdesc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
||||
+ chroma2 |= adesc->nb_components > 1 && bdesc->nb_components > 1;
|
||||
}
|
||||
+ if (found) {
|
||||
+ alpha1 |= adesc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
||||
+ chroma1 |= adesc->nb_components > 1;
|
||||
+ }
|
||||
+ }
|
||||
+ av_free(sorted_b_fmts);
|
||||
}
|
||||
|
||||
// If chroma or alpha can be lost through merging then do not merge
|
||||
Loading…
Add table
Add a link
Reference in a new issue