comparison src/http/modules/ngx_http_mp4_module.c @ 7629:f47f7d3d1bfa

Mp4: fixed possible chunk offset overflow. In "co64" atom chunk start offset is a 64-bit unsigned integer. When trimming the "mdat" atom, chunk offsets are casted to off_t values which are typically 64-bit signed integers. A specially crafted mp4 file with huge chunk offsets may lead to off_t overflow and result in negative trim boundaries. The consequences of the overflow are: - Incorrect Content-Length header value in the response. - Negative left boundary of the response file buffer holding the trimmed "mdat". This leads to pread()/sendfile() errors followed by closing the client connection. On rare systems where off_t is a 32-bit integer, this scenario is also feasible with the "stco" atom. The fix is to add checks which make sure data chunks referenced by each track are within the mp4 file boundaries. Additionally a few more checks are added to ensure mp4 file consistency and log errors.
author Roman Arutyunyan <arut@nginx.com>
date Wed, 26 Feb 2020 15:10:46 +0300
parents be5cb9c67c05
children 7a55311b0dc3
comparison
equal deleted inserted replaced
7628:2e3bfd696ecb 7629:f47f7d3d1bfa
3114 3114
3115 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, 3115 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3116 "chunk samples sizes:%uL", 3116 "chunk samples sizes:%uL",
3117 trak->start_chunk_samples_size); 3117 trak->start_chunk_samples_size);
3118 3118
3119 if (trak->start_chunk_samples_size > (uint64_t) mp4->end) {
3120 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3121 "too large mp4 start samples size in \"%s\"",
3122 mp4->file.name.data);
3123 return NGX_ERROR;
3124 }
3125
3119 if (mp4->length) { 3126 if (mp4->length) {
3120 if (trak->end_sample - trak->start_sample > entries) { 3127 if (trak->end_sample - trak->start_sample > entries) {
3121 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, 3128 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3122 "end time is out mp4 stsz samples in \"%s\"", 3129 "end time is out mp4 stsz samples in \"%s\"",
3123 mp4->file.name.data); 3130 mp4->file.name.data);
3133 } 3140 }
3134 3141
3135 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, 3142 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3136 "mp4 stsz end_chunk_samples_size:%uL", 3143 "mp4 stsz end_chunk_samples_size:%uL",
3137 trak->end_chunk_samples_size); 3144 trak->end_chunk_samples_size);
3145
3146 if (trak->end_chunk_samples_size > (uint64_t) mp4->end) {
3147 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3148 "too large mp4 end samples size in \"%s\"",
3149 mp4->file.name.data);
3150 return NGX_ERROR;
3151 }
3138 } 3152 }
3139 3153
3140 atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos); 3154 atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos);
3141 trak->size += atom_size; 3155 trak->size += atom_size;
3142 3156
3224 ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4, 3238 ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
3225 ngx_http_mp4_trak_t *trak) 3239 ngx_http_mp4_trak_t *trak)
3226 { 3240 {
3227 size_t atom_size; 3241 size_t atom_size;
3228 uint32_t entries; 3242 uint32_t entries;
3243 uint64_t chunk_offset, samples_size;
3229 ngx_buf_t *atom, *data; 3244 ngx_buf_t *atom, *data;
3230 ngx_mp4_stco_atom_t *stco_atom; 3245 ngx_mp4_stco_atom_t *stco_atom;
3231 3246
3232 /* 3247 /*
3233 * mdia.minf.stbl.stco updating requires trak->start_chunk 3248 * mdia.minf.stbl.stco updating requires trak->start_chunk
3254 return NGX_ERROR; 3269 return NGX_ERROR;
3255 } 3270 }
3256 3271
3257 data->pos += trak->start_chunk * sizeof(uint32_t); 3272 data->pos += trak->start_chunk * sizeof(uint32_t);
3258 3273
3259 trak->start_offset = ngx_mp4_get_32value(data->pos); 3274 chunk_offset = ngx_mp4_get_32value(data->pos);
3260 trak->start_offset += trak->start_chunk_samples_size; 3275 samples_size = trak->start_chunk_samples_size;
3276
3277 if (chunk_offset > (uint64_t) mp4->end - samples_size
3278 || chunk_offset + samples_size > NGX_MAX_UINT32_VALUE)
3279 {
3280 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3281 "too large chunk offset in \"%s\"",
3282 mp4->file.name.data);
3283 return NGX_ERROR;
3284 }
3285
3286 trak->start_offset = chunk_offset + samples_size;
3261 ngx_mp4_set_32value(data->pos, trak->start_offset); 3287 ngx_mp4_set_32value(data->pos, trak->start_offset);
3262 3288
3263 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, 3289 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3264 "start chunk offset:%O", trak->start_offset); 3290 "start chunk offset:%O", trak->start_offset);
3265 3291
3274 3300
3275 entries = trak->end_chunk - trak->start_chunk; 3301 entries = trak->end_chunk - trak->start_chunk;
3276 data->last = data->pos + entries * sizeof(uint32_t); 3302 data->last = data->pos + entries * sizeof(uint32_t);
3277 3303
3278 if (entries) { 3304 if (entries) {
3279 trak->end_offset = 3305 chunk_offset = ngx_mp4_get_32value(data->last - sizeof(uint32_t));
3280 ngx_mp4_get_32value(data->last - sizeof(uint32_t)); 3306 samples_size = trak->end_chunk_samples_size;
3281 trak->end_offset += trak->end_chunk_samples_size; 3307
3308 if (chunk_offset > (uint64_t) mp4->end - samples_size
3309 || chunk_offset + samples_size > NGX_MAX_UINT32_VALUE)
3310 {
3311 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3312 "too large chunk offset in \"%s\"",
3313 mp4->file.name.data);
3314 return NGX_ERROR;
3315 }
3316
3317 trak->end_offset = chunk_offset + samples_size;
3282 3318
3283 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, 3319 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3284 "end chunk offset:%O", trak->end_offset); 3320 "end chunk offset:%O", trak->end_offset);
3285 } 3321 }
3286 3322
3407 static ngx_int_t 3443 static ngx_int_t
3408 ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4, 3444 ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
3409 ngx_http_mp4_trak_t *trak) 3445 ngx_http_mp4_trak_t *trak)
3410 { 3446 {
3411 size_t atom_size; 3447 size_t atom_size;
3412 uint64_t entries; 3448 uint64_t entries, chunk_offset, samples_size;
3413 ngx_buf_t *atom, *data; 3449 ngx_buf_t *atom, *data;
3414 ngx_mp4_co64_atom_t *co64_atom; 3450 ngx_mp4_co64_atom_t *co64_atom;
3415 3451
3416 /* 3452 /*
3417 * mdia.minf.stbl.co64 updating requires trak->start_chunk 3453 * mdia.minf.stbl.co64 updating requires trak->start_chunk
3438 return NGX_ERROR; 3474 return NGX_ERROR;
3439 } 3475 }
3440 3476
3441 data->pos += trak->start_chunk * sizeof(uint64_t); 3477 data->pos += trak->start_chunk * sizeof(uint64_t);
3442 3478
3443 trak->start_offset = ngx_mp4_get_64value(data->pos); 3479 chunk_offset = ngx_mp4_get_64value(data->pos);
3444 trak->start_offset += trak->start_chunk_samples_size; 3480 samples_size = trak->start_chunk_samples_size;
3481
3482 if (chunk_offset > (uint64_t) mp4->end - samples_size) {
3483 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3484 "too large chunk offset in \"%s\"",
3485 mp4->file.name.data);
3486 return NGX_ERROR;
3487 }
3488
3489 trak->start_offset = chunk_offset + samples_size;
3445 ngx_mp4_set_64value(data->pos, trak->start_offset); 3490 ngx_mp4_set_64value(data->pos, trak->start_offset);
3446 3491
3447 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, 3492 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3448 "start chunk offset:%O", trak->start_offset); 3493 "start chunk offset:%O", trak->start_offset);
3449 3494
3458 3503
3459 entries = trak->end_chunk - trak->start_chunk; 3504 entries = trak->end_chunk - trak->start_chunk;
3460 data->last = data->pos + entries * sizeof(uint64_t); 3505 data->last = data->pos + entries * sizeof(uint64_t);
3461 3506
3462 if (entries) { 3507 if (entries) {
3463 trak->end_offset = 3508 chunk_offset = ngx_mp4_get_64value(data->last - sizeof(uint64_t));
3464 ngx_mp4_get_64value(data->last - sizeof(uint64_t)); 3509 samples_size = trak->end_chunk_samples_size;
3465 trak->end_offset += trak->end_chunk_samples_size; 3510
3511 if (chunk_offset > (uint64_t) mp4->end - samples_size) {
3512 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3513 "too large chunk offset in \"%s\"",
3514 mp4->file.name.data);
3515 return NGX_ERROR;
3516 }
3517
3518 trak->end_offset = chunk_offset + samples_size;
3466 3519
3467 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, 3520 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3468 "end chunk offset:%O", trak->end_offset); 3521 "end chunk offset:%O", trak->end_offset);
3469 } 3522 }
3470 3523