From: Job Snijders 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 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; +}