Download raw body.
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;
+}
rpki-client: per directory limit for the number of CAs descending from a single issuer