CVE-2021-43114 (published 2021-11-09T13:15Z)

FORT Validator versions prior to 1.5.2 will crash if an RPKI CA publishes an X.509 EE certificate. This will lead to RTR clients such as BGP routers to lose access to the RPKI VRP data set, effectively disabling Route Origin Validation.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/NICMx/FORT-validator
commit_details: repo_url: https://github.com/NICMx/FORT-validator
author: Alberto Leiva Popper
author_date: 2021-10-19 14:47:01-05:00
msg: Protocolary updates for release 1.5.2
repository: repo_url: https://github.com/NICMx/FORT-validator
repo_name: NICMx/FORT-validator
description: RPKI cache validator
repo_language: C
owner: NICMx
file_changes: filename: _config.yml
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,7 @@ defaults:
       layout: "default"
       image: "/img/logo_validador_og.png"
 
-fort-latest-version: 1.5.1
+fort-latest-version: 1.5.2
 plugins:
   - jekyll-seo-tag
   - jekyll-sitemap

programming_language: Markdown
filename: intro-fort.md
change_type: ModificationType.MODIFY
diff: @@ -32,7 +32,7 @@ Further information can be found in the subsections below.
 | [7318](https://tools.ietf.org/html/rfc7318) (Policy Qualifiers)            | 100%        |
 | [7935](https://tools.ietf.org/html/rfc7935) (RPKI algorithms)              | 100%        |
 | [8182](https://tools.ietf.org/html/rfc8182) (RRDP)                         | 100%        |
-| [8209](https://tools.ietf.org/html/rfc8209) (BGPSec Certificates)          | 100%        |
+| [8209](https://tools.ietf.org/html/rfc8209) (BGPSec Certificates)          | 0% (This code was [disabled](https://github.com/NICMx/FORT-validator/issues/58#issuecomment-941977925) in version 1.5.2) |
 | [8210](https://tools.ietf.org/html/rfc8210) (RTR Version 1)                | 100%        |
 | [8360](https://tools.ietf.org/html/rfc8360) (Validation Reconsidered)      | 100%        |
 | [8416](https://tools.ietf.org/html/rfc8416) (SLURM)                        | 100%        |

programming_language: Markdown
filename: usage.md
change_type: ModificationType.MODIFY
diff: @@ -762,18 +762,6 @@ The value specified is utilized in libcurl's option [CURLOPT_CAPATH](https://cur
 - **Type:** String (Path to file)
 - **Availability:** `argv` and JSON
 
-> Note: The paragraphs below apply to [Fort 1.5.0](https://github.com/NICMx/FORT-validator/releases/tag/v1.5.0).
-
-File where the ROAs (found during each validation run) will be stored (in CSV format).
-
-If the file already exists, it will be overwritten. If it doesn't exist, it will be created. To print to standard output, use a hyphen (`-`). If the RTR server is [enabled](#--mode), then the ROAs will be printed every [`--server.interval.validation`](#--serverintervalvalidation) seconds.
-
-Each line of the result is printed in the following order: _AS, Prefix, Max prefix length_. The first line contains the column names.
-
-If `--output.roa` is omitted, the ROAs are not printed.
-
-> Note: The paragraphs below apply to [Fort master](https://github.com/NICMx/FORT-validator).
-
 File where the ROAs (found during each validation run) will be stored. See [`--output.format`](#--outputformat).
 
 If the file already exists, it will be overwritten. If it doesn't exist, it will be created. To print to standard output, use a hyphen (`-`). If the RTR server is [enabled](#--mode), then the ROAs will be printed every [`--server.interval.validation`](#--serverintervalvalidation) secs.
@@ -806,19 +794,7 @@ If `--output.roa` is omitted, the ROAs are not printed.
 - **Type:** String (Path to file)
 - **Availability:** `argv` and JSON
 
-> Note: The paragraphs below apply to [Fort 1.5.0](https://github.com/NICMx/FORT-validator/releases/tag/v1.5.0).
-
-File where the BGPsec Router Keys (found during each validation run) will be stored (in CSV format).
-
-Since most of the data (Subject Key Identifier and Subject Public Key Info) is binary, it is base64url-encoded, without trailing pads.
-
-If the file already exists, it will be overwritten. If it doesn't exist, it will be created. To print to standard output console, use a hyphen (`-`). If the RTR server is [enabled](#--mode), the BGPsec Router Keys will be printed every [`--server.interval.validation`](#--serverintervalvalidation) seconds.
-
-Each line of the result is printed in the following order: _AS, Subject Key Identifier, Subject Public Key Info_. The first line contains the column names.
-
-If `--output.bgpsec` is ommited, then the BGPsec Router Keys are not printed.
-
-> Note: The paragraphs below apply to [Fort master](https://github.com/NICMx/FORT-validator).
+> ![Warning!](img/warn.svg) BGPsec certificate validation has been disabled in version 1.5.2 because of [this bug](https://github.com/NICMx/FORT-validator/issues/58). It will be restored in version 1.5.3.
 
 File where the BGPsec Router Keys (found during each validation run) will be stored. See [`--output.format`](#--outputformat).
 
@@ -855,8 +831,6 @@ If `--output.bgpsec` is ommited, then the BGPsec Router Keys are not printed.
 - **Availability:** `argv` and JSON
 - **Default:** `csv`
 
-> Note: This flag only exists in [Fort master](https://github.com/NICMx/FORT-validator).
-
 Output format for [`--output.roa`](#--outputroa) and [`--output.bgpsec`](#--outputbgpsec).
 
 ### `--asn1-decode-max-stack`

programming_language: Markdown
filename: fort.8
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
-.TH fort 8 "2021-08-05" "v1.5.1" "FORT validator"
+.TH fort 8 "2021-10-19" "v1.5.2" "FORT validator"
 
 .SH NAME
 fort \- RPKI validator and RTR server
@@ -488,6 +488,16 @@ maximum allowed value \fI172800\fR. It must be larger than
 .RE
 .P
 
+.B \-\-server.deltas.lifetime=\fIUNSIGNED_INTEGER\fR
+.RS 4
+When routers first connect to Fort, they request a snapshot of the validation results. (ROAs and Router Keys.) Because they need to keep their validated objects updated, and snapshots tend to be relatively large amounts of information, they request deltas afterwards over configurable intervals. ("Deltas" being the differences between snapshots.)
+.P
+During each validation cycle, Fort generates a new snapshot, as well as the deltas needed to build the new snapshot from the previous one. These are all stored in RAM. \fI--server.deltas.lifetime\fR is the number of iterations a set of deltas will be kept before being deallocated. (Recall that every iteration lasts \fI--server.interval.validation\fR seconds, plus however long the validation takes.)
+.P
+If a router lags behind, to the point Fort has already deleted the deltas it needs to update the router’s snapshot, Fort will have to fall back to fetch the entire latest snapshot instead.
+.RE
+.P
+
 .B \-\-log.enabled=\fItrue\fR|\fIfalse\fR
 .RS 4
 Enables the operation logs.

programming_language: Perl
filename: certificate.c
change_type: ModificationType.MODIFY
diff: @@ -1853,6 +1853,7 @@ get_certificate_type(X509 *cert, bool is_ta, enum cert_type *result)
 		return 0;
 	}
 
+	*result = EE; /* Shuts up nonsense gcc 8.3 warning */
 	return pr_val_err("Certificate is not TA, CA nor BGPsec. Ignoring...");
 }
 

programming_language: C
repo_url: https://github.com/NICMx/FORT-validator
commit_details: repo_url: https://github.com/NICMx/FORT-validator
author: Job Snijders
author_date: 2021-08-30 17:07:15+00:00
msg: Make FORT compile on OpenBSD
repository: repo_url: https://github.com/NICMx/FORT-validator
repo_name: NICMx/FORT-validator
description: RPKI cache validator
repo_language: C
owner: NICMx
file_changes: filename: autogen.sh
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # Hello.
 # Run this file to generate the configure script.

programming_language: Shell
filename: log.c
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 #include <execinfo.h>
 #include <openssl/bio.h>
 #include <openssl/err.h>
+#include <pthread.h>
 #include <signal.h>
 #include <syslog.h>
 #include <time.h>

programming_language: C
repo_url: https://github.com/NICMx/FORT-validator
commit_details: repo_url: https://github.com/NICMx/FORT-validator
author: Alberto Leiva Popper
author_date: 2021-09-03 23:58:01-05:00
msg: RRDP Notification: Optimize delta parse

It was allocating the deltas array twice, for seemingly no reason.
Also, the array slots were pointers, and the two arrays pointed to
different instances of the same objects. For seemingly no reason.

Now there's only one array, and it stores the objects directly.

Also adds relevant unit tests.
repository: repo_url: https://github.com/NICMx/FORT-validator
repo_name: NICMx/FORT-validator
description: RPKI cache validator
repo_language: C
owner: NICMx
file_changes: filename: rrdp_objects.c
change_type: ModificationType.MODIFY
diff: @@ -5,26 +5,7 @@
 #include <string.h>
 #include "log.h"
 
-/*
- * List of deltas inside an update notification file.
- *
- * The structure functions are extended and will have the following meaning:
- *   - capacity : is the size of the array, must be set before using the array
- *                and can't be modified.
- *   - len      : number of elements set in the array.
- *
- * This struct is a diff version of array_list, utilized to store only the
- * amount of deltas that may be needed and validate that an update notification
- * file has a contiguous set of deltas.
- */
-struct deltas_head {
-	/** Unidimensional array. Initialized lazily. */
-	struct delta_head **array;
-	/** Number of elements in @array. */
-	size_t len;
-	/** Actual allocated slots in @array. */
-	size_t capacity;
-};
+DEFINE_ARRAY_LIST_FUNCTIONS(deltas_head, struct delta_head, )
 
 void
 global_data_init(struct global_data *data)
@@ -41,9 +22,9 @@ global_data_cleanup(struct global_data *data)
 void
 doc_data_init(struct doc_data *data)
 {
+	data->uri = NULL;
 	data->hash = NULL;
 	data->hash_len = 0;
-	data->uri = NULL;
 }
 
 void
@@ -53,157 +34,6 @@ doc_data_cleanup(struct doc_data *data)
 	free(data->uri);
 }
 
-int
-delta_head_create(struct delta_head **result)
-{
-	struct delta_head *tmp;
-
-	tmp = malloc(sizeof(struct delta_head));
-	if (tmp == NULL)
-		return pr_enomem();
-
-	doc_data_init(&tmp->doc_data);
-
-	*result = tmp;
-	return 0;
-}
-
-void
-delta_head_destroy(struct delta_head *delta_head)
-{
-	if (delta_head) {
-		doc_data_cleanup(&delta_head->doc_data);
-		free(delta_head);
-	}
-}
-
-static void
-deltas_head_init(struct deltas_head *list)
-{
-	list->array = NULL;
-	list->len = 0;
-	list->capacity = 0;
-}
-
-static void
-deltas_head_cleanup(struct deltas_head *list)
-{
-	size_t i;
-
-	for (i = 0; i < list->capacity; i++)
-		delta_head_destroy(list->array[i]);
-	if (list->array)
-		free(list->array);
-}
-
-static int
-deltas_head_create(struct deltas_head **deltas)
-{
-	struct deltas_head *tmp;
-
-	tmp = malloc(sizeof(struct deltas_head));
-	if (tmp == NULL)
-		return pr_enomem();
-
-	deltas_head_init(tmp);
-
-	*deltas = tmp;
-	return 0;
-}
-
-static void
-deltas_head_destroy(struct deltas_head *deltas)
-{
-	deltas_head_cleanup(deltas);
-	free(deltas);
-}
-
-int
-deltas_head_set_size(struct deltas_head *deltas, size_t capacity)
-{
-	size_t i;
-
-	if (deltas->array != NULL)
-		pr_crit("Size of this list can't be modified");
-
-	deltas->capacity = capacity;
-	if (capacity == 0)
-		return 0; /* Ok, list can have 0 elements */
-
-	deltas->array = malloc(deltas->capacity
-	    * sizeof(struct delta_head *));
-	if (deltas->array == NULL)
-		return pr_enomem();
-
-	/* Point all elements to NULL */
-	for (i = 0; i < deltas->capacity; i++)
-		deltas->array[i] = NULL;
-
-	return 0;
-}
-
-/*
- * A new delta_head will be allocated at its corresponding position inside
- * @deltas (also its URI and HASH will be allocated). The position is calculated
- * using the difference between @max_serial and @serial.
- *
- * The following errors can be returned due to a wrong @position:
- *   -EEXIST: There's already an element at @position.
- *   -EINVAL: @position can't be inside @deltas list, meaning that such element
- *            isn't part of a contiguous list.
- *
- * Don't forget to call deltas_head_set_size() before this!!
- */
-int
-deltas_head_add(struct deltas_head *deltas, unsigned long max_serial,
-    unsigned long serial, char *uri, unsigned char *hash, size_t hash_len)
-{
-	struct delta_head *elem;
-	size_t position;
-	int error;
-
-	position = deltas->capacity - 1 - (max_serial - serial);
-	if (position < 0 || position > deltas->capacity - 1)
-		return -EINVAL;
-
-	if (deltas->array[position] != NULL)
-		return -EEXIST;
-
-	elem = NULL;
-	error = delta_head_create(&elem);
-	if (error)
-		return error;
-
-	elem->serial = serial;
-
-	elem->doc_data.uri = strdup(uri);
-	if (elem->doc_data.uri == NULL) {
-		free(elem);
-		return pr_enomem();
-	}
-
-	elem->doc_data.hash_len = hash_len;
-	elem->doc_data.hash = malloc(hash_len);
-	if (elem->doc_data.hash == NULL) {
-		free(elem->doc_data.uri);
-		free(elem);
-		return pr_enomem();
-	}
-	memcpy(elem->doc_data.hash, hash, hash_len);
-
-	deltas->array[position] = elem;
-	deltas->len++;
-
-	return 0;
-}
-
-/* Are all expected values set? */
-bool
-deltas_head_values_set(struct deltas_head *deltas)
-{
-	return deltas->len == deltas->capacity;
-}
-
 /* Do the @cb to the delta head elements from @from_serial to @max_serial */
 int
 deltas_head_for_each(struct deltas_head *deltas, unsigned long max_serial,
@@ -223,7 +53,7 @@ deltas_head_for_each(struct deltas_head *deltas, unsigned long max_serial,
 	    max_serial);
 	from = deltas->capacity - (max_serial - from_serial);
 	for (index = from; index < deltas->capacity; index++) {
-		error = cb(deltas->array[index], arg);
+		error = cb(&deltas->array[index], arg);
 		if (error)
 			return error;
 	}
@@ -231,39 +61,91 @@ deltas_head_for_each(struct deltas_head *deltas, unsigned long max_serial,
 	return 0;
 }
 
+static int
+swap_until_sorted(struct delta_head *deltas, unsigned int i,
+    unsigned long min, unsigned long max)
+{
+	unsigned int target_slot;
+	struct delta_head tmp;
+
+	while (true) {
+		if (deltas[i].serial < min || max < deltas[i].serial) {
+			return pr_val_err("Deltas: Serial '%lu' is out of bounds. (min:%lu, max:%lu)",
+			    deltas[i].serial, min, max);
+		}
+
+		target_slot = deltas[i].serial - min;
+		if (i == target_slot)
+			return 0;
+		if (deltas[target_slot].serial == deltas[i].serial) {
+			return pr_val_err("Deltas: Serial '%lu' is not unique.",
+			    deltas[i].serial);
+		}
+
+		/* Simple swap */
+		tmp = deltas[target_slot];
+		deltas[target_slot] = deltas[i];
+		deltas[i] = tmp;
+	}
+}
+
 int
-update_notification_create(struct update_notification **file)
+deltas_head_sort(struct deltas_head *deltas, unsigned long max_serial)
 {
-	struct update_notification *tmp;
-	struct deltas_head *list;
+	unsigned long min_serial;
+	struct delta_head *cursor;
+	array_index i;
 	int error;
 
-	tmp = malloc(sizeof(struct update_notification));
-	if (tmp == NULL)
-		return pr_enomem();
+	if (max_serial + 1 < deltas->len)
+		return pr_val_err("Deltas: Too many deltas (%zu) for serial %lu. (Negative serials not implemented.)",
+		    deltas->len, max_serial);
 
-	list = NULL;
-	error = deltas_head_create(&list);
-	if (error) {
-		free(tmp);
-		return error;
-	}
-	tmp->deltas_list = list;
-	tmp->uri = NULL;
+	min_serial = max_serial + 1 - deltas->len;
 
-	global_data_init(&tmp->global_data);
-	doc_data_init(&tmp->snapshot);
+	ARRAYLIST_FOREACH(deltas, cursor, i) {
+		error = swap_until_sorted(deltas->array, i, min_serial,
+		    max_serial);
+		if (error)
+			return error;
+	}
 
-	*file = tmp;
 	return 0;
 }
 
+struct update_notification *
+update_notification_create(char const *uri)
+{
+	struct update_notification *result;
+
+	result = malloc(sizeof(struct update_notification));
+	if (result == NULL)
+		return NULL;
+
+	global_data_init(&result->global_data);
+	doc_data_init(&result->snapshot);
+	deltas_head_init(&result->deltas_list);
+	result->uri = strdup(uri);
+	if (result->uri == NULL) {
+		free(result);
+		return NULL;
+	}
+
+	return result;
+}
+
+static void
+delta_head_destroy(struct delta_head *delta)
+{
+	doc_data_cleanup(&delta->doc_data);
+}
+
 void
 update_notification_destroy(struct update_notification *file)
 {
 	doc_data_cleanup(&file->snapshot);
 	global_data_cleanup(&file->global_data);
-	deltas_head_destroy(file->deltas_list);
+	deltas_head_cleanup(&file->deltas_list, delta_head_destroy);
 	free(file->uri);
 	free(file);
 }

programming_language: C
filename: rrdp_objects.h
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 
 #include <stddef.h>
 #include <stdbool.h>
+#include "data_structure/array_list.h"
 
 /* Possible results for an RRDP URI comparison */
 typedef enum {
@@ -62,18 +63,23 @@ struct snapshot {
 
 /* Delta element located at an update notification file */
 struct delta_head {
+	/*
+	 * TODO this is not an RFC 1982 serial. It's supposed to be unbounded,
+	 * so we should probably handle it as a string.
+	 */
 	unsigned long serial;
 	struct doc_data doc_data;
 };
 
 /* List of deltas inside an update notification file */
-struct deltas_head;
+DEFINE_ARRAY_LIST_STRUCT(deltas_head, struct delta_head);
+DECLARE_ARRAY_LIST_FUNCTIONS(deltas_head, struct delta_head)
 
 /* Update notification file content and location URI */
 struct update_notification {
 	struct global_data global_data;
 	struct doc_data snapshot;
-	struct deltas_head *deltas_list;
+	struct deltas_head deltas_list;
 	char *uri;
 };
 
@@ -83,20 +89,13 @@ void global_data_cleanup(struct global_data *);
 void doc_data_init(struct doc_data *);
 void doc_data_cleanup(struct doc_data *);
 
-int update_notification_create(struct update_notification **);
+struct update_notification *update_notification_create(char const *);
 void update_notification_destroy(struct update_notification *);
 
-int delta_head_create(struct delta_head **);
-void delta_head_destroy(struct delta_head *);
-
 typedef int (*delta_head_cb)(struct delta_head *, void *);
 int deltas_head_for_each(struct deltas_head *, unsigned long, unsigned long,
     delta_head_cb, void *);
-int deltas_head_add(struct deltas_head *, unsigned long, unsigned long, char *,
-    unsigned char *, size_t);
-
-int deltas_head_set_size(struct deltas_head *, size_t);
-bool deltas_head_values_set(struct deltas_head *);
+int deltas_head_sort(struct deltas_head *, unsigned long);
 
 int snapshot_create(struct snapshot **);
 void snapshot_destroy(struct snapshot *);

programming_language: C
filename: rrdp_parser.c
change_type: ModificationType.MODIFY
diff: @@ -36,18 +36,6 @@
 #define RRDP_ATTR_URI		"uri"
 #define RRDP_ATTR_HASH		"hash"
 
-/* Array list to get deltas from notification file */
-DEFINE_ARRAY_LIST_STRUCT(deltas_parsed, struct delta_head *);
-DEFINE_ARRAY_LIST_FUNCTIONS(deltas_parsed, struct delta_head *, static)
-
-/* Context while reading an update notification */
-struct rdr_notification_ctx {
-	/* Data being parsed */
-	struct update_notification *notification;
-	/* Unordered list of deltas */
-	struct deltas_parsed deltas;
-};
-
 /* Context while reading a snapshot */
 struct rdr_snapshot_ctx {
 	/* Data being parsed */
@@ -258,15 +246,17 @@ parse_string(xmlTextReaderPtr reader, char const *attr, char **result)
 	xmlChar *xml_value;
 	char *tmp;
 
-	if (attr == NULL)
+	if (attr == NULL) {
 		xml_value = xmlTextReaderValue(reader);
-	else
+		if (xml_value == NULL)
+			return pr_val_err("RRDP file: Couldn't find string content from '%s'",
+			    xmlTextReaderConstLocalName(reader));
+	} else {
 		xml_value = xmlTextReaderGetAttribute(reader, BAD_CAST attr);
-
-	if (xml_value == NULL)
-		return pr_val_err("RRDP file: Couldn't find %s from '%s'",
-		    (attr == NULL ? "string content" : "xml attribute"),
-		    xmlTextReaderConstLocalName(reader));
+		if (xml_value == NULL)
+			return pr_val_err("RRDP file: Couldn't find xml attribute '%s' from tag '%s'",
+			    attr, xmlTextReaderConstLocalName(reader));
+	}
 
 	tmp = malloc(xmlStrlen(xml_value) + 1);
 	if (tmp == NULL) {
@@ -698,147 +688,54 @@ parse_withdraw_elem(xmlTextReaderPtr reader, struct visited_uris *visited_uris)
 	return 0;
 }
 
-static int
-rdr_notification_ctx_init(struct rdr_notification_ctx *ctx)
-{
-	deltas_parsed_init(&ctx->deltas);
-	return 0;
-}
-
-static void
-__delta_head_destroy(struct delta_head **delta_head)
-{
-	delta_head_destroy(*delta_head);
-}
-
-static void
-rdr_notification_ctx_cleanup(struct rdr_notification_ctx *ctx)
-{
-	if (ctx->deltas.array != NULL)
-		deltas_parsed_cleanup(&ctx->deltas, __delta_head_destroy);
-}
-
 static int
 parse_notification_delta(xmlTextReaderPtr reader,
-    struct rdr_notification_ctx *ctx)
+    struct update_notification *update)
 {
-	struct delta_head *tmp;
-	unsigned long serial;
+	struct delta_head delta;
 	int error;
 
-	error = delta_head_create(&tmp);
+	error = parse_long(reader, RRDP_ATTR_SERIAL, &delta.serial);
 	if (error)
 		return error;
-
-	error = parse_long(reader, RRDP_ATTR_SERIAL, &serial);
+	error = parse_doc_data(reader, true, true, &delta.doc_data);
 	if (error)
-		goto delta_destroy;
-	tmp->serial = serial;
-
-	error = parse_doc_data(reader, true, true, &tmp->doc_data);
-	if (error)
-		goto delta_destroy;
+		return error;
 
-	error = deltas_parsed_add(&ctx->deltas, &tmp);
+	error = deltas_head_add(&update->deltas_list, &delta);
 	if (error)
-		goto delta_destroy;
+		doc_data_cleanup(&delta.doc_data);
 
-	return 0;
-delta_destroy:
-	delta_head_destroy(tmp);
 	return error;
 }
 
-static int
-order_notification_deltas(struct rdr_notification_ctx *ctx)
-{
-	struct delta_head **ptr;
-	array_index i;
-	int error;
-
-	error = deltas_head_set_size(ctx->notification->deltas_list,
-	    ctx->deltas.len);
-	if (error)
-		return error;
-
-	ARRAYLIST_FOREACH(&ctx->deltas, ptr, i) {
-		error = deltas_head_add(ctx->notification->deltas_list,
-		    ctx->notification->global_data.serial,
-		    (*ptr)->serial,
-		    (*ptr)->doc_data.uri,
-		    (*ptr)->doc_data.hash,
-		    (*ptr)->doc_data.hash_len);
-
-		if (!error)
-			continue;
-
-		if (error == -EINVAL)
-			return pr_val_err("Serial '%lu' at delta elements isn't part of a contiguous list of serials.",
-			    (*ptr)->serial);
-
-		if (error == -EEXIST)
-			return pr_val_err("Duplicated serial '%lu' at delta elements.",
-			    (*ptr)->serial);
-
-		return error;
-	}
-
-	/*
-	 * "If delta elements are included, they MUST form a contiguous
-	 * sequence of serial numbers starting at a revision determined by
-	 * the Repository Server, up to the serial number mentioned in the
-	 * notification element."
-	 *
-	 * If all expected elements are set, everything is ok.
-	 */
-	if (!deltas_head_values_set(ctx->notification->deltas_list))
-		return pr_val_err("Deltas listed don't have a contiguous sequence of serial numbers");
-
-	return 0;
-}
-
 static int
 xml_read_notification(xmlTextReaderPtr reader, void *arg)
 {
-	struct rdr_notification_ctx *ctx = arg;
-	xmlReaderTypes type;
+	struct update_notification *update = arg;
 	xmlChar const *name;
-	int error;
 
-	error = 0;
 	name = xmlTextReaderConstLocalName(reader);
-	type = xmlTextReaderNodeType(reader);
-	switch (type) {
+	switch (xmlTextReaderNodeType(reader)) {
 	case XML_READER_TYPE_ELEMENT:
 		if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_DELTA)) {
-			error = parse_notification_delta(reader, ctx);
+			return parse_notification_delta(reader, update);
 		} else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_SNAPSHOT)) {
-			error = parse_doc_data(reader, true, true,
-			    &ctx->notification->snapshot);
+			return parse_doc_data(reader, true, true,
+			    &update->snapshot);
 		} else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_NOTIFICATION)) {
 			/* No need to validate session ID and serial */
-			error = parse_global_data(reader,
-			    &ctx->notification->global_data, NULL, 0);
-			/* Init context for deltas and snapshot */
-			rdr_notification_ctx_init(ctx);
-		} else {
-			return pr_val_err("Unexpected '%s' element", name);
+			return parse_global_data(reader,
+			    &update->global_data, NULL, 0);
 		}
-		break;
+
+		return pr_val_err("Unexpected '%s' element", name);
+
 	case XML_READER_TYPE_END_ELEMENT:
-		if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_NOTIFICATION)) {
-			error = order_notification_deltas(ctx);
-			rdr_notification_ctx_cleanup(ctx);
-			return error; /* Error 0 is ok */
-		}
+		if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_NOTIFICATION))
+			return deltas_head_sort(&update->deltas_list,
+			    update->global_data.serial);
 		break;
-	default:
-		return 0;
-	}
-
-	if (error) {
-		rdr_notification_ctx_cleanup(ctx);
-		return error;
 	}
 
 	return 0;
@@ -847,30 +744,21 @@ xml_read_notification(xmlTextReaderPtr reader, void *arg)
 static int
 parse_notification(struct rpki_uri *uri, struct update_notification **file)
 {
-	struct rdr_notification_ctx ctx;
-	struct update_notification *tmp;
-	char *dup;
+	struct update_notification *result;
 	int error;
 
-	dup = strdup(uri_get_global(uri));
-	if (dup == NULL)
+	result = update_notification_create(uri_get_global(uri));
+	if (result == NULL)
 		return pr_enomem();
 
-	error = update_notification_create(&tmp);
-	if (error)
-		return error;
-
-	tmp->uri = dup;
-
-	ctx.notification = tmp;
 	error = relax_ng_parse(uri_get_local(uri), xml_read_notification,
-	    &ctx);
+	    result);
 	if (error) {
-		update_notification_destroy(tmp);
+		update_notification_destroy(result);
 		return error;
 	}
 
-	*file = tmp;
+	*file = result;
 	return 0;
 }
 
@@ -999,8 +887,9 @@ parse_delta(struct rpki_uri *uri, struct delta_head *parents_data,
 	ctx.expected_serial = parents_data->serial;
 	error = relax_ng_parse(uri_get_local(uri), xml_read_delta, &ctx);
 
-	/* Error 0 is ok */
 	delta_destroy(delta);
+	/* Error 0 is ok */
+
 pop_fnstack:
 	fnstack_pop();
 	return error;
@@ -1141,6 +1030,6 @@ rrdp_process_deltas(struct update_notification *parent,
 	args.visited_uris = visited_uris;
 	args.log_operation = log_operation;
 
-	return deltas_head_for_each(parent->deltas_list,
+	return deltas_head_for_each(&parent->deltas_list,
 	    parent->global_data.serial, cur_serial, process_delta, &args);
 }

programming_language: C
filename: relax_ng.c
change_type: ModificationType.MODIFY
diff: @@ -109,7 +109,7 @@ relax_ng_parse(const char *path, xml_read_cb cb, void *arg)
 	xmlRelaxNGSetValidErrors(rngvalidctx, relax_ng_log_err,
 	    relax_ng_log_warn, NULL);
 
-	error = xmlTextReaderRelaxNGValidateCtxt(reader, rngvalidctx, 1);
+	error = xmlTextReaderRelaxNGValidateCtxt(reader, rngvalidctx, 0);
 	if (error) {
 		error = pr_val_err("Invalid XML document");
 		goto free_valid_ctx;

programming_language: C
filename: Makefile.am
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@ check_PROGRAMS += deltas_array.test
 check_PROGRAMS += db_table.test
 check_PROGRAMS += line_file.test
 check_PROGRAMS += pdu_handler.test
+check_PROGRAMS += rrdp_objects.test
 check_PROGRAMS += rsync.test
 check_PROGRAMS += serial.test
 check_PROGRAMS += tal.test
@@ -50,6 +51,9 @@ line_file_test_LDADD = ${MY_LDADD}
 pdu_handler_test_SOURCES = rtr/pdu_handler_test.c
 pdu_handler_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
 
+rrdp_objects_test_SOURCES = rrdp_objects_test.c
+rrdp_objects_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} ${XML2_LIBS}
+
 rsync_test_SOURCES = rsync_test.c
 rsync_test_LDADD = ${MY_LDADD}
 

programming_language: Shell
filename: rrdp_objects_test.c
change_type: ModificationType.ADD
diff: @@ -0,0 +1,117 @@
+#include <check.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "impersonator.c"
+#include "log.c"
+#include "rrdp/rrdp_objects.c"
+
+#define END 0xFFFF
+
+static void
+add_serials(struct deltas_head *deltas, ...)
+{
+	struct delta_head delta;
+	va_list vl;
+
+	doc_data_init(&delta.doc_data);
+
+	va_start(vl, deltas);
+	while ((delta.serial = va_arg(vl, unsigned long)) != END)
+		ck_assert_int_eq(0, deltas_head_add(deltas, &delta));
+	va_end(vl);
+}
+
+static void
+validate_serials(struct deltas_head *deltas, ...)
+{
+	unsigned long serial;
+	unsigned int i;
+	va_list vl;
+
+	va_start(vl, deltas);
+
+	i = 0;
+	while ((serial = va_arg(vl, unsigned long)) != END) {
+		ck_assert_uint_eq(serial, deltas->array[i].serial);
+		i++;
+	}
+
+	va_end(vl);
+}
+
+START_TEST(test_deltas_head_sort)
+{
+	struct deltas_head deltas;
+
+	deltas_head_init(&deltas);
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 0));
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 1));
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 2));
+
+	add_serials(&deltas, 0, END);
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 0));
+	validate_serials(&deltas, 0, END);
+
+	ck_assert_int_eq(-EINVAL, deltas_head_sort(&deltas, 2));
+	ck_assert_int_eq(-EINVAL, deltas_head_sort(&deltas, 1));
+
+	add_serials(&deltas, 1, 2, 3, END);
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 3));
+	validate_serials(&deltas, 0, 1, 2, 3, END);
+
+	ck_assert_int_eq(-EINVAL, deltas_head_sort(&deltas, 4));
+	ck_assert_int_eq(-EINVAL, deltas_head_sort(&deltas, 2));
+
+	deltas_head_cleanup(&deltas, NULL);
+	deltas_head_init(&deltas);
+
+	add_serials(&deltas, 3, 0, 1, 2, END);
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 3));
+	validate_serials(&deltas, 0, 1, 2, 3, END);
+
+	deltas_head_cleanup(&deltas, NULL);
+	deltas_head_init(&deltas);
+
+	add_serials(&deltas, 4, 3, 2, 1, 0, END);
+	ck_assert_int_eq(0, deltas_head_sort(&deltas, 4));
+	validate_serials(&deltas, 0, 1, 2, 3, 4, END);
+
+	ck_assert_int_eq(-EINVAL, deltas_head_sort(&deltas, 5));
+	ck_assert_int_eq(-EINVAL, deltas_head_sort(&deltas, 3));
+
+	deltas_head_cleanup(&deltas, NULL);
+}
+END_TEST
+
+Suite *xml_load_suite(void)
+{
+	Suite *suite;
+	TCase *validate;
+
+	validate = tcase_create("Validate");
+	tcase_add_test(validate, test_deltas_head_sort);
+
+	suite = suite_create("xml_test()");
+	suite_add_tcase(suite, validate);
+
+	return suite;
+}
+
+int main(void)
+{
+	Suite *suite;
+	SRunner *runner;
+	int tests_failed;
+
+	log_setup(true);
+
+	suite = xml_load_suite();
+
+	runner = srunner_create(suite);
+	srunner_run_all(runner, CK_NORMAL);
+	tests_failed = srunner_ntests_failed(runner);
+	srunner_free(runner);
+
+	return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

programming_language: C
repo_url: https://github.com/NICMx/FORT-validator
commit_details: repo_url: https://github.com/NICMx/FORT-validator
author: Alberto Leiva Popper
author_date: 2021-09-08 12:40:26-05:00
msg: Certificates: Fuse meta and level stacks

These stacks always had the same size, and their corresponding elements
always referred to the same certificate.

This was pending work from #55, which I think is now properly solved.

Also refactors x509stack_push(); was messy. Patched an unlikely memory
leak in the chaos.
repository: repo_url: https://github.com/NICMx/FORT-validator
repo_name: NICMx/FORT-validator
description: RPKI cache validator
repo_language: C
owner: NICMx
file_changes: filename: cert_stack.c
change_type: ModificationType.MODIFY
diff: @@ -56,24 +56,19 @@ struct metadata_node {
 	struct serial_numbers serials;
 	struct subjects subjects;
 
+	/*
+	 * Certificate repository "level". This aims to identify if the
+	 * certificate is located at a distinct server than its father (common
+	 * case when the RIRs delegate RPKI repositories).
+	 */
+	unsigned int level;
+
 	/** Used by certstack. Points to the next stacked certificate. */
 	SLIST_ENTRY(metadata_node) next;
 };
 
 SLIST_HEAD(metadata_stack, metadata_node);
 
-/**
- * Certificate repository "level". This aims to identify if the
- * certificate is located at a distinct server than its father (common
- * case when the RIRs delegate RPKI repositories).
- */
-struct repo_level_node {
-	unsigned int level;
-	SLIST_ENTRY(repo_level_node) next;
-};
-
-SLIST_HEAD(repo_level_stack, repo_level_node);
-
 /**
  * This is the foundation through which we pull off our iterative traversal,
  * as opposed to a stack-threatening recursive one.
@@ -109,12 +104,6 @@ struct cert_stack {
 	 * seemingly not intended to be used outside of its library.)
 	 */
 	struct metadata_stack metas;
-
-	/**
-	 * Stacked data to store the repository "levels" (each level is a
-	 * delegation of an RPKI server).
-	 */
-	struct repo_level_stack levels;
 };
 
 int
