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]

Reply via email to