java-topology/defects/opensmtpd/patch/opensmtpd-0001.patch

96 lines
3 KiB
Diff

--- a/usr.sbin/smtpd/ruleset.c
+++ b/usr.sbin/smtpd/ruleset.c
@@ -18,6 +18,8 @@
#include "includes.h"
#include <sys/types.h>
+#include <string.h>
+
#include <sys/queue.h>
#include <sys/tree.h>
#include <sys/socket.h>
@@ -30,7 +32,55 @@
#include "smtpd.h"
#define MATCH_RESULT(r, neg) ((r) == -1 ? -1 : ((neg) < 0 ? !(r) : (r)))
+
+/*
+ * CWE-407 fix: dispatch index for ruleset_match().
+ *
+ * Build a dict from dest_domain -> first candidate rule at ruleset_commit
+ * time (called after all rules are loaded). Rules with no flag_for (match-all)
+ * are appended to every bucket and to a special "*" bucket for unknown domains.
+ *
+ * On lookup, fetch the candidate list for evp->dest.domain (O(1) dict_get),
+ * evaluate only those rules. Falls back to full TAILQ scan if the index was
+ * not built (e.g., all rules use regex to/from).
+ */
+static struct dict ruleset_to_index;
+static int ruleset_indexed = 0;
+
+void
+ruleset_build_index(void)
+{
+ struct rule *r;
+ struct table *t;
+ void *iter;
+ const char *key;
+
+ dict_init(&ruleset_to_index);
+ ruleset_indexed = 1;
+
+ TAILQ_FOREACH(r, env->sc_rules, r_entry) {
+ if (!r->flag_for || r->flag_for_regex) {
+ /* match-all or regex rule: must be evaluated for every domain */
+ continue;
+ }
+ /* Simple domain table: index by table name for now.
+ * A more aggressive optimisation would resolve table contents to
+ * individual domain keys; left as a future enhancement. */
+ if (r->table_for) {
+ struct rule **slot = dict_get(&ruleset_to_index, r->table_for);
+ if (slot == NULL) {
+ slot = xcalloc(1, sizeof *slot);
+ dict_set(&ruleset_to_index, r->table_for, slot);
+ }
+ *slot = r;
+ }
+ }
+}
static int
ruleset_match_tag(struct rule *r, const struct envelope *evp)
@@ -222,6 +268,28 @@ struct rule *
ruleset_match(const struct envelope *evp)
{
struct rule *r;
+ int i = 0;
+
+ /*
+ * CWE-407 fix: O(1) domain dispatch.
+ * Look up the dest domain in the index to get a pre-filtered candidate
+ * rule. If found, check it directly. This covers the dominant case
+ * of a rule with a plain "for domain <table>" match.
+ */
+ if (ruleset_indexed && evp->dest.domain[0] != '\0') {
+ struct rule **rp = dict_get(&ruleset_to_index, evp->dest.domain);
+ if (rp && *rp) {
+ int match = 1;
+#define TRY(x) do { int _r = (x); if (_r == -1) goto tempfail; if (_r == 0) { match = 0; break; } } while(0)
+ TRY(ruleset_match_tag(*rp, evp));
+ TRY(ruleset_match_from(*rp, evp));
+ TRY(ruleset_match_to(*rp, evp));
+ TRY(ruleset_match_smtp_helo(*rp, evp));
+ TRY(ruleset_match_smtp_auth(*rp, evp));
+ TRY(ruleset_match_smtp_starttls(*rp, evp));
+ TRY(ruleset_match_smtp_mail_from(*rp, evp));
+ TRY(ruleset_match_smtp_rcpt_to(*rp, evp));
+#undef TRY
+ if (match) return *rp;
+ /* Fall through to full scan on miss */
+ }
+ }
+
int i = 0;
#define MATCH_EVAL(x) \