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>
This commit is contained in:
luoye-cpu authored and Marcos Ashton committed 2026-11-04 19:36:53 +00:00
1 parent 2ec7bc915e
commit 182bb23bf8
3 files changed
+137

No files matched your search

+35
View File
@@ -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:
+92
View File
@@ -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");
+10
View File
@@ -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