]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Force RRDP files to be hosted by the same origin
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 11 Mar 2024 20:22:26 +0000 (14:22 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 11 Mar 2024 20:22:26 +0000 (14:22 -0600)
Reject RRDP snapshot and deltas if they're not hosted in the same origin
as the notification. Prevents malicious notifications from wasting other
servers' bandwidth.

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

Thanks to Job Snijders for reporting this.

src/rrdp.c
src/types/uri.c
src/types/uri.h
test/rrdp_test.c
test/types/uri_test.c

index 7f3d52dfaf24feb244dc0d07f4fabfb27161d328..f99849d09d82c1b2f4c84a9cad8bde9be668201f 100644 (file)
@@ -589,6 +589,23 @@ handle_withdraw(xmlTextReaderPtr reader, struct rpki_uri *notif)
        return error;
 }
 
+static int
+parse_notification_snapshot(xmlTextReaderPtr reader,
+    struct update_notification *notif)
+{
+       int error;
+
+       error = parse_file_metadata(reader, NULL, HR_MANDATORY, &notif->snapshot);
+       if (error)
+               return error;
+
+       if (!uri_same_origin(notif->uri, notif->snapshot.uri))
+               return pr_val_err("Notification %s and Snapshot %s are not hosted by the same origin.",
+                   uri_get_global(notif->uri), uri_get_global(notif->snapshot.uri));
+
+       return 0;
+}
+
 static int
 parse_notification_delta(xmlTextReaderPtr reader,
     struct update_notification *notif)
@@ -606,6 +623,10 @@ parse_notification_delta(xmlTextReaderPtr reader,
                return error;
        }
 
+       if (!uri_same_origin(notif->uri, delta.meta.uri))
+               return pr_val_err("Notification %s and Delta %s are not hosted by the same origin.",
+                   uri_get_global(notif->uri), uri_get_global(delta.meta.uri));
+
        notification_deltas_add(&notif->deltas, &delta);
        return 0;
 }
@@ -710,8 +731,7 @@ xml_read_notif(xmlTextReaderPtr reader, void *arg)
                if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_DELTA)) {
                        return parse_notification_delta(reader, notif);
                } else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_SNAPSHOT)) {
-                       return parse_file_metadata(reader, NULL, HR_MANDATORY,
-                           &notif->snapshot);
+                       return parse_notification_snapshot(reader, notif);
                } else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_NOTIFICATION)) {
                        /* No need to validate session ID and serial */
                        return parse_session(reader, &notif->session);
index 919eef99f0ede5c240de5005cabf40ec52408d1a..4b64d8634b911ab3cc7586a30fdc679b4aa7985b 100644 (file)
@@ -529,6 +529,36 @@ uri_equals(struct rpki_uri *u1, struct rpki_uri *u2)
        return strcmp(u1->global, u2->global) == 0;
 }
 
+bool
+uri_same_origin(struct rpki_uri *u1, struct rpki_uri *u2)
+{
+       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 '/':
+                       slashes++;
+                       if (slashes == 3)
+                               return true;
+                       break;
+               case '\0':
+                       return slashes == 2;
+               }
+       }
+
+       if (g1[c] == '\0')
+               return (slashes == 2) && g2[c] == '/';
+       if (g2[c] == '\0')
+               return (slashes == 2) && g1[c] == '/';
+
+       return false;
+}
+
 /* @ext must include the period. */
 bool
 uri_has_extension(struct rpki_uri *uri, char const *ext)
index ea770692083d208b7ef7b6a40de4ca7d0a78151a..3105757c72ca994bb3ba8c8ffa5c1d1958ad9734 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 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 b752646f2b564876de5c614ef19b5e152d057e39..f15bcf0effafa350ba640bb0f6ca67ecc5ed0ff2 100644 (file)
@@ -458,15 +458,24 @@ START_TEST(test_update_notif)
 }
 END_TEST
 
