diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 86ea84f75..24d06dd1e 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -46,18 +46,58 @@ extern "C" { #endif #endif +SPA_API_AUDIO_FORMAT_UTILS bool +spa_format_audio_ext_valid_size(uint32_t media_subtype, size_t size) +{ + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + return size >= offsetof(struct spa_audio_info, info.raw) && + SPA_AUDIO_INFO_RAW_VALID_SIZE(size - offsetof(struct spa_audio_info, info.raw)); + +#define _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(format) \ + case SPA_MEDIA_SUBTYPE_ ## format: \ + return size >= offsetof(struct spa_audio_info, info.format) + sizeof(struct spa_audio_info_ ## format); + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsp) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(iec958) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsd) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mp3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(aac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(vorbis) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(wma) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ra) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(amr) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(alac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(flac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ape) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ac3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(eac3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(truehd) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dts) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mpegh) +#undef _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE + } + return false; +} + SPA_API_AUDIO_FORMAT_UTILS int spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) { int res; + uint32_t media_type, media_subtype; - if ((res = spa_format_parse(format, &info->media_type, &info->media_subtype)) < 0) + if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) return res; - if (info->media_type != SPA_MEDIA_TYPE_audio) + if (media_type != SPA_MEDIA_TYPE_audio) return -EINVAL; - switch (info->media_subtype) { + if (!spa_format_audio_ext_valid_size(media_subtype, size)) + return -EINVAL; + + info->media_type = media_type; + info->media_subtype = media_subtype; + + switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_parse(format, &info->info.raw, size - offsetof(struct spa_audio_info, info.raw)); @@ -109,6 +149,11 @@ SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info *info, size_t size) { + if (!spa_format_audio_ext_valid_size(info->media_subtype, size)) { + errno = EINVAL; + return NULL; + } + switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, diff --git a/spa/include/spa/param/audio/format.h b/spa/include/spa/param/audio/format.h index ac9b10dda..6e7a71f6c 100644 --- a/spa/include/spa/param/audio/format.h +++ b/spa/include/spa/param/audio/format.h @@ -59,6 +59,8 @@ struct spa_audio_info { struct spa_audio_info_dts dts; struct spa_audio_info_mpegh mpegh; } info; + + /* padding follows here when info has flexible size */ }; /** diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index e83c4495b..38fcb449c 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -60,6 +60,10 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, { uint32_t v; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { if (force || info->format == 0) info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); @@ -100,6 +104,9 @@ spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t { int res; + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + memset(info, 0, size); SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); if (dict) { diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index ce5e61e67..3bb94eaa3 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -35,6 +35,9 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in int res; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, @@ -64,6 +67,11 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, struct spa_pod_frame f; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) { + errno = EINVAL; + return NULL; + } + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 392b65daa..bcc0a122d 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -293,6 +293,8 @@ struct spa_audio_info_raw { #define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) +#define SPA_AUDIO_INFO_RAW_VALID_SIZE(size) ((size) >= offsetof(struct spa_audio_info_raw, position)) + #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ diff --git a/test/meson.build b/test/meson.build index ad658bcc6..5e38db383 100644 --- a/test/meson.build +++ b/test/meson.build @@ -112,6 +112,7 @@ test('test-spa', executable('test-spa', 'test-spa-buffer.c', 'test-spa-control.c', + 'test-spa-format.c', 'test-spa-json.c', 'test-spa-utils.c', 'test-spa-log.c', diff --git a/test/test-spa-format.c b/test/test-spa-format.c new file mode 100644 index 000000000..7cbec7690 --- /dev/null +++ b/test/test-spa-format.c @@ -0,0 +1,129 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include + +#include "pwtest.h" + +PWTEST(audio_format_sizes) +{ + union { + uint8_t buf[1024]; + struct spa_audio_info align; + } data; + struct spa_audio_info info; + size_t i; + + memset(&info, 0xf3, sizeof(info)); + info.media_type = SPA_MEDIA_TYPE_audio; + info.media_subtype = SPA_MEDIA_SUBTYPE_raw; + info.info.raw.channels = 5; + info.info.raw.format = SPA_AUDIO_FORMAT_F32P; + info.info.raw.rate = 12345; + info.info.raw.flags = 0; + info.info.raw.position[0] = 1; + info.info.raw.position[1] = 2; + info.info.raw.position[2] = 3; + info.info.raw.position[3] = 4; + info.info.raw.position[4] = 5; + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + memcpy(data.buf, &info, sizeof(info)); + + pod = spa_format_audio_ext_build(&b, 123, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + offsetof(struct spa_audio_info_raw, position)) + pwtest_bool_true(!pod); + else + pwtest_bool_true(pod); + } + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + int ret; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + pod = spa_format_audio_ext_build(&b, 123, &info, sizeof(info)); + pwtest_bool_true(pod); + + memset(data.buf, 0xf3, sizeof(data.buf)); + + ret = spa_format_audio_ext_parse(pod, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + offsetof(struct spa_audio_info_raw, position) + + info.info.raw.channels*sizeof(uint32_t)) { + for (size_t j = i; j < sizeof(data.buf); ++j) + pwtest_int_eq(data.buf[j], 0xf3); + pwtest_int_lt(ret, 0); + } else { + pwtest_int_ge(ret, 0); + pwtest_bool_true(memcmp(data.buf, &info, SPA_MIN(i, sizeof(info))) == 0); + } + } + + memset(&info, 0xf3, sizeof(info)); + info.media_type = SPA_MEDIA_TYPE_audio; + info.media_subtype = SPA_MEDIA_SUBTYPE_aac; + info.info.aac.rate = 12345; + info.info.aac.channels = 6; + info.info.aac.bitrate = 54321; + info.info.aac.stream_format = SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM; + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + memcpy(data.buf, &info, sizeof(info)); + + pod = spa_format_audio_ext_build(&b, 123, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + sizeof(struct spa_audio_info_aac)) + pwtest_bool_true(!pod); + else + pwtest_bool_true(pod); + } + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + int ret; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + pod = spa_format_audio_ext_build(&b, 123, &info, sizeof(info)); + pwtest_bool_true(pod); + + memset(data.buf, 0xf3, sizeof(data.buf)); + + ret = spa_format_audio_ext_parse(pod, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + sizeof(struct spa_audio_info_aac)) { + for (size_t j = i; j < sizeof(data.buf); ++j) + pwtest_int_eq(data.buf[j], 0xf3); + pwtest_int_lt(ret, 0); + } else { + pwtest_int_ge(ret, 0); + pwtest_bool_true(memcmp(data.buf, &info, SPA_MIN(i, sizeof(info))) == 0); + } + } + + return PWTEST_PASS; +} + +PWTEST_SUITE(spa_format) +{ + pwtest_add(audio_format_sizes, PWTEST_NOARG); + + return PWTEST_PASS; +}