From: Alberto Leiva Popper Date: Mon, 11 Mar 2024 20:22:38 +0000 (-0600) Subject: Reject HTTP redirects to different origins X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f29f9e6421feb9f57e74f408af797b38013b84be;p=thirdparty%2FFORT-validator.git Reject HTTP redirects to different origins 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. --- diff --git a/src/http/http.c b/src/http/http.c index 0de473bb..b783ba1b 100644 --- a/src/http/http.c +++ b/src/http/http.c @@ -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; } diff --git a/src/types/uri.c b/src/types/uri.c index 4b64d863..ee6854e5 100644 --- a/src/types/uri.c +++ b/src/types/uri.c @@ -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) diff --git a/src/types/uri.h b/src/types/uri.h index 3105757c..8cc93824 100644 --- a/src/types/uri.h +++ b/src/types/uri.h @@ -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 *); diff --git a/test/types/uri_test.c b/test/types/uri_test.c index fcc47a5a..35dfe018 100644 --- a/test/types/uri_test.c +++ b/test/types/uri_test.c @@ -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