Commit 182bb23bf8 for ffmpeg

commit 182bb23bf84869e8487a5b5c6253fc9e9c26adfc
Author: luoye-cpu <3412829159@qq.com>
Date:   Fri Oct 30 06:42:22 2026 +0800

    avutil/hdr_dynamic_metadata: reject oversized HDR10+ counts

    av_dynamic_hdr_plus_to_t35() uses caller-supplied counts as loop
    bounds over fixed-size arrays. A count slightly above an array
    length can make the write loop read out of bounds while the
    payload remains well within AV_HDR_PLUS_MAX_PAYLOAD_SIZE.

    Reject counts above the array bounds with AVERROR(EINVAL), since
    these are caller-supplied fields rather than data parsed off the
    wire. The checks are gated like the reads they guard: the
    targeted-system-display and mastering-display peak-luminance
    counts and the bezier anchors are only read when their respective
    flags are set. Rejecting oversized counts also keeps the payload
    within AV_HDR_PLUS_MAX_PAYLOAD_SIZE, but the size assertion is not
    a substitute for validating the array bounds.

    FATE cases cover every rejected count and, for gated counts,
    out-of-range values with their flags clear, which must still
    serialize.

    Signed-off-by: luoye-cpu <3412829159@qq.com>

diff --git a/libavutil/hdr_dynamic_metadata.c b/libavutil/hdr_dynamic_metadata.c
index 9b46499343..d52470da6d 100644
--- a/libavutil/hdr_dynamic_metadata.c
+++ b/libavutil/hdr_dynamic_metadata.c
@@ -244,6 +244,41 @@ int av_dynamic_hdr_plus_to_t35(const AVDynamicHDRPlus *s, uint8_t **data, size_t
         return AVERROR(EINVAL);
     if ((!data || *data) && !size)
        return AVERROR(EINVAL);
+    /* The caps put the payload at exactly 907 bytes; the assert cannot fire. */
+    if (s->num_windows > FF_ARRAY_ELEMS(s->params))
+        return AVERROR(EINVAL);
+
+    for (int w = 0; w < s->num_windows; w++) {
+        const AVHDRPlusColorTransformParams *p = &s->params[w];
+
+        if (p->num_distribution_maxrgb_percentiles >
+            FF_ARRAY_ELEMS(p->distribution_maxrgb))
+            return AVERROR(EINVAL);
+
+        /* The anchors are only read while tone mapping is enabled. */
+        if (p->tone_mapping_flag &&
+            p->num_bezier_curve_anchors >
+            FF_ARRAY_ELEMS(p->bezier_curve_anchors))
+            return AVERROR(EINVAL);
+    }
+
+    /* The peak-luminance pairs use a row count and a column count, and are
+     * only read when their flag is set, so check them the same way. */
+    if (s->targeted_system_display_actual_peak_luminance_flag) {
+        if (s->num_rows_targeted_system_display_actual_peak_luminance >
+            FF_ARRAY_ELEMS(s->targeted_system_display_actual_peak_luminance) ||
+            s->num_cols_targeted_system_display_actual_peak_luminance >
+            FF_ARRAY_ELEMS(s->targeted_system_display_actual_peak_luminance[0]))
+            return AVERROR(EINVAL);
+    }
+
+    if (s->mastering_display_actual_peak_luminance_flag) {
+        if (s->num_rows_mastering_display_actual_peak_luminance >
+            FF_ARRAY_ELEMS(s->mastering_display_actual_peak_luminance) ||
+            s->num_cols_mastering_display_actual_peak_luminance >
+            FF_ARRAY_ELEMS(s->mastering_display_actual_peak_luminance[0]))
+            return AVERROR(EINVAL);
+    }

     /**
      * Buffer size per CTA-861-H p.253-254:
diff --git a/libavutil/tests/hdr_dynamic_metadata.c b/libavutil/tests/hdr_dynamic_metadata.c
index e85604e4f0..dc0501333d 100644
--- a/libavutil/tests/hdr_dynamic_metadata.c
+++ b/libavutil/tests/hdr_dynamic_metadata.c
@@ -486,6 +486,98 @@ static void test_hdr_plus(void)
     printf("to_t35 no data no size: ret==AVERROR(EINVAL)=%s\n",
            ret == AVERROR(EINVAL) ? "yes" : "no");

+    /* A num_windows above the params[] length must be refused. */
+    hdr->num_windows = FF_ARRAY_ELEMS(hdr->params) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 num_windows>3: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->num_windows = 2;
+
+    uint8_t pct  = hdr->params[0].num_distribution_maxrgb_percentiles;
+    uint8_t anch = hdr->params[0].num_bezier_curve_anchors;
+    uint8_t trow = hdr->num_rows_targeted_system_display_actual_peak_luminance;
+    uint8_t tcol = hdr->num_cols_targeted_system_display_actual_peak_luminance;
+    uint8_t mrow = hdr->num_rows_mastering_display_actual_peak_luminance;
+    uint8_t mcol = hdr->num_cols_mastering_display_actual_peak_luminance;
+
+    hdr->params[0].num_distribution_maxrgb_percentiles =
+        FF_ARRAY_ELEMS(hdr->params[0].distribution_maxrgb) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 percentiles>15: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->params[0].num_distribution_maxrgb_percentiles = pct;
+
+    hdr->params[0].num_bezier_curve_anchors =
+        FF_ARRAY_ELEMS(hdr->params[0].bezier_curve_anchors) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 anchors>15: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->params[0].num_bezier_curve_anchors = anch;
+
+    /* The peak-luminance pairs are only checked when their flags are set. */
+    hdr->num_rows_targeted_system_display_actual_peak_luminance =
+        FF_ARRAY_ELEMS(hdr->targeted_system_display_actual_peak_luminance) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 targeted rows>25: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->num_rows_targeted_system_display_actual_peak_luminance = trow;
+
+    hdr->num_cols_targeted_system_display_actual_peak_luminance =
+        FF_ARRAY_ELEMS(hdr->targeted_system_display_actual_peak_luminance[0]) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 targeted cols>25: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->num_cols_targeted_system_display_actual_peak_luminance = tcol;
+
+    hdr->num_rows_mastering_display_actual_peak_luminance =
+        FF_ARRAY_ELEMS(hdr->mastering_display_actual_peak_luminance) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 mastering rows>25: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->num_rows_mastering_display_actual_peak_luminance = mrow;
+
+    hdr->num_cols_mastering_display_actual_peak_luminance =
+        FF_ARRAY_ELEMS(hdr->mastering_display_actual_peak_luminance[0]) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 mastering cols>25: ret==AVERROR(EINVAL)=%s\n",
+           ret == AVERROR(EINVAL) ? "yes" : "no");
+    hdr->num_cols_mastering_display_actual_peak_luminance = mcol;
+
+    /* With the flags clear, oversized counts must still serialize. */
+    hdr->targeted_system_display_actual_peak_luminance_flag = 0;
+    hdr->mastering_display_actual_peak_luminance_flag       = 0;
+    hdr->num_rows_targeted_system_display_actual_peak_luminance =
+        FF_ARRAY_ELEMS(hdr->targeted_system_display_actual_peak_luminance) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 rows>25 with flag clear: ret==0=%s\n",
+           ret == 0 ? "yes" : "no");
+    hdr->num_rows_targeted_system_display_actual_peak_luminance = trow;
+
+    hdr->num_rows_mastering_display_actual_peak_luminance =
+        FF_ARRAY_ELEMS(hdr->mastering_display_actual_peak_luminance) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 mastering rows>25 with flag clear: ret==0=%s\n",
+           ret == 0 ? "yes" : "no");
+    hdr->num_rows_mastering_display_actual_peak_luminance = mrow;
+
+    hdr->params[0].tone_mapping_flag = 0;
+    hdr->params[0].num_bezier_curve_anchors =
+        FF_ARRAY_ELEMS(hdr->params[0].bezier_curve_anchors) + 1;
+    size = 0;
+    ret = av_dynamic_hdr_plus_to_t35(hdr, NULL, &size);
+    printf("to_t35 anchors>15 with flag clear: ret==0=%s\n",
+           ret == 0 ? "yes" : "no");
+    hdr->params[0].num_bezier_curve_anchors = anch;
+
     ret = av_dynamic_hdr_plus_from_t35(NULL, (const uint8_t *)"", 0);
     printf("from_t35 NULL s: ret==AVERROR(EINVAL)=%s\n",
            ret == AVERROR(EINVAL) ? "yes" : "no");