@@ -134,7 +123,6 @@ certstack_create(struct cert_stack **result)
 
 	SLIST_INIT(&stack->defers);
 	SLIST_INIT(&stack->metas);
-	SLIST_INIT(&stack->levels);
 
 	*result = stack;
 	return 0;
@@ -185,7 +173,6 @@ certstack_destroy(struct cert_stack *stack)
 	unsigned int stack_size;
 	struct metadata_node *meta;
 	struct defer_node *post;
-	struct repo_level_node *level;
 
 	stack_size = 0;
 	while (!SLIST_EMPTY(&stack->defers)) {
@@ -208,15 +195,6 @@ certstack_destroy(struct cert_stack *stack)
 	}
 	pr_val_debug("Deleted %u metadatas.", stack_size);
 
-	stack_size = 0;
-	while (!SLIST_EMPTY(&stack->levels)) {
-		level = SLIST_FIRST(&stack->levels);
-		SLIST_REMOVE_HEAD(&stack->levels, next);
-		free(level);
-		stack_size++;
-	}
-	pr_val_debug("Deleted %u stacked levels.", stack_size);
-
 	free(stack);
 }
 
@@ -242,7 +220,6 @@ x509stack_pop(struct cert_stack *stack)
 {
 	X509 *cert;
 	struct metadata_node *meta;
-	struct repo_level_node *repo;
 
 	cert = sk_X509_pop(stack->x509s);
 	if (cert == NULL)
@@ -254,12 +231,6 @@ x509stack_pop(struct cert_stack *stack)
 		pr_crit("Attempted to pop empty metadata stack");
 	SLIST_REMOVE_HEAD(&stack->metas, next);
 	meta_destroy(meta);
-
-	repo = SLIST_FIRST(&stack->levels);
-	if (repo == NULL)
-		pr_crit("Attempted to pop empty repo level stack");
-	SLIST_REMOVE_HEAD(&stack->levels, next);
-	free(repo);
 }
 
 /**
@@ -297,75 +268,115 @@ deferstack_is_empty(struct cert_stack *stack)
 	return SLIST_EMPTY(&stack->defers);
 }
 
+static int
+init_resources(X509 *x509, enum rpki_policy policy, enum cert_type type,
+    struct resources **_result)
+{
+	struct resources *result;
+	int error;
+
+	result = resources_create(false);
+	if (result == NULL)
+		return pr_enomem();
+
+	resources_set_policy(result, policy);
+	error = certificate_get_resources(x509, result, type);
+	if (error)
+		goto fail;
+
+	/*
+	 * rfc8630#section-2.3
+	 * "The INR extension(s) of this TA MUST contain a non-empty set of
+	 * number resources."
+	 * The "It MUST NOT use the "inherit" form of the INR extension(s)"
+	 * part is already handled in certificate_get_resources().
+	 */
+	if (type == TA && resources_empty(result)) {
+		error = pr_val_err("Trust Anchor certificate does not define any number resources.");
+		goto fail;
+	}
+
+	*_result = result;
+	return 0;
+
+fail:
+	resources_destroy(result);
+	return error;
+}
+
+static int
+init_level(struct cert_stack *stack, unsigned int *_result)
+{
+	struct metadata_node *head_meta;
+	unsigned int work_repo_level;
+	unsigned int result;
+
+	/*
+	 * Bruh, I don't understand the point of this block.
+	 * Why can't it just be `result = working_repo_peek_level();`?
+	 */
+
+	result = 0;
+	work_repo_level = working_repo_peek_level();
+	head_meta = SLIST_FIRST(&stack->metas);
+	if (head_meta != NULL && work_repo_level > head_meta->level)
+		result = work_repo_level;
+
+	*_result = result;
+	return 0;
+}
+
+static struct defer_node *
+create_separator(void)
+{
+	struct defer_node *result;
+
+	result = malloc(sizeof(struct defer_node));
+	if (result == NULL)
+		return NULL;
+
+	result->type = DNT_SEPARATOR;
+	return result;
+}
+
 /** Steals ownership of @x509 on success. */
 int
 x509stack_push(struct cert_stack *stack, struct rpki_uri *uri, X509 *x509,
     enum rpki_policy policy, enum cert_type type)
 {
 	struct metadata_node *meta;
-	struct repo_level_node *repo, *head_repo;
 	struct defer_node *defer_separator;
-	unsigned int work_repo_level;
 	int ok;
 	int error;
 
-	repo = malloc(sizeof(struct repo_level_node));
-	if (repo == NULL)
-		return pr_enomem();
-
-	repo->level = 0;
-	work_repo_level = working_repo_peek_level();
-	head_repo = SLIST_FIRST(&stack->levels);
-	if (head_repo != NULL && work_repo_level > head_repo->level)
-		repo->level = work_repo_level;
-
-	SLIST_INSERT_HEAD(&stack->levels, repo, next);
-
 	meta = malloc(sizeof(struct metadata_node));
-	if (meta == NULL) {
-		error = pr_enomem();
-		goto end3;
-	}
+	if (meta == NULL)
+		return pr_enomem();
 
 	meta->uri = uri;
 	uri_refget(uri);
 	serial_numbers_init(&meta->serials);
 	subjects_init(&meta->subjects);
 
-	meta->resources = resources_create(false);
-	if (meta->resources == NULL) {
-		error = pr_enomem();
-		goto end4;
-	}
-	resources_set_policy(meta->resources, policy);
-	error = certificate_get_resources(x509, meta->resources, type);
+	error = init_resources(x509, policy, type, &meta->resources);
 	if (error)
-		goto end5;
+		goto cleanup_subjects;
 
-	/*
-	 * rfc8630#section-2.3
-	 * "The INR extension(s) of this TA MUST contain a non-empty set of
-	 * number resources."
-	 * The "It MUST NOT use the "inherit" form of the INR extension(s)"
-	 * part is already handled in certificate_get_resources().
-	 */
-	if (type == TA && resources_empty(meta->resources)) {
-		error = pr_val_err("Trust Anchor certificate does not define any number resources.");
-		goto end5;
-	}
+	error = init_level(stack, &meta->level); /* Does not need a revert */
+	if (error)
+		goto destroy_resources;
 
-	defer_separator = malloc(sizeof(struct defer_node));
+	defer_separator = create_separator();
 	if (defer_separator == NULL) {
 		error = pr_enomem();
-		goto end5;
+		goto destroy_resources;
 	}
-	defer_separator->type = DNT_SEPARATOR;
 
 	ok = sk_X509_push(stack->x509s, x509);
 	if (ok <= 0) {
 		error = val_crypto_err(
 		    "Could not add certificate to trusted stack: %d", ok);
-		goto end5;
+		goto destroy_separator;
 	}
 
 	SLIST_INSERT_HEAD(&stack->defers, defer_separator, next);
@@ -373,13 +384,15 @@ x509stack_push(struct cert_stack *stack, struct rpki_uri *uri, X509 *x509,
 
 	return 0;
 
-end5:	resources_destroy(meta->resources);
-end4:	subjects_cleanup(&meta->subjects, subject_cleanup);
+destroy_separator:
+	free(defer_separator);
+destroy_resources:
+	resources_destroy(meta->resources);
+cleanup_subjects:
+	subjects_cleanup(&meta->subjects, subject_cleanup);
 	serial_numbers_cleanup(&meta->serials, serial_cleanup);
 	uri_refput(meta->uri);
 	free(meta);
-end3:	SLIST_REMOVE_HEAD(&stack->levels, next);
-	free(repo);
 	return error;
 }
 
@@ -428,8 +441,8 @@ x509stack_peek_resources(struct cert_stack *stack)
 unsigned int
 x509stack_peek_level(struct cert_stack *stack)
 {
-	struct repo_level_node *repo = SLIST_FIRST(&stack->levels);
-	return (repo != NULL) ? repo->level : 0;
+	struct metadata_node *meta = SLIST_FIRST(&stack->metas);
+	return (meta != NULL) ? meta->level : 0;
 }
 
 static int

programming_language: C
filename: certificate.c
change_type: ModificationType.MODIFY
diff: @@ -1152,6 +1152,9 @@ __certificate_get_resources(X509 *cert, struct resources *resources,
 	return 0;
 }
 
+/**
+ * Copies the resources from @cert to @resources.
+ */
 int
 certificate_get_resources(X509 *cert, struct resources *resources,
     enum cert_type type)

programming_language: C