FFmpeg/libavformat/hls.c

1778 lines
60 KiB
C
Raw Normal View History

/*
* Apple HTTP Live Streaming demuxer
* Copyright (c) 2010 Martin Storsjo
* Copyright (c) 2013 Anssi Hannula
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* Apple HTTP Live Streaming demuxer
* http://tools.ietf.org/html/draft-pantos-http-live-streaming
*/
#include "libavutil/avstring.h"
#include "libavutil/avassert.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/dict.h"
#include "libavutil/time.h"
#include "avformat.h"
#include "internal.h"
#include "avio_internal.h"
#include "url.h"
#include "id3v2.h"
#define INITIAL_BUFFER_SIZE 32768
#define MAX_FIELD_LEN 64
#define MAX_CHARACTERISTICS_LEN 512
#define MPEG_TIME_BASE 90000
#define MPEG_TIME_BASE_Q (AVRational){1, MPEG_TIME_BASE}
/*
* An apple http stream consists of a playlist with media segment files,
* played sequentially. There may be several playlists with the same
* video content, in different bandwidth variants, that are played in
* parallel (preferably only one bandwidth variant at a time). In this case,
* the user supplied the url to a main playlist that only lists the variant
* playlists.
*
* If the main playlist doesn't point at any variants, we still create
* one anonymous toplevel variant for this, to maintain the structure.
*/
enum KeyType {
KEY_NONE,
KEY_AES_128,
};
struct segment {
int64_t duration;
int64_t url_offset;
int64_t size;
char *url;
char *key;
enum KeyType key_type;
uint8_t iv[16];
};
struct rendition;
enum PlaylistType {
PLS_TYPE_UNSPECIFIED,
PLS_TYPE_EVENT,
PLS_TYPE_VOD
};
/*
* Each playlist has its own demuxer. If it currently is active,
* it has an open AVIOContext too, and potentially an AVPacket
* containing the next packet from this stream.
*/
struct playlist {
char url[MAX_URL_SIZE];
AVIOContext pb;
uint8_t* read_buffer;
URLContext *input;
AVFormatContext *parent;
int index;
AVFormatContext *ctx;
AVPacket pkt;
int stream_offset;
int finished;
enum PlaylistType type;
int64_t target_duration;
int start_seq_no;
int n_segments;
struct segment **segments;
int needed, cur_needed;
int cur_seq_no;
int64_t cur_seg_offset;
int64_t last_load_time;
char key_url[MAX_URL_SIZE];
uint8_t key[16];
/* ID3 timestamp handling (elementary audio streams have ID3 timestamps
* (and possibly other ID3 tags) in the beginning of each segment) */
int is_id3_timestamped; /* -1: not yet known */
int64_t id3_mpegts_timestamp; /* in mpegts tb */
int64_t id3_offset; /* in stream original tb */
uint8_t* id3_buf; /* temp buffer for id3 parsing */
unsigned int id3_buf_size;
AVDictionary *id3_initial; /* data from first id3 tag */
int id3_found; /* ID3 tag found at some point */
int id3_changed; /* ID3 tag data has changed at some point */
ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer is opened */
int64_t seek_timestamp;
int seek_flags;
int seek_stream_index; /* into subdemuxer stream array */
/* Renditions associated with this playlist, if any.
* Alternative rendition playlists have a single rendition associated
* with them, and variant main Media Playlists may have
* multiple (playlist-less) renditions associated with them. */
int n_renditions;
struct rendition **renditions;
};
/*
* Renditions are e.g. alternative subtitle or audio streams.
* The rendition may either be an external playlist or it may be
* contained in the main Media Playlist of the variant (in which case
* playlist is NULL).
*/
struct rendition {
enum AVMediaType type;
struct playlist *playlist;
char group_id[MAX_FIELD_LEN];
char language[MAX_FIELD_LEN];
char name[MAX_FIELD_LEN];
int disposition;
};
struct variant {
int bandwidth;
/* every variant contains at least the main Media Playlist in index 0 */
int n_playlists;
struct playlist **playlists;
char audio_group[MAX_FIELD_LEN];
char video_group[MAX_FIELD_LEN];
char subtitles_group[MAX_FIELD_LEN];
};
typedef struct HLSContext {
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
AVClass *class;
int n_variants;
struct variant **variants;
int n_playlists;
struct playlist **playlists;
int n_renditions;
struct rendition **renditions;
int cur_seq_no;
int first_packet;
int64_t first_timestamp;
int64_t cur_timestamp;
2011-11-06 20:34:24 +00:00
AVIOInterruptCB *interrupt_callback;
char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context
char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context
char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
char *allowed_extensions;
int max_reload;
} HLSContext;
static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
{
int len = ff_get_line(s, buf, maxlen);
while (len > 0 && av_isspace(buf[len - 1]))
buf[--len] = '\0';
return len;
}
static void free_segment_list(struct playlist *pls)
{
int i;
for (i = 0; i < pls->n_segments; i++) {
av_free(pls->segments[i]->key);
av_free(pls->segments[i]->url);
av_free(pls->segments[i]);
}
av_freep(&pls->segments);
pls->n_segments = 0;
}
static void free_playlist_list(HLSContext *c)
{
int i;
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
free_segment_list(pls);
av_freep(&pls->renditions);
av_freep(&pls->id3_buf);
av_dict_free(&pls->id3_initial);
ff_id3v2_free_extra_meta(&pls->id3_deferred_extra);
av_free_packet(&pls->pkt);
av_free(pls->pb.buffer);
if (pls->input)
ffurl_close(pls->input);
if (pls->ctx) {
pls->ctx->pb = NULL;
avformat_close_input(&pls->ctx);
}
av_free(pls);
}
av_freep(&c->playlists);
av_freep(&c->cookies);
av_freep(&c->user_agent);
c->n_playlists = 0;
}
static void free_variant_list(HLSContext *c)
{
int i;
for (i = 0; i < c->n_variants; i++) {
struct variant *var = c->variants[i];
av_freep(&var->playlists);
av_free(var);
}
av_freep(&c->variants);
c->n_variants = 0;
}
static void free_rendition_list(HLSContext *c)
{
int i;
for (i = 0; i < c->n_renditions; i++)
av_free(c->renditions[i]);
av_freep(&c->renditions);
c->n_renditions = 0;
}
/*
* Used to reset a statically allocated AVPacket to a clean slate,
* containing no data.
*/
static void reset_packet(AVPacket *pkt)
{
av_init_packet(pkt);
pkt->data = NULL;
}
static struct playlist *new_playlist(HLSContext *c, const char *url,
const char *base)
{
struct playlist *pls = av_mallocz(sizeof(struct playlist));
if (!pls)
return NULL;
reset_packet(&pls->pkt);
ff_make_absolute_url(pls->url, sizeof(pls->url), base, url);
pls->seek_timestamp = AV_NOPTS_VALUE;
pls->is_id3_timestamped = -1;
pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;
dynarray_add(&c->playlists, &c->n_playlists, pls);
return pls;
}
struct variant_info {
char bandwidth[20];
/* variant group ids: */
char audio[MAX_FIELD_LEN];
char video[MAX_FIELD_LEN];
char subtitles[MAX_FIELD_LEN];
};
static struct variant *new_variant(HLSContext *c, struct variant_info *info,
const char *url, const char *base)
{
struct variant *var;
struct playlist *pls;
pls = new_playlist(c, url, base);
if (!pls)
return NULL;
var = av_mallocz(sizeof(struct variant));
if (!var)
return NULL;
if (info) {
var->bandwidth = atoi(info->bandwidth);
strcpy(var->audio_group, info->audio);
strcpy(var->video_group, info->video);
strcpy(var->subtitles_group, info->subtitles);
}
dynarray_add(&c->variants, &c->n_variants, var);
dynarray_add(&var->playlists, &var->n_playlists, pls);
return var;
}
static void handle_variant_args(struct variant_info *info, const char *key,
int key_len, char **dest, int *dest_len)
{
if (!strncmp(key, "BANDWIDTH=", key_len)) {
*dest = info->bandwidth;
*dest_len = sizeof(info->bandwidth);
} else if (!strncmp(key, "AUDIO=", key_len)) {
*dest = info->audio;
*dest_len = sizeof(info->audio);
} else if (!strncmp(key, "VIDEO=", key_len)) {
*dest = info->video;
*dest_len = sizeof(info->video);
} else if (!strncmp(key, "SUBTITLES=", key_len)) {
*dest = info->subtitles;
*dest_len = sizeof(info->subtitles);
}
}
struct key_info {
char uri[MAX_URL_SIZE];
char method[10];
char iv[35];
};
static void handle_key_args(struct key_info *info, const char *key,
int key_len, char **dest, int *dest_len)
{
if (!strncmp(key, "METHOD=", key_len)) {
*dest = info->method;
*dest_len = sizeof(info->method);
} else if (!strncmp(key, "URI=", key_len)) {
*dest = info->uri;
*dest_len = sizeof(info->uri);
} else if (!strncmp(key, "IV=", key_len)) {
*dest = info->iv;
*dest_len = sizeof(info->iv);
}
}
struct rendition_info {
char type[16];
char uri[MAX_URL_SIZE];
char group_id[MAX_FIELD_LEN];
char language[MAX_FIELD_LEN];
char assoc_language[MAX_FIELD_LEN];
char name[MAX_FIELD_LEN];
char defaultr[4];
char forced[4];
char characteristics[MAX_CHARACTERISTICS_LEN];
};
static struct rendition *new_rendition(HLSContext *c, struct rendition_info *info,
const char *url_base)
{
struct rendition *rend;
enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN;
char *characteristic;
char *chr_ptr;
char *saveptr;
if (!strcmp(info->type, "AUDIO"))
type = AVMEDIA_TYPE_AUDIO;
else if (!strcmp(info->type, "VIDEO"))
type = AVMEDIA_TYPE_VIDEO;
else if (!strcmp(info->type, "SUBTITLES"))
type = AVMEDIA_TYPE_SUBTITLE;
else if (!strcmp(info->type, "CLOSED-CAPTIONS"))
/* CLOSED-CAPTIONS is ignored since we do not support CEA-608 CC in
* AVC SEI RBSP anyway */
return NULL;
if (type == AVMEDIA_TYPE_UNKNOWN)
return NULL;
/* URI is mandatory for subtitles as per spec */
if (type == AVMEDIA_TYPE_SUBTITLE && !info->uri[0])
return NULL;
/* TODO: handle subtitles (each segment has to parsed separately) */
if (type == AVMEDIA_TYPE_SUBTITLE)
return NULL;
rend = av_mallocz(sizeof(struct rendition));
if (!rend)
return NULL;
dynarray_add(&c->renditions, &c->n_renditions, rend);
rend->type = type;
strcpy(rend->group_id, info->group_id);
strcpy(rend->language, info->language);
strcpy(rend->name, info->name);
/* add the playlist if this is an external rendition */
if (info->uri[0]) {
rend->playlist = new_playlist(c, info->uri, url_base);
if (rend->playlist)
dynarray_add(&rend->playlist->renditions,
&rend->playlist->n_renditions, rend);
}
if (info->assoc_language[0]) {
int langlen = strlen(rend->language);
if (langlen < sizeof(rend->language) - 3) {
rend->language[langlen] = ',';
strncpy(rend->language + langlen + 1, info->assoc_language,
sizeof(rend->language) - langlen - 2);
}
}
if (!strcmp(info->defaultr, "YES"))
rend->disposition |= AV_DISPOSITION_DEFAULT;
if (!strcmp(info->forced, "YES"))
rend->disposition |= AV_DISPOSITION_FORCED;
chr_ptr = info->characteristics;
while ((characteristic = av_strtok(chr_ptr, ",", &saveptr))) {
if (!strcmp(characteristic, "public.accessibility.describes-music-and-sound"))
rend->disposition |= AV_DISPOSITION_HEARING_IMPAIRED;
else if (!strcmp(characteristic, "public.accessibility.describes-video"))
rend->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
chr_ptr = NULL;
}
return rend;
}
static void handle_rendition_args(struct rendition_info *info, const char *key,
int key_len, char **dest, int *dest_len)
{
if (!strncmp(key, "TYPE=", key_len)) {
*dest = info->type;
*dest_len = sizeof(info->type);
} else if (!strncmp(key, "URI=", key_len)) {
*dest = info->uri;
*dest_len = sizeof(info->uri);
} else if (!strncmp(key, "GROUP-ID=", key_len)) {
*dest = info->group_id;
*dest_len = sizeof(info->group_id);
} else if (!strncmp(key, "LANGUAGE=", key_len)) {
*dest = info->language;
*dest_len = sizeof(info->language);
} else if (!strncmp(key, "ASSOC-LANGUAGE=", key_len)) {
*dest = info->assoc_language;
*dest_len = sizeof(info->assoc_language);
} else if (!strncmp(key, "NAME=", key_len)) {
*dest = info->name;
*dest_len = sizeof(info->name);
} else if (!strncmp(key, "DEFAULT=", key_len)) {
*dest = info->defaultr;
*dest_len = sizeof(info->defaultr);
} else if (!strncmp(key, "FORCED=", key_len)) {
*dest = info->forced;
*dest_len = sizeof(info->forced);
} else if (!strncmp(key, "CHARACTERISTICS=", key_len)) {
*dest = info->characteristics;
*dest_len = sizeof(info->characteristics);
}
/*
* ignored:
* - AUTOSELECT: client may autoselect based on e.g. system language
* - INSTREAM-ID: EIA-608 closed caption number ("CC1".."CC4")
*/
}
/* used by parse_playlist to allocate a new variant+playlist when the
* playlist is detected to be a Media Playlist (not Master Playlist)
* and we have no parent Master Playlist (parsing of which would have
* allocated the variant and playlist already) */
static int ensure_playlist(HLSContext *c, struct playlist **pls, const char *url)
{
if (*pls)
return 0;
if (!new_variant(c, NULL, url, NULL))
return AVERROR(ENOMEM);
*pls = c->playlists[c->n_playlists - 1];
return 0;
}
/* pls = NULL => Master Playlist or parentless Media Playlist
* pls = !NULL => parented Media Playlist, playlist+variant allocated */
static int parse_playlist(HLSContext *c, const char *url,
struct playlist *pls, AVIOContext *in)
{
int ret = 0, is_segment = 0, is_variant = 0;
int64_t duration = 0;
enum KeyType key_type = KEY_NONE;
uint8_t iv[16] = "";
int has_iv = 0;
char key[MAX_URL_SIZE] = "";
char line[MAX_URL_SIZE];
const char *ptr;
int close_in = 0;
int64_t seg_offset = 0;
int64_t seg_size = -1;
uint8_t *new_url = NULL;
struct variant_info variant_info;
char tmp_str[MAX_URL_SIZE];
if (!in) {
AVDictionary *opts = NULL;
close_in = 1;
/* Some HLS servers don't like being sent the range header */
av_dict_set(&opts, "seekable", "0", 0);
// broker prior HTTP options that should be consistent across requests
av_dict_set(&opts, "user-agent", c->user_agent, 0);
av_dict_set(&opts, "cookies", c->cookies, 0);
av_dict_set(&opts, "headers", c->headers, 0);
ret = avio_open2(&in, url, AVIO_FLAG_READ,
c->interrupt_callback, &opts);
av_dict_free(&opts);
if (ret < 0)
return ret;
}
if (av_opt_get(in, "location", AV_OPT_SEARCH_CHILDREN, &new_url) >= 0)
url = new_url;
read_chomp_line(in, line, sizeof(line));
if (strcmp(line, "#EXTM3U")) {
ret = AVERROR_INVALIDDATA;
goto fail;
}
if (pls) {
free_segment_list(pls);
pls->finished = 0;
pls->type = PLS_TYPE_UNSPECIFIED;
}
while (!avio_feof(in)) {
read_chomp_line(in, line, sizeof(line));
if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
is_variant = 1;
memset(&variant_info, 0, sizeof(variant_info));
ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args,
&variant_info);
} else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) {
struct key_info info = {{0}};
ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args,
&info);
key_type = KEY_NONE;
has_iv = 0;
if (!strcmp(info.method, "AES-128"))
key_type = KEY_AES_128;
if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) {
ff_hex_to_data(iv, info.iv + 2);
has_iv = 1;
}
av_strlcpy(key, info.uri, sizeof(key));
} else if (av_strstart(line, "#EXT-X-MEDIA:", &ptr)) {
struct rendition_info info = {{0}};
ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_rendition_args,
&info);
new_rendition(c, &info, url);
} else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
ret = ensure_playlist(c, &pls, url);
if (ret < 0)
goto fail;
pls->target_duration = atoi(ptr) * AV_TIME_BASE;
} else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
ret = ensure_playlist(c, &pls, url);
if (ret < 0)
goto fail;
pls->start_seq_no = atoi(ptr);
} else if (av_strstart(line, "#EXT-X-PLAYLIST-TYPE:", &ptr)) {
ret = ensure_playlist(c, &pls, url);
if (ret < 0)
goto fail;
if (!strcmp(ptr, "EVENT"))
pls->type = PLS_TYPE_EVENT;
else if (!strcmp(ptr, "VOD"))
pls->type = PLS_TYPE_VOD;
} else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
if (pls)
pls->finished = 1;
} else if (av_strstart(line, "#EXTINF:", &ptr)) {
is_segment = 1;
duration = atof(ptr) * AV_TIME_BASE;
} else if (av_strstart(line, "#EXT-X-BYTERANGE:", &ptr)) {
seg_size = atoi(ptr);
ptr = strchr(ptr, '@');
if (ptr)
seg_offset = atoi(ptr+1);
} else if (av_strstart(line, "#", NULL)) {
continue;
} else if (line[0]) {
if (is_variant) {
if (!new_variant(c, &variant_info, line, url)) {
ret = AVERROR(ENOMEM);
goto fail;
}
is_variant = 0;
}
if (is_segment) {
struct segment *seg;
if (!pls) {
if (!new_variant(c, 0, url, NULL)) {
ret = AVERROR(ENOMEM);
goto fail;
}
pls = c->playlists[c->n_playlists - 1];
}
seg = av_malloc(sizeof(struct segment));
if (!seg) {
ret = AVERROR(ENOMEM);
goto fail;
}
seg->duration = duration;
seg->key_type = key_type;
if (has_iv) {
memcpy(seg->iv, iv, sizeof(iv));
} else {
int seq = pls->start_seq_no + pls->n_segments;
memset(seg->iv, 0, sizeof(seg->iv));
AV_WB32(seg->iv + 12, seq);
}
if (key_type != KEY_NONE) {
ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, key);
seg->key = av_strdup(tmp_str);
if (!seg->key) {
av_free(seg);
ret = AVERROR(ENOMEM);
goto fail;
}
} else {
seg->key = NULL;
}
ff_make_absolute_url(tmp_str, sizeof(tmp_str), url, line);
seg->url = av_strdup(tmp_str);
if (!seg->url) {
av_free(seg->key);
av_free(seg);
ret = AVERROR(ENOMEM);
goto fail;
}
dynarray_add(&pls->segments, &pls->n_segments, seg);
is_segment = 0;
seg->size = seg_size;
if (seg_size >= 0) {
seg->url_offset = seg_offset;
seg_offset += seg_size;
seg_size = -1;
} else {
seg->url_offset = 0;
seg_offset = 0;
}
}
}
}
if (pls)
pls->last_load_time = av_gettime();
fail:
av_free(new_url);
if (close_in)
avio_close(in);
return ret;
}
enum ReadFromURLMode {
READ_NORMAL,
READ_COMPLETE,
};
/* read from URLContext, limiting read to current segment */
static int read_from_url(struct playlist *pls, uint8_t *buf, int buf_size,
enum ReadFromURLMode mode)
{
int ret;
struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no];
/* limit read if the segment was only a part of a file */
if (seg->size >= 0)
buf_size = FFMIN(buf_size, seg->size - pls->cur_seg_offset);
if (mode == READ_COMPLETE)
ret = ffurl_read_complete(pls->input, buf, buf_size);
else
ret = ffurl_read(pls->input, buf, buf_size);
if (ret > 0)
pls->cur_seg_offset += ret;
return ret;
}
/* Parse the raw ID3 data and pass contents to caller */
static void parse_id3(AVFormatContext *s, AVIOContext *pb,
AVDictionary **metadata, int64_t *dts,
ID3v2ExtraMetaAPIC **apic, ID3v2ExtraMeta **extra_meta)
{
static const char id3_priv_owner_ts[] = "com.apple.streaming.transportStreamTimestamp";
ID3v2ExtraMeta *meta;
ff_id3v2_read_dict(pb, metadata, ID3v2_DEFAULT_MAGIC, extra_meta);
for (meta = *extra_meta; meta; meta = meta->next) {
if (!strcmp(meta->tag, "PRIV")) {
ID3v2ExtraMetaPRIV *priv = meta->data;
if (priv->datasize == 8 && !strcmp(priv->owner, id3_priv_owner_ts)) {
/* 33-bit MPEG timestamp */
int64_t ts = AV_RB64(priv->data);
av_log(s, AV_LOG_DEBUG, "HLS ID3 audio timestamp %"PRId64"\n", ts);
if ((ts & ~((1ULL << 33) - 1)) == 0)
*dts = ts;
else
av_log(s, AV_LOG_ERROR, "Invalid HLS ID3 audio timestamp %"PRId64"\n", ts);
}
} else if (!strcmp(meta->tag, "APIC") && apic)
*apic = meta->data;
}
}
/* Check if the ID3 metadata contents have changed */
static int id3_has_changed_values(struct playlist *pls, AVDictionary *metadata,
ID3v2ExtraMetaAPIC *apic)
{
AVDictionaryEntry *entry = NULL;
AVDictionaryEntry *oldentry;
/* check that no keys have changed values */
while ((entry = av_dict_get(metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
oldentry = av_dict_get(pls->id3_initial, entry->key, NULL, AV_DICT_MATCH_CASE);
if (!oldentry || strcmp(oldentry->value, entry->value) != 0)
return 1;
}
/* check if apic appeared */
if (apic && (pls->ctx->nb_streams != 2 || !pls->ctx->streams[1]->attached_pic.data))
return 1;
if (apic) {
int size = pls->ctx->streams[1]->attached_pic.size;
if (size != apic->buf->size - FF_INPUT_BUFFER_PADDING_SIZE)
return 1;
if (memcmp(apic->buf->data, pls->ctx->streams[1]->attached_pic.data, size) != 0)
return 1;
}
return 0;
}
/* Parse ID3 data and handle the found data */
static void handle_id3(AVIOContext *pb, struct playlist *pls)
{
AVDictionary *metadata = NULL;
ID3v2ExtraMetaAPIC *apic = NULL;
ID3v2ExtraMeta *extra_meta = NULL;
int64_t timestamp = AV_NOPTS_VALUE;
parse_id3(pls->ctx, pb, &metadata, &timestamp, &apic, &extra_meta);
if (timestamp != AV_NOPTS_VALUE) {
pls->id3_mpegts_timestamp = timestamp;
pls->id3_offset = 0;
}
if (!pls->id3_found) {
/* initial ID3 tags */
av_assert0(!pls->id3_deferred_extra);
pls->id3_found = 1;
/* get picture attachment and set text metadata */
if (pls->ctx->nb_streams)
ff_id3v2_parse_apic(pls->ctx, &extra_meta);
else
/* demuxer not yet opened, defer picture attachment */
pls->id3_deferred_extra = extra_meta;
av_dict_copy(&pls->ctx->metadata, metadata, 0);
pls->id3_initial = metadata;
} else {
if (!pls->id3_changed && id3_has_changed_values(pls, metadata, apic)) {
avpriv_report_missing_feature(pls->ctx, "Changing ID3 metadata in HLS audio elementary stream");
pls->id3_changed = 1;
}
av_dict_free(&metadata);
}
if (!pls->id3_deferred_extra)
ff_id3v2_free_extra_meta(&extra_meta);
}
/* Intercept and handle ID3 tags between URLContext and AVIOContext */
static void intercept_id3(struct playlist *pls, uint8_t *buf,
int buf_size, int *len)
{
/* intercept id3 tags, we do not want to pass them to the raw
* demuxer on all segment switches */
int bytes;
int id3_buf_pos = 0;
int fill_buf = 0;
/* gather all the id3 tags */
while (1) {
/* see if we can retrieve enough data for ID3 header */
if (*len < ID3v2_HEADER_SIZE && buf_size >= ID3v2_HEADER_SIZE) {
bytes = read_from_url(pls, buf + *len, ID3v2_HEADER_SIZE - *len, READ_COMPLETE);
if (bytes > 0) {
if (bytes == ID3v2_HEADER_SIZE - *len)
/* no EOF yet, so fill the caller buffer again after
* we have stripped the ID3 tags */
fill_buf = 1;
*len += bytes;
} else if (*len <= 0) {
/* error/EOF */
*len = bytes;
fill_buf = 0;
}
}
if (*len < ID3v2_HEADER_SIZE)
break;
if (ff_id3v2_match(buf, ID3v2_DEFAULT_MAGIC)) {
struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no];
int64_t maxsize = seg->size >= 0 ? seg->size : 1024*1024;
int taglen = ff_id3v2_tag_len(buf);
int tag_got_bytes = FFMIN(taglen, *len);
int remaining = taglen - tag_got_bytes;
if (taglen > maxsize) {
av_log(pls->ctx, AV_LOG_ERROR, "Too large HLS ID3 tag (%d > %"PRId64" bytes)\n",
taglen, maxsize);
break;
}
/*
* Copy the id3 tag to our temporary id3 buffer.
* We could read a small id3 tag directly without memcpy, but
* we would still need to copy the large tags, and handling
* both of those cases together with the possibility for multiple
* tags would make the handling a bit complex.
*/
pls->id3_buf = av_fast_realloc(pls->id3_buf, &pls->id3_buf_size, id3_buf_pos + taglen);
if (!pls->id3_buf)
break;
memcpy(pls->id3_buf + id3_buf_pos, buf, tag_got_bytes);
id3_buf_pos += tag_got_bytes;
/* strip the intercepted bytes */
*len -= tag_got_bytes;
memmove(buf, buf + tag_got_bytes, *len);
av_log(pls->ctx, AV_LOG_DEBUG, "Stripped %d HLS ID3 bytes\n", tag_got_bytes);
if (remaining > 0) {
/* read the rest of the tag in */
if (read_from_url(pls, pls->id3_buf + id3_buf_pos, remaining, READ_COMPLETE) != remaining)
break;
id3_buf_pos += remaining;
av_log(pls->ctx, AV_LOG_DEBUG, "Stripped additional %d HLS ID3 bytes\n", remaining);
}
} else {
/* no more ID3 tags */
break;
}
}
/* re-fill buffer for the caller unless EOF */
if (*len >= 0 && (fill_buf || *len == 0)) {
bytes = read_from_url(pls, buf + *len, buf_size - *len, READ_NORMAL);
/* ignore error if we already had some data */
if (bytes >= 0)
*len += bytes;
else if (*len == 0)
*len = bytes;
}
if (pls->id3_buf) {
/* Now parse all the ID3 tags */
AVIOContext id3ioctx;
ffio_init_context(&id3ioctx, pls->id3_buf, id3_buf_pos, 0, NULL, NULL, NULL, NULL);
handle_id3(&id3ioctx, pls);
}
if (pls->is_id3_timestamped == -1)
pls->is_id3_timestamped = (pls->id3_mpegts_timestamp != AV_NOPTS_VALUE);
}
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
static int check_url(HLSContext *c, const char *url) {
const char *proto_name = avio_find_protocol_name(url);
if (!proto_name)
return AVERROR_INVALIDDATA;
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
if (av_strstart(proto_name, "file", NULL)) {
if (strcmp(c->allowed_extensions, "ALL") && !av_match_ext(url, c->allowed_extensions)) {
av_log(c, AV_LOG_ERROR,
"Filename extension of \'%s\' is not a common multimedia extension, blocked for security reasons.\n"
"If you wish to override this adjust allowed_extensions, you can set it to \'ALL\' to allow all\n",
url);
return AVERROR_INVALIDDATA;
}
} else if (av_strstart(proto_name, "http", NULL)) {
;
} else
return AVERROR_INVALIDDATA;
if (!strncmp(proto_name, url, strlen(proto_name)) && url[strlen(proto_name)] == ':')
return 0;
else if (strcmp(proto_name, "file") || !strncmp(url, "file,", 5))
return AVERROR_INVALIDDATA;
return 0;
}
static int open_input(HLSContext *c, struct playlist *pls)
{
AVDictionary *opts = NULL;
AVDictionary *opts2 = NULL;
int ret;
struct segment *seg = pls->segments[pls->cur_seq_no - pls->start_seq_no];
// broker prior HTTP options that should be consistent across requests
av_dict_set(&opts, "user-agent", c->user_agent, 0);
av_dict_set(&opts, "cookies", c->cookies, 0);
av_dict_set(&opts, "headers", c->headers, 0);
av_dict_set(&opts, "seekable", "0", 0);
// Same opts for key request (ffurl_open mutilates the opts so it cannot be used twice)
av_dict_copy(&opts2, opts, 0);
if (seg->size >= 0) {
/* try to restrict the HTTP request to the part we want
* (if this is in fact a HTTP request) */
av_dict_set_int(&opts, "offset", seg->url_offset, 0);
av_dict_set_int(&opts, "end_offset", seg->url_offset + seg->size, 0);
}
av_log(pls->parent, AV_LOG_VERBOSE, "HLS request for url '%s', offset %"PRId64", playlist %d\n",
seg->url, seg->url_offset, pls->index);
if (seg->key_type == KEY_NONE) {
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
ret = check_url(c, seg->url);
if (ret < 0)
goto cleanup;
ret = ffurl_open(&pls->input, seg->url, AVIO_FLAG_READ,
&pls->parent->interrupt_callback, &opts);
} else if (seg->key_type == KEY_AES_128) {
char iv[33], key[33], url[MAX_URL_SIZE];
if (strcmp(seg->key, pls->key_url)) {
URLContext *uc;
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
ret = check_url(c, seg->key);
if (ret < 0)
goto cleanup;
if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ,
&pls->parent->interrupt_callback, &opts2) == 0) {
if (ffurl_read_complete(uc, pls->key, sizeof(pls->key))
!= sizeof(pls->key)) {
av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n",
seg->key);
}
ffurl_close(uc);
} else {
av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n",
seg->key);
}
av_strlcpy(pls->key_url, seg->key, sizeof(pls->key_url));
}
ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0);
ff_data_to_hex(key, pls->key, sizeof(pls->key), 0);
iv[32] = key[32] = '\0';
if (strstr(seg->url, "://"))
snprintf(url, sizeof(url), "crypto+%s", seg->url);
else
snprintf(url, sizeof(url), "crypto:%s", seg->url);
if ((ret = ffurl_alloc(&pls->input, url, AVIO_FLAG_READ,
&pls->parent->interrupt_callback)) < 0)
goto cleanup;
av_opt_set(pls->input->priv_data, "key", key, 0);
av_opt_set(pls->input->priv_data, "iv", iv, 0);
if ((ret = ffurl_connect(pls->input, &opts)) < 0) {
ffurl_close(pls->input);
pls->input = NULL;
goto cleanup;
}
ret = 0;
}
else
ret = AVERROR(ENOSYS);
/* Seek to the requested position. If this was a HTTP request, the offset
* should already be where want it to, but this allows e.g. local testing
* without a HTTP server. */
if (ret == 0 && seg->key_type == KEY_NONE) {
int seekret = ffurl_seek(pls->input, seg->url_offset, SEEK_SET);
if (seekret < 0) {
av_log(pls->parent, AV_LOG_ERROR, "Unable to seek to offset %"PRId64" of HLS segment '%s'\n", seg->url_offset, seg->url);
ret = seekret;
ffurl_close(pls->input);
pls->input = NULL;
}
}
cleanup:
av_dict_free(&opts);
av_dict_free(&opts2);
pls->cur_seg_offset = 0;
return ret;
}
static int64_t default_reload_interval(struct playlist *pls)
{
return pls->n_segments > 0 ?
pls->segments[pls->n_segments - 1]->duration :
pls->target_duration;
}
static int read_data(void *opaque, uint8_t *buf, int buf_size)
{
struct playlist *v = opaque;
HLSContext *c = v->parent->priv_data;
int ret, i;
int just_opened = 0;
int reload_count = 0;
restart:
if (!v->needed)
return AVERROR_EOF;
if (!v->input) {
int64_t reload_interval;
/* Check that the playlist is still needed before opening a new
* segment. */
if (v->ctx && v->ctx->nb_streams &&
v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) {
v->needed = 0;
for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams;
i++) {
if (v->parent->streams[i]->discard < AVDISCARD_ALL)
v->needed = 1;
}
}
if (!v->needed) {
av_log(v->parent, AV_LOG_INFO, "No longer receiving playlist %d\n",
v->index);
return AVERROR_EOF;
}
/* If this is a live stream and the reload interval has elapsed since
* the last playlist reload, reload the playlists now. */
reload_interval = default_reload_interval(v);
reload:
reload_count++;
if (reload_count > c->max_reload)
return AVERROR_EOF;
if (!v->finished &&
av_gettime() - v->last_load_time >= reload_interval) {
if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) {
av_log(v->parent, AV_LOG_WARNING, "Failed to reload playlist %d\n",
v->index);
return ret;
}
/* If we need to reload the playlist again below (if
* there's still no more segments), switch to a reload
* interval of half the target duration. */
reload_interval = v->target_duration / 2;
}
if (v->cur_seq_no < v->start_seq_no) {
av_log(NULL, AV_LOG_WARNING,
"skipping %d segments ahead, expired from playlists\n",
v->start_seq_no - v->cur_seq_no);
v->cur_seq_no = v->start_seq_no;
}
if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
if (v->finished)
return AVERROR_EOF;
while (av_gettime() - v->last_load_time < reload_interval) {
2011-11-06 20:34:24 +00:00
if (ff_check_interrupt(c->interrupt_callback))
return AVERROR_EXIT;
av_usleep(100*1000);
}
/* Enough time has elapsed since the last reload */
goto reload;
}
ret = open_input(c, v);
if (ret < 0) {
av_log(v->parent, AV_LOG_WARNING, "Failed to open segment of playlist %d\n",
v->index);
return ret;
}
just_opened = 1;
}
ret = read_from_url(v, buf, buf_size, READ_NORMAL);
if (ret > 0) {
if (just_opened && v->is_id3_timestamped != 0) {
/* Intercept ID3 tags here, elementary audio streams are required
* to convey timestamps using them in the beginning of each segment. */
intercept_id3(v, buf, buf_size, &ret);
}
return ret;
}
ffurl_close(v->input);
v->input = NULL;
v->cur_seq_no++;
c->cur_seq_no = v->cur_seq_no;
goto restart;
}
static int playlist_in_multiple_variants(HLSContext *c, struct playlist *pls)
{
int variant_count = 0;
int i, j;
for (i = 0; i < c->n_variants && variant_count < 2; i++) {
struct variant *v = c->variants[i];
for (j = 0; j < v->n_playlists; j++) {
if (v->playlists[j] == pls) {
variant_count++;
break;
}
}
}
return variant_count >= 2;
}
static void add_renditions_to_variant(HLSContext *c, struct variant *var,
enum AVMediaType type, const char *group_id)
{
int i;
for (i = 0; i < c->n_renditions; i++) {
struct rendition *rend = c->renditions[i];
if (rend->type == type && !strcmp(rend->group_id, group_id)) {
if (rend->playlist)
/* rendition is an external playlist
* => add the playlist to the variant */
dynarray_add(&var->playlists, &var->n_playlists, rend->playlist);
else
/* rendition is part of the variant main Media Playlist
* => add the rendition to the main Media Playlist */
dynarray_add(&var->playlists[0]->renditions,
&var->playlists[0]->n_renditions,
rend);
}
}
}
static void add_metadata_from_renditions(AVFormatContext *s, struct playlist *pls,
enum AVMediaType type)
{
int rend_idx = 0;
int i;
for (i = 0; i < pls->ctx->nb_streams; i++) {
AVStream *st = s->streams[pls->stream_offset + i];
if (st->codec->codec_type != type)
continue;
for (; rend_idx < pls->n_renditions; rend_idx++) {
struct rendition *rend = pls->renditions[rend_idx];
if (rend->type != type)
continue;
if (rend->language[0])
av_dict_set(&st->metadata, "language", rend->language, 0);
if (rend->name[0])
av_dict_set(&st->metadata, "comment", rend->name, 0);
st->disposition |= rend->disposition;
}
if (rend_idx >=pls->n_renditions)
break;
}
}
/* if timestamp was in valid range: returns 1 and sets seq_no
* if not: returns 0 and sets seq_no to closest segment */
static int find_timestamp_in_playlist(HLSContext *c, struct playlist *pls,
int64_t timestamp, int *seq_no)
{
int i;
int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ?
0 : c->first_timestamp;
if (timestamp < pos) {
*seq_no = pls->start_seq_no;
return 0;
}
for (i = 0; i < pls->n_segments; i++) {
int64_t diff = pos + pls->segments[i]->duration - timestamp;
if (diff > 0) {
*seq_no = pls->start_seq_no + i;
return 1;
}
pos += pls->segments[i]->duration;
}
*seq_no = pls->start_seq_no + pls->n_segments - 1;
return 0;
}
static int select_cur_seq_no(HLSContext *c, struct playlist *pls)
{
int seq_no;
if (!pls->finished && !c->first_packet &&
av_gettime() - pls->last_load_time >= default_reload_interval(pls))
/* reload the playlist since it was suspended */
parse_playlist(c, pls->url, pls, NULL);
/* If playback is already in progress (we are just selecting a new
* playlist) and this is a complete file, find the matching segment
* by counting durations. */
if (pls->finished && c->cur_timestamp != AV_NOPTS_VALUE) {
find_timestamp_in_playlist(c, pls, c->cur_timestamp, &seq_no);
return seq_no;
}
if (!pls->finished) {
if (!c->first_packet && /* we are doing a segment selection during playback */
c->cur_seq_no >= pls->start_seq_no &&
c->cur_seq_no < pls->start_seq_no + pls->n_segments)
/* While spec 3.4.3 says that we cannot assume anything about the
* content at the same sequence number on different playlists,
* in practice this seems to work and doing it otherwise would
* require us to download a segment to inspect its timestamps. */
return c->cur_seq_no;
/* If this is a live stream with more than 3 segments, start at the
* third last segment. */
if (pls->n_segments > 3)
return pls->start_seq_no + pls->n_segments - 3;
}
/* Otherwise just start on the first segment. */
return pls->start_seq_no;
}
static int hls_read_header(AVFormatContext *s)
{
URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque;
HLSContext *c = s->priv_data;
int ret = 0, i, j, stream_offset = 0;
2011-11-06 20:34:24 +00:00
c->interrupt_callback = &s->interrupt_callback;
c->first_packet = 1;
c->first_timestamp = AV_NOPTS_VALUE;
c->cur_timestamp = AV_NOPTS_VALUE;
// if the URL context is good, read important options we must broker later
if (u && u->prot->priv_data_class) {
// get the previous user agent & set back to null if string size is zero
av_freep(&c->user_agent);
av_opt_get(u->priv_data, "user-agent", 0, (uint8_t**)&(c->user_agent));
if (c->user_agent && !strlen(c->user_agent))
av_freep(&c->user_agent);
// get the previous cookies & set back to null if string size is zero
av_freep(&c->cookies);
av_opt_get(u->priv_data, "cookies", 0, (uint8_t**)&(c->cookies));
if (c->cookies && !strlen(c->cookies))
av_freep(&c->cookies);
// get the previous headers & set back to null if string size is zero
av_freep(&c->headers);
av_opt_get(u->priv_data, "headers", 0, (uint8_t**)&(c->headers));
if (c->headers && !strlen(c->headers))
av_freep(&c->headers);
}
if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0)
goto fail;
if (c->n_variants == 0) {
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
ret = AVERROR_EOF;
goto fail;
}
/* If the playlist only contained playlists (Master Playlist),
* parse each individual playlist. */
if (c->n_playlists > 1 || c->playlists[0]->n_segments == 0) {
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
if ((ret = parse_playlist(c, pls->url, pls, NULL)) < 0)
goto fail;
}
}
if (c->variants[0]->playlists[0]->n_segments == 0) {
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
ret = AVERROR_EOF;
goto fail;
}
/* If this isn't a live stream, calculate the total duration of the
* stream. */
if (c->variants[0]->playlists[0]->finished) {
int64_t duration = 0;
for (i = 0; i < c->variants[0]->playlists[0]->n_segments; i++)
duration += c->variants[0]->playlists[0]->segments[i]->duration;
s->duration = duration;
}
/* Associate renditions with variants */
for (i = 0; i < c->n_variants; i++) {
struct variant *var = c->variants[i];
if (var->audio_group[0])
add_renditions_to_variant(c, var, AVMEDIA_TYPE_AUDIO, var->audio_group);
if (var->video_group[0])
add_renditions_to_variant(c, var, AVMEDIA_TYPE_VIDEO, var->video_group);
if (var->subtitles_group[0])
add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group);
}
/* Open the demuxer for each playlist */
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
AVInputFormat *in_fmt = NULL;
if (pls->n_segments == 0)
continue;
if (!(pls->ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto fail;
}
pls->index = i;
pls->needed = 1;
pls->parent = s;
pls->cur_seq_no = select_cur_seq_no(c, pls);
pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
read_data, NULL, NULL);
pls->pb.seekable = 0;
ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
NULL, 0, 0);
if (ret < 0) {
/* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn't be called. If
* avformat_open_input fails below, it frees and zeros the
* context, so it doesn't need any special treatment like this. */
av_log(s, AV_LOG_ERROR, "Error when loading first segment '%s'\n", pls->segments[0]->url);
avformat_free_context(pls->ctx);
pls->ctx = NULL;
goto fail;
}
pls->ctx->pb = &pls->pb;
pls->stream_offset = stream_offset;
ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);
if (ret < 0)
goto fail;
if (pls->id3_deferred_extra && pls->ctx->nb_streams == 1) {
ff_id3v2_parse_apic(pls->ctx, &pls->id3_deferred_extra);
avformat_queue_attached_pictures(pls->ctx);
ff_id3v2_free_extra_meta(&pls->id3_deferred_extra);
pls->id3_deferred_extra = NULL;
}
pls->ctx->ctx_flags &= ~AVFMTCTX_NOHEADER;
ret = avformat_find_stream_info(pls->ctx, NULL);
if (ret < 0)
goto fail;
if (pls->is_id3_timestamped == -1)
av_log(s, AV_LOG_WARNING, "No expected HTTP requests have been made\n");
/* Create new AVStreams for each stream in this playlist */
for (j = 0; j < pls->ctx->nb_streams; j++) {
AVStream *st = avformat_new_stream(s, NULL);
AVStream *ist = pls->ctx->streams[j];
if (!st) {
ret = AVERROR(ENOMEM);
goto fail;
}
st->id = i;
avcodec_copy_context(st->codec, pls->ctx->streams[j]->codec);
if (pls->is_id3_timestamped) /* custom timestamps via id3 */
avpriv_set_pts_info(st, 33, 1, MPEG_TIME_BASE);
else
avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
}
add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_AUDIO);
add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_VIDEO);
add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_SUBTITLE);
stream_offset += pls->ctx->nb_streams;
}
/* Create a program for each variant */
for (i = 0; i < c->n_variants; i++) {
struct variant *v = c->variants[i];
AVProgram *program;
program = av_new_program(s, i);
if (!program)
goto fail;
av_dict_set_int(&program->metadata, "variant_bitrate", v->bandwidth, 0);
for (j = 0; j < v->n_playlists; j++) {
struct playlist *pls = v->playlists[j];
int is_shared = playlist_in_multiple_variants(c, pls);
int k;
for (k = 0; k < pls->ctx->nb_streams; k++) {
struct AVStream *st = s->streams[pls->stream_offset + k];
ff_program_add_stream_index(s, i, pls->stream_offset + k);
/* Set variant_bitrate for streams unique to this variant */
if (!is_shared && v->bandwidth)
av_dict_set_int(&st->metadata, "variant_bitrate", v->bandwidth, 0);
}
}
}
return 0;
fail:
free_playlist_list(c);
free_variant_list(c);
free_rendition_list(c);
return ret;
}
static int recheck_discard_flags(AVFormatContext *s, int first)
{
HLSContext *c = s->priv_data;
int i, changed = 0;
/* Check if any new streams are needed */
for (i = 0; i < c->n_playlists; i++)
c->playlists[i]->cur_needed = 0;
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
struct playlist *pls = c->playlists[s->streams[i]->id];
if (st->discard < AVDISCARD_ALL)
pls->cur_needed = 1;
}
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
if (pls->cur_needed && !pls->needed) {
pls->needed = 1;
changed = 1;
pls->cur_seq_no = select_cur_seq_no(c, pls);
pls->pb.eof_reached = 0;
if (c->cur_timestamp != AV_NOPTS_VALUE) {
/* catch up */
pls->seek_timestamp = c->cur_timestamp;
pls->seek_flags = AVSEEK_FLAG_ANY;
pls->seek_stream_index = -1;
}
av_log(s, AV_LOG_INFO, "Now receiving playlist %d, segment %d\n", i, pls->cur_seq_no);
} else if (first && !pls->cur_needed && pls->needed) {
if (pls->input)
ffurl_close(pls->input);
pls->input = NULL;
pls->needed = 0;
changed = 1;
av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i);
}
}
return changed;
}
static void fill_timing_for_id3_timestamped_stream(struct playlist *pls)
{
if (pls->id3_offset >= 0) {
pls->pkt.dts = pls->id3_mpegts_timestamp +
av_rescale_q(pls->id3_offset,
pls->ctx->streams[pls->pkt.stream_index]->time_base,
MPEG_TIME_BASE_Q);
if (pls->pkt.duration)
pls->id3_offset += pls->pkt.duration;
else
pls->id3_offset = -1;
} else {
/* there have been packets with unknown duration
* since the last id3 tag, should not normally happen */
pls->pkt.dts = AV_NOPTS_VALUE;
}
if (pls->pkt.duration)
pls->pkt.duration = av_rescale_q(pls->pkt.duration,
pls->ctx->streams[pls->pkt.stream_index]->time_base,
MPEG_TIME_BASE_Q);
pls->pkt.pts = AV_NOPTS_VALUE;
}
static AVRational get_timebase(struct playlist *pls)
{
if (pls->is_id3_timestamped)
return MPEG_TIME_BASE_Q;
return pls->ctx->streams[pls->pkt.stream_index]->time_base;
}
static int compare_ts_with_wrapdetect(int64_t ts_a, struct playlist *pls_a,
int64_t ts_b, struct playlist *pls_b)
{
int64_t scaled_ts_a = av_rescale_q(ts_a, get_timebase(pls_a), MPEG_TIME_BASE_Q);
int64_t scaled_ts_b = av_rescale_q(ts_b, get_timebase(pls_b), MPEG_TIME_BASE_Q);
return av_compare_mod(scaled_ts_a, scaled_ts_b, 1LL << 33);
}
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
{
HLSContext *c = s->priv_data;
int ret, i, minplaylist = -1;
recheck_discard_flags(s, c->first_packet);
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
/* Make sure we've got one buffered packet from each open playlist
* stream */
if (pls->needed && !pls->pkt.data) {
while (1) {
int64_t ts_diff;
AVRational tb;
ret = av_read_frame(pls->ctx, &pls->pkt);
if (ret < 0) {
if (!avio_feof(&pls->pb) && ret != AVERROR_EOF)
return ret;
reset_packet(&pls->pkt);
break;
} else {
/* stream_index check prevents matching picture attachments etc. */
if (pls->is_id3_timestamped && pls->pkt.stream_index == 0) {
/* audio elementary streams are id3 timestamped */
fill_timing_for_id3_timestamped_stream(pls);
}
if (c->first_timestamp == AV_NOPTS_VALUE &&
pls->pkt.dts != AV_NOPTS_VALUE)
c->first_timestamp = av_rescale_q(pls->pkt.dts,
get_timebase(pls), AV_TIME_BASE_Q);
}
if (pls->seek_timestamp == AV_NOPTS_VALUE)
break;
if (pls->seek_stream_index < 0 ||
pls->seek_stream_index == pls->pkt.stream_index) {
if (pls->pkt.dts == AV_NOPTS_VALUE) {
pls->seek_timestamp = AV_NOPTS_VALUE;
break;
}
tb = get_timebase(pls);
ts_diff = av_rescale_rnd(pls->pkt.dts, AV_TIME_BASE,
tb.den, AV_ROUND_DOWN) -
pls->seek_timestamp;
if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY ||
pls->pkt.flags & AV_PKT_FLAG_KEY)) {
pls->seek_timestamp = AV_NOPTS_VALUE;
break;
}
}
av_free_packet(&pls->pkt);
reset_packet(&pls->pkt);
}
}
/* Check if this stream has the packet with the lowest dts */
if (pls->pkt.data) {
struct playlist *minpls = minplaylist < 0 ?
NULL : c->playlists[minplaylist];
if (minplaylist < 0) {
minplaylist = i;
} else {
int64_t dts = pls->pkt.dts;
int64_t mindts = minpls->pkt.dts;
if (dts == AV_NOPTS_VALUE ||
(mindts != AV_NOPTS_VALUE && compare_ts_with_wrapdetect(dts, pls, mindts, minpls) < 0))
minplaylist = i;
}
}
}
/* If we got a packet, return it */
if (minplaylist >= 0) {
struct playlist *pls = c->playlists[minplaylist];
*pkt = pls->pkt;
pkt->stream_index += pls->stream_offset;
reset_packet(&c->playlists[minplaylist]->pkt);
if (pkt->dts != AV_NOPTS_VALUE)
c->cur_timestamp = av_rescale_q(pkt->dts,
pls->ctx->streams[pls->pkt.stream_index]->time_base,
AV_TIME_BASE_Q);
return 0;
}
return AVERROR_EOF;
}
static int hls_close(AVFormatContext *s)
{
HLSContext *c = s->priv_data;
free_playlist_list(c);
free_variant_list(c);
free_rendition_list(c);
return 0;
}
static int hls_read_seek(AVFormatContext *s, int stream_index,
int64_t timestamp, int flags)
{
HLSContext *c = s->priv_data;
struct playlist *seek_pls = NULL;
int i, seq_no;
int64_t first_timestamp, seek_timestamp, duration;
if ((flags & AVSEEK_FLAG_BYTE) ||
!(c->variants[0]->playlists[0]->finished || c->variants[0]->playlists[0]->type == PLS_TYPE_EVENT))
return AVERROR(ENOSYS);
first_timestamp = c->first_timestamp == AV_NOPTS_VALUE ?
0 : c->first_timestamp;
seek_timestamp = av_rescale_rnd(timestamp, AV_TIME_BASE,
s->streams[stream_index]->time_base.den,
flags & AVSEEK_FLAG_BACKWARD ?
AV_ROUND_DOWN : AV_ROUND_UP);
duration = s->duration == AV_NOPTS_VALUE ?
0 : s->duration;
if (0 < duration && duration < seek_timestamp - first_timestamp)
return AVERROR(EIO);
/* find the playlist with the specified stream */
for (i = 0; i < c->n_playlists; i++) {
struct playlist *pls = c->playlists[i];
if (stream_index >= pls->stream_offset &&
stream_index - pls->stream_offset < pls->ctx->nb_streams) {
seek_pls = pls;
break;
}
}
/* check if the timestamp is valid for the playlist with the
* specified stream index */
if (!seek_pls || !find_timestamp_in_playlist(c, seek_pls, seek_timestamp, &seq_no))
return AVERROR(EIO);
/* set segment now so we do not need to search again below */
seek_pls->cur_seq_no = seq_no;
seek_pls->seek_stream_index = stream_index - seek_pls->stream_offset;
for (i = 0; i < c->n_playlists; i++) {
/* Reset reading */
struct playlist *pls = c->playlists[i];
if (pls->input) {
ffurl_close(pls->input);
pls->input = NULL;
}
av_free_packet(&pls->pkt);
reset_packet(&pls->pkt);
pls->pb.eof_reached = 0;
/* Clear any buffered data */
pls->pb.buf_end = pls->pb.buf_ptr = pls->pb.buffer;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */
pls->pb.pos = 0;
/* Flush the packet queue of the subdemuxer. */
ff_read_frame_flush(pls->ctx);
pls->seek_timestamp = seek_timestamp;
pls->seek_flags = flags;
if (pls != seek_pls) {
/* set closest segment seq_no for playlists not handled above */
find_timestamp_in_playlist(c, pls, seek_timestamp, &pls->cur_seq_no);
/* seek the playlist to the given position without taking
* keyframes into account since this playlist does not have the
* specified stream where we should look for the keyframes */
pls->seek_stream_index = -1;
pls->seek_flags |= AVSEEK_FLAG_ANY;
}
}
c->cur_timestamp = seek_timestamp;
return 0;
}
static int hls_probe(AVProbeData *p)
{
/* Require #EXTM3U at the start, and either one of the ones below
* somewhere for a proper match. */
if (strncmp(p->buf, "#EXTM3U", 7))
return 0;
if (strstr(p->buf, "#EXT-X-STREAM-INF:") ||
strstr(p->buf, "#EXT-X-TARGETDURATION:") ||
strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:"))
return AVPROBE_SCORE_MAX;
return 0;
}
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
#define OFFSET(x) offsetof(HLSContext, x)
#define FLAGS AV_OPT_FLAG_DECODING_PARAM
static const AVOption hls_options[] = {
{"allowed_extensions", "List of file extensions that hls is allowed to access",
OFFSET(allowed_extensions), AV_OPT_TYPE_STRING,
{.str = "3gp,aac,avi,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,wav"},
INT_MIN, INT_MAX, FLAGS},
{"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded",
OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
{NULL}
};
static const AVClass hls_class = {
.class_name = "hls,applehttp",
.item_name = av_default_item_name,
.option = hls_options,
.version = LIBAVUTIL_VERSION_INT,
};
AVInputFormat ff_hls_demuxer = {
.name = "hls,applehttp",
.long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
.priv_data_size = sizeof(HLSContext),
.read_probe = hls_probe,
.read_header = hls_read_header,
.read_packet = hls_read_packet,
.read_close = hls_close,
.read_seek = hls_read_seek,
avformat/hls: Check local file extensions This reduces the attack surface of local file-system information leaking. It prevents the existing exploit leading to an information leak. As well as similar hypothetical attacks. Leaks of information from files and symlinks ending in common multimedia extensions are still possible. But files with sensitive information like private keys and passwords generally do not use common multimedia filename extensions. It does not stop leaks via remote addresses in the LAN. The existing exploit depends on a specific decoder as well. It does appear though that the exploit should be possible with any decoder. The problem is that as long as sensitive information gets into the decoder, the output of the decoder becomes sensitive as well. The only obvious solution is to prevent access to sensitive information. Or to disable hls or possibly some of its feature. More complex solutions like checking the path to limit access to only subdirectories of the hls path may work as an alternative. But such solutions are fragile and tricky to implement portably and would not stop every possible attack nor would they work with all valid hls files. Developers have expressed their dislike / objected to disabling hls by default as well as disabling hls with local files. There also where objections against restricting remote url file extensions. This here is a less robust but also lower inconvenience solution. It can be applied stand alone or together with other solutions. limiting the check to local files was suggested by nevcairiel This recommits the security fix without the author name joke which was originally requested by Nicolas. Found-by: Emil Lerner and Pavel Cheremushkin Reported-by: Thierry Foucu <tfoucu@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc> (cherry picked from commit 189ff4219644532bdfa7bab28dfedaee4d6d4021) Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2017-06-03 19:20:04 +00:00
.priv_class = &hls_class,
};