]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Reject HTTP redirects to different origins draft-spaghetti-sidrops-rrdp-same-origin
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 11 Mar 2024 20:22:38 +0000 (14:22 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 11 Mar 2024 20:22:38 +0000 (14:22 -0600)
Prevents malicious RRDP servers from wasting other servers' bandwidth.

Second half of draft-spaghetti-sidrops-rrdp-same-origin-00.

Thanks to Job Snijders for reporting this.

src/http/http.c
src/types/uri.c
src/types/uri.h
test/types/uri_test.c

index 0de473bbea8949382691466ccd098c68f34ad6d6..b783ba1b168beb7f2a82ee3180abc24e73b281d9 100644 (file)
@@ -131,9 +131,6 @@ http_easy_init(struct http_handler *handler, curl_off_t ims)
 
        setopt_str(result, CURLOPT_USERAGENT, config_get_http_user_agent());
 
-       setopt_long(result, CURLOPT_FOLLOWLOCATION, 1);
-       setopt_long(result, CURLOPT_MAXREDIRS, config_get_max_redirs());
-
        setopt_long(result, CURLOPT_CONNECTTIMEOUT,
            config_get_http_connect_timeout());
        setopt_long(result, CURLOPT_TIMEOUT,
@@ -197,7 +194,7 @@ curl_err_string(struct http_handler *handler, CURLcode res)
 }
 
 static int
-validate_file_size(char const *uri, struct write_callback_arg *args)
+validate_file_size(struct write_callback_arg *args)
 {
        float ratio;
 
@@ -254,72 +251,107 @@ http_fetch(char const *src, char const *dst, curl_off_t ims, bool *changed)
        struct write_callback_arg args;
        CURLcode res;
        long http_code;
+       char *redirect;
+       unsigned int r;
        int error;
 
        error = http_easy_init(&handler, ims);
        if (error)
                return error;
 
-       handler.errbuf[0] = 0;
-       setopt_str(handler.curl, CURLOPT_URL, src);
+       redirect = NULL;
+       r = 0;
 
-       args.total_bytes = 0;
-       args.error = 0;
-       args.file_name = dst;
-       args.file = NULL;
-       setopt_writedata(handler.curl, &args);
+       do {
+               handler.errbuf[0] = 0;
+               setopt_str(handler.curl, CURLOPT_URL, (redirect != NULL) ? redirect : src);
 
-       res = curl_easy_perform(handler.curl); /* write_callback() */
-       if (args.file != NULL)
-               file_close(args.file);
-       pr_val_debug("Done. Total bytes transferred: %zu", args.total_bytes);
+               args.total_bytes = 0;
+               args.error = 0;
+               args.file_name = dst;
+               args.file = NULL;
+               setopt_writedata(handler.curl, &args);
 
-       args.error = validate_file_size(src, &args);
-       if (args.error) {
-               error = args.error;
-               goto end;
-       }
+               res = curl_easy_perform(handler.curl); /* write_callback() */
+               if (args.file != NULL)
+                       file_close(args.file);
+               pr_val_debug("Done. Total bytes transferred: %zu", args.total_bytes);
 
-       args.error = get_http_response_code(&handler, &http_code, src);
-       if (args.error) {
-               error = args.error;
-               goto end;
-       }
+               args.error = validate_file_size(&args);
+               if (args.error) {
+                       error = args.error;
+                       goto end;
+               }
 
-       if (res != CURLE_OK) {
-               pr_val_err("Error requesting URL: %s. (HTTP code: %ld)",
-                   curl_err_string(&handler, res), http_code);
+               args.error = get_http_response_code(&handler, &http_code, src);
+               if (args.error) {
+                       error = args.error;
+                       goto end;
+               }
+
+               if (res != CURLE_OK) {
+                       pr_val_err("Error requesting URL: %s. (HTTP code: %ld)",
+                           curl_err_string(&handler, res), http_code);
+
+                       switch (res) {
+                       case CURLE_FILESIZE_EXCEEDED:
+                               error = -EFBIG; /* Do not retry */
+                               goto end;
+                       case CURLE_OPERATION_TIMEDOUT:
+                       case CURLE_COULDNT_RESOLVE_HOST:
+                       case CURLE_COULDNT_RESOLVE_PROXY:
+                       case CURLE_FTP_ACCEPT_TIMEOUT:
+                               error = EAGAIN; /* Retry */
+                               goto end;
+                       default:
+                               error = handle_http_response_code(http_code);
+                               goto end;
+                       }
+               }
 
-               switch (res) {
-               case CURLE_FILESIZE_EXCEEDED:
-                       error = -EFBIG; /* Do not retry */
+               if (http_code >= 400 || http_code == 204) {
+                       pr_val_err("HTTP result code: %ld", http_code);
+                       error = handle_http_response_code(http_code);
+                       goto end;
+               }
+               if (http_code == 304) {
+                       /* Write callback not called, no file to remove. */
+                       pr_val_debug("Not modified.");
+                       error = 0;
                        goto end;
-               case CURLE_OPERATION_TIMEDOUT:
-               case CURLE_COULDNT_RESOLVE_HOST:
-               case CURLE_COULDNT_RESOLVE_PROXY:
-               case CURLE_FTP_ACCEPT_TIMEOUT:
-                       error = EAGAIN; /* Retry */
+               }
+
+               if (redirect != NULL) {
+                       free(redirect);
+                       redirect = NULL;
+               }
+
+               res = curl_easy_getinfo(handler.curl, CURLINFO_REDIRECT_URL, &redirect);
+               if (res != CURLE_OK) {
+                       error = pr_op_err("curl_easy_getinfo(CURLINFO_REDIRECT_URL) returned %u.", res);
+                       redirect = NULL;
                        goto end;
-               case CURLE_TOO_MANY_REDIRECTS:
-                       error = -EINVAL;
+               }
+
+               if (redirect == NULL)
+                       break;
+               if (!str_same_origin(src, redirect)) {
+                       error = pr_val_err("%s is redirecting to %s; disallowing because of different origin.",
+                          src, redirect);
+                       redirect = NULL;
                        goto end;
-               default:
-                       error = handle_http_response_code(http_code);
+               }
+
+               r++;
+               if (r > config_get_max_redirs()) {
+                       error = pr_val_err("Too many redirects.");
+                       redirect = NULL;
                        goto end;
                }
-       }
 
-       if (http_code >= 400 || http_code == 204) {
-               pr_val_err("HTTP result code: %ld", http_code);
-               error = handle_http_response_code(http_code);
-               goto end;
-       }
-       if (http_code == 304) {
-               /* Write callback not called, no file to remove. */
-               pr_val_debug("Not modified.");
-               error = 0;
-               goto end;
-       }
+               /* The original redirect is destroyed during the next curl_easy_perform(). */
+               redirect = pstrdup(redirect);
+       } while (true);
 
        pr_val_debug("HTTP result code: %ld", http_code);
        error = 0;
@@ -329,6 +361,8 @@ http_fetch(char const *src, char const *dst, curl_off_t ims, bool *changed)
 end:   http_easy_cleanup(&handler);
        if (error)
                file_rm_f(dst);
+       if (redirect != NULL)
+               free(redirect);
        return error;
 }
 
index 4b64d8634b911ab3cc7586a30fdc679b4aa7985b..ee6854e5bdf0358bcf1ed403c7a1a3adfc127f92 100644 (file)
@@ -530,15 +530,11 @@ uri_equals(struct rpki_uri *u1, struct rpki_uri *u2)
 }
 
 bool
-uri_same_origin(struct rpki_uri *u1, struct rpki_uri *u2)
+str_same_origin(char const *g1, char const *g2)
 {
-       char const *g1, *g2;
        size_t c, slashes;
 
-       g1 = u1->global;
-       g2 = u2->global;
        slashes = 0;
-
        for (c = 0; g1[c] == g2[c]; c++) {
                switch (g1[c]) {
                case '/':
@@ -559,6 +555,12 @@ uri_same_origin(struct rpki_uri *u1, struct rpki_uri *u2)
        return false;
 }
 
+bool
+uri_same_origin(struct rpki_uri *u1, struct rpki_uri *u2)
+{
+       return str_same_origin(u1->global, u2->global);
+}
+
 /* @ext must include the period. */
 bool
 uri_has_extension(struct rpki_uri *uri, char const *ext)
index 3105757c72ca994bb3ba8c8ffa5c1d1958ad9734..8cc938243952f733df0789f76b44b410cc8d9905 100644 (file)
@@ -71,6 +71,7 @@ char const *uri_get_global(struct rpki_uri *);
 char const *uri_get_local(struct rpki_uri *);
 
 bool uri_equals(struct rpki_uri *, struct rpki_uri *);
+bool str_same_origin(char const *, char const *);
 bool uri_same_origin(struct rpki_uri *, struct rpki_uri *);
 bool uri_has_extension(struct rpki_uri *, char const *);
 bool uri_is_certificate(struct rpki_uri *);
index fcc47a5aae3b000399c3048aa0745d717be4a0e3..35dfe0180f65e94bb94b600edb5e7d97e1277d9c 100644 (file)
@@ -196,35 +196,25 @@ START_TEST(check_caged)
 }
 END_TEST
 
-static bool
-same_origin(char *g1, char *g2)
-{
-       struct rpki_uri u1 = { .type = UT_NOTIF, .references = 1 };
-       struct rpki_uri u2 = { .type = UT_TMP,   .references = 1 };
-       u1.global = g1;
-       u2.global = g2;
-       return uri_same_origin(&u1, &u2);
-}
-
 START_TEST(test_same_origin)
 {
-       ck_assert_int_eq(true,  same_origin("https://a.b.c/d/e/f",      "https://a.b.c/g/h/i"));
-       ck_assert_int_eq(false, same_origin("https://a.b.cc/d/e/f",     "https://a.b.c/g/h/i"));
-       ck_assert_int_eq(false, same_origin("https://a.b.c/d/e/f",      "https://a.b.cc/g/h/i"));
-       ck_assert_int_eq(true,  same_origin("https://a.b.c",            "https://a.b.c"));
-       ck_assert_int_eq(true,  same_origin("https://a.b.c/",           "https://a.b.c"));
-       ck_assert_int_eq(true,  same_origin("https://a.b.c",            "https://a.b.c/"));
-       ck_assert_int_eq(true,  same_origin("https://",                 "https://"));
-       ck_assert_int_eq(false, same_origin("https://",                 "https://a"));
-       ck_assert_int_eq(false, same_origin("https://a",                "https://b"));
+       ck_assert_int_eq(true,  str_same_origin("https://a.b.c/d/e/f",  "https://a.b.c/g/h/i"));
+       ck_assert_int_eq(false, str_same_origin("https://a.b.cc/d/e/f", "https://a.b.c/g/h/i"));
+       ck_assert_int_eq(false, str_same_origin("https://a.b.c/d/e/f",  "https://a.b.cc/g/h/i"));
+       ck_assert_int_eq(true,  str_same_origin("https://a.b.c",        "https://a.b.c"));
+       ck_assert_int_eq(true,  str_same_origin("https://a.b.c/",       "https://a.b.c"));
+       ck_assert_int_eq(true,  str_same_origin("https://a.b.c",        "https://a.b.c/"));
+       ck_assert_int_eq(true,  str_same_origin("https://",             "https://"));
+       ck_assert_int_eq(false, str_same_origin("https://",             "https://a"));
+       ck_assert_int_eq(false, str_same_origin("https://a",            "https://b"));
 
        /* Undefined, but manhandle the code anyway */
-       ck_assert_int_eq(false, same_origin("",                         ""));
-       ck_assert_int_eq(false, same_origin("ht",                       "ht"));
-       ck_assert_int_eq(false, same_origin("https:",                   "https:"));
-       ck_assert_int_eq(false, same_origin("https:/",                  "https:/"));
-       ck_assert_int_eq(false, same_origin("https:/a",                 "https:/a"));
-       ck_assert_int_eq(true,  same_origin("https:/a/",                "https:/a/"));
+       ck_assert_int_eq(false, str_same_origin("",                     ""));
+       ck_assert_int_eq(false, str_same_origin("ht",                   "ht"));
+       ck_assert_int_eq(false, str_same_origin("https:",               "https:"));
+       ck_assert_int_eq(false, str_same_origin("https:/",              "https:/"));
+       ck_assert_int_eq(false, str_same_origin("https:/a",             "https:/a"));
+       ck_assert_int_eq(true,  str_same_origin("https:/a/",            "https:/a/"));
 }
 END_TEST