PR #24857 opened by Kacper Michajłow (kasper93) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24857 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24857.patch
Be strict about setting options, to avoid unexpected behaviour. Options that can fail, should be explicitly feature checked before setting. Fixes: #24812 From 8d14c2ac2b6e306a4e5be7d84be3a2267f116d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Sat, 3 Oct 2026 02:00:41 +0200 Subject: [PATCH] avformat/libcurl: fail when an option cannot be set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Be strict about setting options, to avoid unexpected behaviour. Options that can fail, should be explicitly feature checked before setting. Fixes: #24812 Signed-off-by: Kacper Michajłow <[email protected]> --- libavformat/libcurl.c | 227 +++++++++++++++++++++++++++--------------- 1 file changed, 146 insertions(+), 81 deletions(-) diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c index 983b207722..443952fced 100644 --- a/libavformat/libcurl.c +++ b/libavformat/libcurl.c @@ -196,7 +196,10 @@ static int curlcode_to_averror(CURLcode code) switch (code) { case CURLE_OK: return 0; case CURLE_URL_MALFORMAT: - case CURLE_UNSUPPORTED_PROTOCOL: return AVERROR(EINVAL); + case CURLE_UNSUPPORTED_PROTOCOL: + case CURLE_BAD_FUNCTION_ARGUMENT: return AVERROR(EINVAL); + case CURLE_UNKNOWN_OPTION: + case CURLE_NOT_BUILT_IN: return AVERROR(ENOSYS); case CURLE_COULDNT_RESOLVE_PROXY: case CURLE_COULDNT_RESOLVE_HOST: return AVERROR(EHOSTUNREACH); case CURLE_COULDNT_CONNECT: return AVERROR(ECONNREFUSED); @@ -250,6 +253,16 @@ static int is_recoverable(CURLcode code) } } +static int check_setopt(CurlContext *c, const char *name, CURLcode code) +{ + if (code == CURLE_OK) + return 0; + + av_log(c->h, AV_LOG_ERROR, "Failed to set %s: %s\n", name, + curl_easy_strerror(code)); + return curlcode_to_averror(code); +} + /* ------------------------------------------------------------------------- */ /* curl callbacks (run on the loop thread) */ /* ------------------------------------------------------------------------- */ @@ -634,9 +647,12 @@ static int xferinfo_callback(void *userdata, curl_off_t dltotal, curl_off_t dlno * thread only. */ static void start_request(CurlContext *c) { + char range[48], *range_arg = NULL; + CURLcode code; + int ret; + if (!c->probed || c->seekable) { int64_t start = c->request_start; - char range[48]; int64_t request_size = c->request_size; if (c->is_initial && c->initial_request_size > 0) request_size = c->initial_request_size; @@ -652,9 +668,15 @@ static void start_request(CurlContext *c) } else { snprintf(range, sizeof(range), "%"PRId64"-", start); } - curl_easy_setopt(c->easy, CURLOPT_RANGE, range); - } else { - curl_easy_setopt(c->easy, CURLOPT_RANGE, NULL); + range_arg = range; + } + code = curl_easy_setopt(c->easy, CURLOPT_RANGE, range_arg); + if ((ret = check_setopt(c, "CURLOPT_RANGE", code)) < 0) { + pthread_mutex_lock(&c->mutex); + update_status_locked(c, ret, code); + pthread_cond_broadcast(&c->cond); + pthread_mutex_unlock(&c->mutex); + return; } c->loop->num_requests++; c->request_received = 0; @@ -937,6 +959,7 @@ static void share_unlock_callback(CURL *handle, curl_lock_data data, static CurlLoop *curl_loop_create(AVFormatContext *avfc) { CurlLoop *loop = av_mallocz(sizeof(*loop)); + CURLSHcode res; if (!loop) return NULL; loop->avfc = avfc; @@ -954,16 +977,22 @@ static CurlLoop *curl_loop_create(AVFormatContext *avfc) loop->multi = curl_multi_init(); if (!loop->multi) goto fail3; - curl_multi_setopt(loop->multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); + if (curl_multi_setopt(loop->multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX) != CURLM_OK) + goto fail3; loop->share = curl_share_init(); if (!loop->share) goto fail3; - curl_share_setopt(loop->share, CURLSHOPT_USERDATA, loop); - curl_share_setopt(loop->share, CURLSHOPT_LOCKFUNC, share_lock_callback); - curl_share_setopt(loop->share, CURLSHOPT_UNLOCKFUNC, share_unlock_callback); - curl_share_setopt(loop->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); - curl_share_setopt(loop->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS); + if (curl_share_setopt(loop->share, CURLSHOPT_USERDATA, loop) != CURLSHE_OK || + curl_share_setopt(loop->share, CURLSHOPT_LOCKFUNC, share_lock_callback) != CURLSHE_OK || + curl_share_setopt(loop->share, CURLSHOPT_UNLOCKFUNC, share_unlock_callback) != CURLSHE_OK) + goto fail3; + res = curl_share_setopt(loop->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); + if (res != CURLSHE_OK && res != CURLSHE_NOT_BUILT_IN) + goto fail3; + res = curl_share_setopt(loop->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS); + if (res != CURLSHE_OK && res != CURLSHE_NOT_BUILT_IN) + goto fail3; if (pthread_create(&loop->thread, NULL, curl_worker, loop)) goto fail3; @@ -1086,37 +1115,49 @@ static int debug_callback(CURL *easy, curl_infotype type, char *data, return 0; } -/* Build the custom request header list from the referer and headers options. */ -static struct curl_slist *build_headers(CurlContext *c) +static int slist_append(struct curl_slist **list, const char *str) { - struct curl_slist *list = NULL; - int user_set_icy = 0; + struct curl_slist *tmp = curl_slist_append(*list, str); + if (!tmp) + return AVERROR(ENOMEM); + *list = tmp; + return 0; +} + +/* Build the custom request header list from the referer and headers options. */ +static int build_headers(CurlContext *c) +{ + int user_set_icy = 0, ret = 0; if (c->referer && c->referer[0]) { char *h = av_asprintf("Referer: %s", c->referer); - if (h) { - list = curl_slist_append(list, h); - av_free(h); - } + if (!h) + return AVERROR(ENOMEM); + ret = slist_append(&c->header_list, h); + av_free(h); + if (ret < 0) + return ret; } if (c->headers && c->headers[0]) { char *copy = av_strdup(c->headers); char *line, *saveptr = NULL; - if (copy) { - for (line = av_strtok(copy, "\r\n", &saveptr); line; - line = av_strtok(NULL, "\r\n", &saveptr)) { - if (!av_strncasecmp(line, "Icy-MetaData:", 13)) - user_set_icy = 1; - list = curl_slist_append(list, line); - } - av_free(copy); + if (!copy) + return AVERROR(ENOMEM); + for (line = av_strtok(copy, "\r\n", &saveptr); line && ret >= 0; + line = av_strtok(NULL, "\r\n", &saveptr)) { + if (!av_strncasecmp(line, "Icy-MetaData:", 13)) + user_set_icy = 1; + ret = slist_append(&c->header_list, line); } + av_free(copy); + if (ret < 0) + return ret; } /* libcurl does not deduplicate the list, so only add ours if the user * did not already ask for one. */ if (c->icy && !user_set_icy) - list = curl_slist_append(list, "Icy-MetaData: 1"); - return list; + return slist_append(&c->header_list, "Icy-MetaData: 1"); + return 0; } static int setup_protocols(CurlContext *c) @@ -1154,97 +1195,119 @@ static int setup_protocols(CurlContext *c) return AVERROR(EINVAL); } - curl_easy_setopt(c->easy, CURLOPT_PROTOCOLS_STR, bp.str); - curl_easy_setopt(c->easy, CURLOPT_REDIR_PROTOCOLS_STR, bp.str); + int ret = check_setopt(c, "CURLOPT_PROTOCOLS_STR", + curl_easy_setopt(c->easy, CURLOPT_PROTOCOLS_STR, bp.str)); + if (ret >= 0) + ret = check_setopt(c, "CURLOPT_REDIR_PROTOCOLS_STR", + curl_easy_setopt(c->easy, CURLOPT_REDIR_PROTOCOLS_STR, bp.str)); av_bprint_finalize(&bp, NULL); - return 0; + return ret; } -static void setup_curl(CurlContext *c) +#define SETOPT(opt, val) \ + do { \ + int err = check_setopt(c, #opt, curl_easy_setopt(e, opt, val)); \ + if (err < 0) \ + return err; \ + } while (0) + +static int setup_curl(CurlContext *c) { + curl_version_info_data *info = curl_version_info(CURLVERSION_NOW); CURL *e = c->easy; const char *url = c->h->filename; + int ret = 0; /* Drop an optional "libcurl:" prefix that forces this protocol. */ av_strstart(url, "libcurl:", &url); - curl_easy_setopt(e, CURLOPT_URL, url); - curl_easy_setopt(e, CURLOPT_PRIVATE, c); - curl_easy_setopt(e, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(e, CURLOPT_SHARE, c->loop->share); + SETOPT(CURLOPT_URL, url); + SETOPT(CURLOPT_PRIVATE, c); + SETOPT(CURLOPT_NOSIGNAL, 1L); + SETOPT(CURLOPT_SHARE, c->loop->share); - curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(e, CURLOPT_WRITEDATA, c); - curl_easy_setopt(e, CURLOPT_HEADERFUNCTION, header_callback); - curl_easy_setopt(e, CURLOPT_HEADERDATA, c); + SETOPT(CURLOPT_WRITEFUNCTION, write_callback); + SETOPT(CURLOPT_WRITEDATA, c); + SETOPT(CURLOPT_HEADERFUNCTION, header_callback); + SETOPT(CURLOPT_HEADERDATA, c); - curl_easy_setopt(e, CURLOPT_NOPROGRESS, 0L); - curl_easy_setopt(e, CURLOPT_XFERINFOFUNCTION, xferinfo_callback); - curl_easy_setopt(e, CURLOPT_XFERINFODATA, c); + SETOPT(CURLOPT_NOPROGRESS, 0L); + SETOPT(CURLOPT_XFERINFOFUNCTION, xferinfo_callback); + SETOPT(CURLOPT_XFERINFODATA, c); if (av_log_get_level() >= AV_LOG_DEBUG) { - curl_easy_setopt(e, CURLOPT_VERBOSE, 1L); - curl_easy_setopt(e, CURLOPT_DEBUGFUNCTION, debug_callback); - curl_easy_setopt(e, CURLOPT_DEBUGDATA, c); + SETOPT(CURLOPT_VERBOSE, 1L); + SETOPT(CURLOPT_DEBUGFUNCTION, debug_callback); + SETOPT(CURLOPT_DEBUGDATA, c); } - curl_easy_setopt(e, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(e, CURLOPT_MAXREDIRS, (long)c->max_redirects); - curl_easy_setopt(e, CURLOPT_HTTP_VERSION, (long)c->http_version); - curl_easy_setopt(e, CURLOPT_TCP_KEEPALIVE, c->multiple_requests ? 1L : 0L); - curl_easy_setopt(e, CURLOPT_FORBID_REUSE, c->multiple_requests ? 0L : 1L); - curl_easy_setopt(e, CURLOPT_HSTS_CTRL, (long)CURLHSTS_ENABLE); - curl_easy_setopt(e, CURLOPT_ACCEPT_ENCODING, - c->off > 0 || c->end_off > 0 ? "identity" : ""); + SETOPT(CURLOPT_FOLLOWLOCATION, 1L); + SETOPT(CURLOPT_MAXREDIRS, (long)c->max_redirects); + SETOPT(CURLOPT_HTTP_VERSION, (long)c->http_version); + SETOPT(CURLOPT_TCP_KEEPALIVE, c->multiple_requests ? 1L : 0L); + SETOPT(CURLOPT_FORBID_REUSE, c->multiple_requests ? 0L : 1L); + if (info->features & CURL_VERSION_HSTS) + SETOPT(CURLOPT_HSTS_CTRL, (long)CURLHSTS_ENABLE); + SETOPT(CURLOPT_ACCEPT_ENCODING, c->off > 0 || c->end_off > 0 ? "identity" : ""); if (c->connect_timeout > 0) - curl_easy_setopt(e, CURLOPT_CONNECTTIMEOUT_MS, - (long)c->connect_timeout * 1000); + SETOPT(CURLOPT_CONNECTTIMEOUT_MS, (long)c->connect_timeout * 1000); if (c->user_agent && c->user_agent[0]) - curl_easy_setopt(e, CURLOPT_USERAGENT, c->user_agent); + SETOPT(CURLOPT_USERAGENT, c->user_agent); if (c->http_proxy && c->http_proxy[0]) - curl_easy_setopt(e, CURLOPT_PROXY, c->http_proxy); + SETOPT(CURLOPT_PROXY, c->http_proxy); - curl_easy_setopt(e, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA); - curl_easy_setopt(e, CURLOPT_SSL_VERIFYPEER, c->tls_verify ? 1L : 0L); - curl_easy_setopt(e, CURLOPT_SSL_VERIFYHOST, c->tls_verify ? 2L : 0L); + if (info->features & CURL_VERSION_SSL) + SETOPT(CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA); + SETOPT(CURLOPT_SSL_VERIFYPEER, c->tls_verify ? 1L : 0L); + SETOPT(CURLOPT_SSL_VERIFYHOST, c->tls_verify ? 2L : 0L); if (c->ca_file) - curl_easy_setopt(e, CURLOPT_CAINFO, c->ca_file); + SETOPT(CURLOPT_CAINFO, c->ca_file); if (c->cert_file) - curl_easy_setopt(e, CURLOPT_SSLCERT, c->cert_file); + SETOPT(CURLOPT_SSLCERT, c->cert_file); if (c->key_file) - curl_easy_setopt(e, CURLOPT_SSLKEY, c->key_file); + SETOPT(CURLOPT_SSLKEY, c->key_file); - curl_easy_setopt(e, CURLOPT_COOKIEFILE, ""); + SETOPT(CURLOPT_COOKIEFILE, ""); if (c->cookies && c->cookies[0]) { char *copy = av_strdup(c->cookies); char *line, *saveptr = NULL; - if (copy) { - for (line = av_strtok(copy, "\r\n", &saveptr); line; - line = av_strtok(NULL, "\r\n", &saveptr)) { - char *sc = av_asprintf("Set-Cookie: %s", line); - if (sc) { - curl_easy_setopt(e, CURLOPT_COOKIELIST, sc); - av_free(sc); - } + if (!copy) + return AVERROR(ENOMEM); + for (line = av_strtok(copy, "\r\n", &saveptr); line && ret >= 0; + line = av_strtok(NULL, "\r\n", &saveptr)) { + char *sc = av_asprintf("Set-Cookie: %s", line); + if (!sc) { + ret = AVERROR(ENOMEM); + break; } - av_free(copy); + ret = check_setopt(c, "CURLOPT_COOKIELIST", + curl_easy_setopt(e, CURLOPT_COOKIELIST, sc)); + av_free(sc); } + av_free(copy); + if (ret < 0) + return ret; } - c->header_list = build_headers(c); + if ((ret = build_headers(c)) < 0) + return ret; if (c->header_list) - curl_easy_setopt(e, CURLOPT_HTTPHEADER, c->header_list); + SETOPT(CURLOPT_HTTPHEADER, c->header_list); /* Shoutcast v1 answers "ICY 200 OK", which curl would otherwise reject as * HTTP/0.9 before any header reaches header_callback(). */ if (c->icy_status) { - c->alias_list = curl_slist_append(NULL, "ICY 200"); - if (c->alias_list) - curl_easy_setopt(e, CURLOPT_HTTP200ALIASES, c->alias_list); + if ((ret = slist_append(&c->alias_list, "ICY 200")) < 0) + return ret; + SETOPT(CURLOPT_HTTP200ALIASES, c->alias_list); } + + return 0; } +#undef SETOPT + static void curl_cond_wait(CurlContext *c) { int64_t t = av_gettime() + CURL_WAIT_US; @@ -1385,7 +1448,9 @@ static int libcurl_open(URLContext *h, const char *url, int flags, if (ret < 0) goto fail; - setup_curl(c); + ret = setup_curl(c); + if (ret < 0) + goto fail; ret = curl_dispatch(c->loop, CMD_ADD, c, 0, 0); if (ret < 0) -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
