qtdemux: move stco, stts, stss and stps atom parsing over to GstByteReader

Make sure we don't read beyond the atom boundary. Note that the code
behaves slightly differently in the corner case where there is not
enough atom data for the specified number of samples (n_samples_time)
in the atom, but still enough data to fill the pre-allocated index of
n_samples entries: before we would just stop parsing the stts data
and continue, whereas now we will likely error out. This should not
be a problem in practice though. We could maintain the old behaviour
by doing reads with a size check inside the loop if needed.
This commit is contained in:
Tim-Philipp Müller 2009-08-21 14:21:08 +01:00
parent 4be46b1586
commit c8c9b0f35d

View file

@ -3497,24 +3497,20 @@ static gboolean
qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
GNode * stbl)
{
QtAtomParser co_reader;
QtAtomParser stsz;
QtAtomParser stsc;
GNode *stco;
GNode *co64;
GNode *stts;
GNode *stss;
GNode *stps;
QtAtomParser stts;
GNode *ctts;
const guint8 *stco_data, *co64_data, *stts_data;
guint32 sample_size;
guint32 n_samples;
guint32 n_samples_per_chunk;
int sample_index;
int n_sample_times;
QtDemuxSample *samples;
gint i, j, k;
int index;
guint64 timestamp, time;
guint co_size;
/* sample to chunk */
if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stsc, &stsc))
@ -3525,27 +3521,26 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
goto corrupt_file;
/* chunk offsets */
stco = qtdemux_tree_get_child_by_type (stbl, FOURCC_stco);
co64 = qtdemux_tree_get_child_by_type (stbl, FOURCC_co64);
if (stco) {
stco_data = (const guint8 *) stco->data;
co64_data = NULL;
} else {
stco_data = NULL;
if (co64 == NULL)
goto corrupt_file;
co64_data = (const guint8 *) co64->data;
}
/* sample time */
if (!(stts = qtdemux_tree_get_child_by_type (stbl, FOURCC_stts)))
if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stco, &co_reader))
co_size = sizeof (guint32);
else if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_co64, &co_reader))
co_size = sizeof (guint64);
else
goto corrupt_file;
/* sample time */
if (!qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stts, &stts))
goto corrupt_file;
stts_data = (const guint8 *) stts->data;
if (!qt_atom_parser_skip (&stsz, 1 + 3) ||
!qt_atom_parser_get_uint32 (&stsz, &sample_size))
goto corrupt_file;
if (sample_size == 0 || stream->sampled) {
/* skip version, flags, number of entries */
if (!gst_byte_reader_skip (&co_reader, 1 + 3 + 4))
goto corrupt_file;
if (!qt_atom_parser_get_uint32 (&stsz, &n_samples))
goto corrupt_file;
@ -3589,6 +3584,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
index = 0;
for (i = 0; i < n_samples_per_chunk; i++) {
QtAtomParser co_chunk;
guint32 first_chunk, last_chunk;
guint32 samples_per_chunk;
@ -3615,17 +3611,28 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
--last_chunk;
}
if (G_UNLIKELY (last_chunk < first_chunk))
goto corrupt_file;
if (last_chunk != G_MAXUINT32) {
if (!qt_atom_parser_peek_sub (&co_reader, first_chunk * co_size,
(last_chunk - first_chunk) * co_size, &co_chunk))
goto corrupt_file;
} else {
co_chunk = co_reader;
if (!qt_atom_parser_skip (&co_chunk, first_chunk * co_size))
goto corrupt_file;
}
for (j = first_chunk; j < last_chunk; j++) {
guint64 chunk_offset;
if (stco) {
chunk_offset = QT_UINT32 (stco_data + 16 + j * 4);
} else {
chunk_offset = QT_UINT64 (co64_data + 16 + j * 8);
}
if (!qt_atom_parser_get_offset (&co_chunk, co_size, &chunk_offset))
goto corrupt_file;
for (k = 0; k < samples_per_chunk; k++) {
GST_LOG_OBJECT (qtdemux, "Creating entry %d with offset %lld",
index, chunk_offset);
GST_LOG_OBJECT (qtdemux, "Creating entry %d with offset %"
G_GUINT64_FORMAT, index, chunk_offset);
samples[index].offset = chunk_offset;
chunk_offset += samples[index].size;
index++;
@ -3635,112 +3642,132 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
}
}
done2:
{
guint32 n_sample_times;
n_sample_times = QT_UINT32 (stts_data + 12);
GST_LOG_OBJECT (qtdemux, "%u timestamp blocks", n_sample_times);
timestamp = 0;
stream->min_duration = 0;
time = 0;
index = 0;
stts_data += 16;
for (i = 0; i < n_sample_times; i++) {
guint32 n;
guint32 duration;
if (!qt_atom_parser_skip (&stts, 4))
goto corrupt_file;
if (!qt_atom_parser_get_uint32 (&stts, &n_sample_times))
goto corrupt_file;
GST_LOG_OBJECT (qtdemux, "%u timestamp blocks", n_sample_times);
n = QT_UINT32 (stts_data);
stts_data += 4;
duration = QT_UINT32 (stts_data);
stts_data += 4;
GST_LOG_OBJECT (qtdemux, "block %d, %u timestamps, duration %u ", i, n,
duration);
/* make sure there's enough data */
if (qt_atom_parser_get_remaining (&stts) < (n_sample_times * (2 * 4)))
goto corrupt_file;
/* take first duration for fps */
if (G_UNLIKELY (stream->min_duration == 0))
stream->min_duration = duration;
timestamp = 0;
stream->min_duration = 0;
time = 0;
index = 0;
for (i = 0; i < n_sample_times; i++) {
guint32 n;
guint32 duration;
for (j = 0; j < n; j++) {
GST_DEBUG_OBJECT (qtdemux,
"sample %d: index %d, timestamp %" GST_TIME_FORMAT, index, j,
GST_TIME_ARGS (timestamp));
n = qt_atom_parser_get_uint32_unchecked (&stts);
duration = qt_atom_parser_get_uint32_unchecked (&stts);
GST_LOG_OBJECT (qtdemux, "block %d, %u timestamps, duration %u ", i, n,
duration);
samples[index].timestamp = timestamp;
/* add non-scaled values to avoid rounding errors */
time += duration;
timestamp = gst_util_uint64_scale (time, GST_SECOND, stream->timescale);
samples[index].duration = timestamp - samples[index].timestamp;
/* take first duration for fps */
if (G_UNLIKELY (stream->min_duration == 0))
stream->min_duration = duration;
index++;
if (G_UNLIKELY (index >= n_samples))
goto done3;
}
}
/* fill up empty timestamps with the last timestamp, this can happen when
* the last samples do not decode and so we don't have timestamps for them.
* We however look at the last timestamp to estimate the track length so we
* need something in here. */
for (; index < n_samples; index++) {
GST_DEBUG_OBJECT (qtdemux, "fill sample %d: timestamp %" GST_TIME_FORMAT,
index, GST_TIME_ARGS (timestamp));
samples[index].timestamp = timestamp;
samples[index].duration = -1;
}
done3:
for (j = 0; j < n; j++) {
GST_DEBUG_OBJECT (qtdemux,
"sample %d: index %d, timestamp %" GST_TIME_FORMAT, index, j,
GST_TIME_ARGS (timestamp));
/* sample sync, can be NULL */
stss = qtdemux_tree_get_child_by_type (stbl, FOURCC_stss);
samples[index].timestamp = timestamp;
/* add non-scaled values to avoid rounding errors */
time += duration;
timestamp =
gst_util_uint64_scale (time, GST_SECOND, stream->timescale);
samples[index].duration = timestamp - samples[index].timestamp;
if (stss) {
/* mark keyframes */
guint32 n_sample_syncs;
const guint8 *stss_p = (guint8 *) stss->data;
stss_p += 12;
n_sample_syncs = QT_UINT32 (stss_p);
if (n_sample_syncs == 0) {
stream->all_keyframe = TRUE;
} else {
for (i = 0; i < n_sample_syncs; i++) {
stss_p += 4;
/* note that the first sample is index 1, not 0 */
index = QT_UINT32 (stss_p);
if (G_LIKELY (index > 0 && index <= n_samples))
samples[index - 1].keyframe = TRUE;
index++;
if (G_UNLIKELY (index >= n_samples))
goto done3;
}
}
stps = qtdemux_tree_get_child_by_type (stbl, FOURCC_stps);
if (stps) {
/* stps marks partial sync frames like open GOP I-Frames */
guint32 n_sample_syncs;
const guint8 *stps_p = (guint8 *) stps->data;
/* fill up empty timestamps with the last timestamp, this can happen when
* the last samples do not decode and so we don't have timestamps for them.
* We however look at the last timestamp to estimate the track length so we
* need something in here. */
for (; index < n_samples; index++) {
GST_DEBUG_OBJECT (qtdemux, "fill sample %d: timestamp %" GST_TIME_FORMAT,
index, GST_TIME_ARGS (timestamp));
samples[index].timestamp = timestamp;
samples[index].duration = -1;
}
}
done3:
{
/* FIXME: split this block out into a separate function */
QtAtomParser stss, stps;
stps_p += 12;
n_sample_syncs = QT_UINT32 (stps_p);
if (n_sample_syncs != 0) {
/* no entries, the stss table contains the real sync
* samples */
/* sample sync, can be NULL */
if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stss, &stss)) {
guint32 n_sample_syncs;
/* mark keyframes */
if (!qt_atom_parser_skip (&stss, 4))
goto corrupt_file;
if (!qt_atom_parser_get_uint32 (&stss, &n_sample_syncs))
goto corrupt_file;
if (n_sample_syncs == 0) {
stream->all_keyframe = TRUE;
} else {
/* make sure there's enough data */
if (qt_atom_parser_get_remaining (&stss) < (n_sample_syncs * 4))
goto corrupt_file;
for (i = 0; i < n_sample_syncs; i++) {
stps_p += 4;
/* note that the first sample is index 1, not 0 */
index = QT_UINT32 (stps_p);
index = qt_atom_parser_get_uint32_unchecked (&stss);
if (G_LIKELY (index > 0 && index <= n_samples))
samples[index - 1].keyframe = TRUE;
}
}
/* stps marks partial sync frames like open GOP I-Frames */
if (qtdemux_tree_get_child_by_type_full (stbl, FOURCC_stps, &stps)) {
guint32 n_sample_syncs;
if (!qt_atom_parser_skip (&stps, 4))
goto corrupt_file;
if (!qt_atom_parser_get_uint32 (&stps, &n_sample_syncs))
goto corrupt_file;
if (n_sample_syncs != 0) {
/* no entries, the stss table contains the real sync
* samples */
} else {
/* make sure there's enough data */
if (qt_atom_parser_get_remaining (&stps) < (n_sample_syncs * 4))
goto corrupt_file;
for (i = 0; i < n_sample_syncs; i++) {
/* note that the first sample is index 1, not 0 */
index = qt_atom_parser_get_uint32_unchecked (&stps);
if (G_LIKELY (index > 0 && index <= n_samples))
samples[index - 1].keyframe = TRUE;
}
}
}
} else {
/* no stss, all samples are keyframes */
stream->all_keyframe = TRUE;
}
} else {
/* no stss, all samples are keyframes */
stream->all_keyframe = TRUE;
}
} else {
GST_DEBUG_OBJECT (qtdemux,
"stsz sample_size %d != 0, treating chunks as samples", sample_size);
/* skip version + flags */
if (!gst_byte_reader_skip (&co_reader, 1 + 3))
goto corrupt_file;
/* treat chunks as samples */
if (stco) {
n_samples = QT_UINT32 (stco_data + 12);
} else {
n_samples = QT_UINT32 (co64_data + 12);
}
if (!gst_byte_reader_get_uint32_be (&co_reader, &n_samples))
goto corrupt_file;
if (n_samples == 0)
goto no_samples;
@ -3766,6 +3793,7 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
goto corrupt_file;
for (i = 0; i < n_samples_per_chunk; i++) {
QtAtomParser co_chunk;
guint32 first_chunk, last_chunk;
guint32 samples_per_chunk;
@ -3796,22 +3824,28 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream,
"entry %d has first_chunk %d, last_chunk %d, samples_per_chunk %d", i,
first_chunk, last_chunk, samples_per_chunk);
for (j = first_chunk; j < last_chunk; j++) {
guint64 chunk_offset;
if (G_UNLIKELY (last_chunk < first_chunk))
goto corrupt_file;
if (last_chunk != G_MAXUINT32) {
if (!qt_atom_parser_peek_sub (&co_reader, first_chunk * co_size,
(last_chunk - first_chunk) * co_size, &co_chunk))
goto corrupt_file;
} else {
co_chunk = co_reader;
if (!qt_atom_parser_skip (&co_chunk, first_chunk * co_size))
goto corrupt_file;
}
for (j = first_chunk; j < last_chunk; j++) {
if (j >= n_samples)
goto done;
if (stco) {
chunk_offset = QT_UINT32 (stco_data + 16 + j * 4);
} else {
chunk_offset = QT_UINT64 (co64_data + 16 + j * 8);
}
GST_LOG_OBJECT (qtdemux,
"Creating entry %d with offset %" G_GUINT64_FORMAT, j,
chunk_offset);
samples[j].offset =
qt_atom_parser_get_offset_unchecked (&co_chunk, co_size);
samples[j].offset = chunk_offset;
GST_LOG_OBJECT (qtdemux, "Created entry %d with offset "
"%" G_GUINT64_FORMAT, j, samples[j].offset);
if (stream->samples_per_frame * stream->bytes_per_frame) {
samples[j].size = (samples_per_chunk * stream->n_channels) /