Index | Thread | Search

From:
Job Snijders <job@bsd.nl>
Subject:
rpki-client: per directory limit for the number of CAs descending from a single issuer
To:
tech@openbsd.org
Date:
Tue, 6 Oct 2026 08:33:07 +0000

Download raw body.

Thread
  • Job Snijders:

    rpki-client: per directory limit for the number of CAs descending from a single issuer

Dear all,

It has been pointed out that an adversary could group many manifests
together in the same physical directory each referencing the same
products causing a quadratic workload for the RP.

A single directory may contain multiple caRepositories, this could
happen during key rollovers, but in the normal course of operation
issuing CAs will isolate their children into their own separate
directories.

A mitigation for this type of DoS is to simply place an upper bound
on the number of references originating from a single issuer towards
a single caRepository. In this form of accounting the issuer can
be represented by its CRLDP. When the counter for a given (CRLDP,
caRepository) overflows simply stop queuing any extra manifest entities.

Eyeing the current ecosystem, a limit of 20 seems enough. OK?

Kind regards,

Job

Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
diff -u -p -r1.300 extern.h
--- extern.h	29 Sep 2026 21:59:14 -0000	1.300
+++ extern.h	5 Oct 2026 12:12:43 -0000
@@ -942,6 +942,7 @@ void		 repo_tal_stats_collect(void (*)(c
 void		 repo_stats_collect(void (*)(const struct repo *,
 		    const struct repostats *, void *), void *);
 void		 repo_free(void);
+int		 repo_check_overflow(const struct cert *);
 
 void		 rsync_finish(unsigned int, int);
 void		 http_finish(unsigned int, enum http_result, const char *);
@@ -1127,6 +1128,7 @@ int	mkpathat(int, const char *);
 
 /* Maximum number of delegated hosting locations (repositories) for each TAL. */
 #define MAX_REPO_PER_TAL	1000
+#define MAX_MFT_PER_CAREPO	20
 
 #define HTTP_PROTO		"http://"
 #define HTTP_PROTO_LEN		(sizeof(HTTP_PROTO) - 1)
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
diff -u -p -r1.316 main.c
--- main.c	30 Sep 2026 20:48:11 -0000	1.316
+++ main.c	5 Oct 2026 12:12:44 -0000
@@ -562,6 +562,9 @@ queue_add_from_cert(const struct cert *c
 	if (nca_skip_sync(ncas, cert))
 		return;
 
+	if (repo_check_overflow(cert))
+		return;
+
 	repo = repo_lookup(cert->talid, cert->repo,
 	    rrdpon ? cert->notify : NULL);
 	if (repo == NULL)
Index: repo.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/repo.c,v
diff -u -p -r1.92 repo.c
--- repo.c	1 Oct 2026 13:06:56 -0000	1.92
+++ repo.c	5 Oct 2026 12:12:44 -0000
@@ -2124,3 +2124,75 @@ remove_contents(char *base)
 	if (fts_close(fts) == -1)
 		err(1, "fts_close");
 }
+
+struct mft_per_carepo {
+	RB_ENTRY(mft_per_carepo) entry;
+	char *carepo;
+	char *crl;
+	unsigned int num;
+};
+
+static inline int
+mft_per_carepocmp(struct mft_per_carepo *a, struct mft_per_carepo *b)
+{
+	int cmp;
+
+	cmp = strcmp(a->carepo, b->carepo);
+	if (cmp > 0)
+		return 1;
+	if (cmp < 0)
+		return -1;
+
+	cmp = strcmp(a->crl, b->crl);
+	if (cmp > 0)
+		return 1;
+	if (cmp < 0)
+		return -1;
+
+	return 0;
+}
+
+static RB_HEAD(mft_per_carepo_tree, mft_per_carepo) \
+    mft_per_carepos = RB_INITIALIZER(&mft_per_carepos);
+
+RB_GENERATE_STATIC(mft_per_carepo_tree, mft_per_carepo, entry,
+    mft_per_carepocmp);
+
+int
+repo_check_overflow(const struct cert *cert)
+{
+	struct mft_per_carepo *mft_per_carepo, needle;
+	unsigned int num;
+
+	if (cert->purpose == CERT_PURPOSE_TA)
+		return 0;
+
+	needle.carepo = cert->repo;
+	needle.crl = cert->crl;
+
+	if ((mft_per_carepo = RB_FIND(mft_per_carepo_tree, &mft_per_carepos,
+	    &needle)) == NULL) {
+		mft_per_carepo = calloc(1, sizeof(*mft_per_carepo));
+		if (mft_per_carepo == NULL)
+			err(1, NULL);
+		if ((mft_per_carepo->carepo = strdup(cert->repo)) == NULL)
+			err(1, NULL);
+		if ((mft_per_carepo->crl = strdup(cert->crl)) == NULL)
+			err(1, NULL);
+		mft_per_carepo->num = 0;
+
+		if (RB_INSERT(mft_per_carepo_tree, &mft_per_carepos,
+		    mft_per_carepo) != NULL)
+			err(1, "mft_per_carepo_tree corrupted");
+	}
+
+	num = ++mft_per_carepo->num;
+
+	if (num < MAX_MFT_PER_CAREPO)
+		return 0;
+
+	if (num == MAX_MFT_PER_CAREPO)
+		warnx("%s: caRepository %s overflowed", cert->path, cert->repo);
+
+	return 1;
+}