+static void
+init_uri(struct rpki_uri *uri, char *global, char *local, enum uri_type type)
+{
+       uri->global = global;
+       uri->local = local;
+       uri->type = type;
+       uri->references = 1;
+}
+
+#define init_notif_uri(u, g, l) init_uri(u, g, l, UT_NOTIF)
+
 START_TEST(test_parse_notification_ok)
 {
-       struct rpki_uri uri = { 0 };
+       struct rpki_uri uri;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       uri.local = "resources/rrdp/notif-ok.xml";
-       uri.type = UT_NOTIF;
-       uri.references = 1;
+       init_notif_uri(&uri, "https://host/notification.xml", "resources/rrdp/notif-ok.xml");
        ck_assert_int_eq(0, parse_notification(&uri, &notif));
 
        ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28", (char const *)notif.session.session_id);
@@ -495,13 +504,11 @@ END_TEST
 
 START_TEST(test_parse_notification_0deltas)
 {
-       struct rpki_uri uri = { 0 };
+       struct rpki_uri uri;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       uri.local = "resources/rrdp/notif-0deltas.xml";
-       uri.type = UT_NOTIF;
-       uri.references = 1;
+       init_notif_uri(&uri, "https://host/notification.xml", "resources/rrdp/notif-0deltas.xml");
        ck_assert_int_eq(0, parse_notification(&uri, &notif));
 
        ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28", (char const *)notif.session.session_id);
@@ -520,13 +527,11 @@ END_TEST
 
 START_TEST(test_parse_notification_large_serial)
 {
-       struct rpki_uri uri = { 0 };
+       struct rpki_uri uri;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       uri.local = "resources/rrdp/notif-large-serial.xml";
-       uri.type = UT_NOTIF;
-       uri.references = 1;
+       init_notif_uri(&uri, "https://host/notification.xml", "resources/rrdp/notif-large-serial.xml");
        ck_assert_int_eq(0, parse_notification(&uri, &notif));
 
        ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28", (char const *)notif.session.session_id);
@@ -552,13 +557,11 @@ END_TEST
 static void
 test_parse_notification_error(char *file)
 {
-       struct rpki_uri uri = { 0 };
+       struct rpki_uri uri;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       uri.local = file;
-       uri.type = UT_NOTIF;
-       uri.references = 1;
+       init_notif_uri(&uri, "https://host/notification.xml", file);
        ck_assert_int_eq(-EINVAL, parse_notification(&uri, &notif));
 
        relax_ng_cleanup();
@@ -620,13 +623,8 @@ START_TEST(test_parse_snapshot_bad_publish)
 
        ck_assert_int_eq(0, relax_ng_init());
 
-       notif_uri.global = "https://example.com/notification.xml";
-       notif_uri.local = "cache/example.com/notification.xml";
-       notif_uri.type = UT_NOTIF;
-       notif_uri.references = 1;
-
-       snapshot_uri.local = "resources/rrdp/snapshot-bad-publish.xml";
-       snapshot_uri.references = 1;
+       init_notif_uri(&notif_uri, "https://example.com/notification.xml", "cache/example.com/notification.xml");
+       init_uri(&snapshot_uri, "https://example.com/snapshot.xml", "resources/rrdp/snapshot-bad-publish.xml", UT_TMP);
 
        notif.session.session_id = "9df4b597-af9e-4dca-bdda-719cce2c4e28";
        notif.session.serial.str = "2";
index 46585074f1ac2a98e9d8384716f535ae390214a1..fcc47a5aae3b000399c3048aa0745d717be4a0e3 100644 (file)
@@ -196,6 +196,38 @@ 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"));
+
+       /* 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/"));
+}
+END_TEST
+
 static Suite *address_load_suite(void)
 {
        Suite *suite;
@@ -205,6 +237,7 @@ static Suite *address_load_suite(void)
        tcase_add_test(core, test_constructor);
        tcase_add_test(core, check_validate_current_directory);
        tcase_add_test(core, check_caged);
+       tcase_add_test(core, test_same_origin);
 
        suite = suite_create("Encoding checking");
        suite_add_tcase(suite, core);