diff --git a/tests/ref/fate/hdr_dynamic_metadata b/tests/ref/fate/hdr_dynamic_metadata
index 118f69d7e5..3ec298fc17 100644
--- a/tests/ref/fate/hdr_dynamic_metadata
+++ b/tests/ref/fate/hdr_dynamic_metadata
@@ -60,6 +60,16 @@ decoded:
 Testing error paths
 to_t35 NULL s: ret==AVERROR(EINVAL)=yes
 to_t35 no data no size: ret==AVERROR(EINVAL)=yes
+to_t35 num_windows>3: ret==AVERROR(EINVAL)=yes
+to_t35 percentiles>15: ret==AVERROR(EINVAL)=yes
+to_t35 anchors>15: ret==AVERROR(EINVAL)=yes
+to_t35 targeted rows>25: ret==AVERROR(EINVAL)=yes
+to_t35 targeted cols>25: ret==AVERROR(EINVAL)=yes
+to_t35 mastering rows>25: ret==AVERROR(EINVAL)=yes
+to_t35 mastering cols>25: ret==AVERROR(EINVAL)=yes
+to_t35 rows>25 with flag clear: ret==0=yes
+to_t35 mastering rows>25 with flag clear: ret==0=yes
+to_t35 anchors>15 with flag clear: ret==0=yes
 from_t35 NULL s: ret==AVERROR(EINVAL)=yes
 from_t35 oversized: ret==AVERROR(EINVAL)=yes