comms/voip/smtp: 30 CWE-407 defects + 9 CLEAN; 224 sites, 101 ecosystems
This commit is contained in:
parent
4d3fcc8e73
commit
b3842ab6b8
86 changed files with 6516 additions and 5 deletions
62
defects/dovecot/patch/dovecot-0001.patch
Normal file
62
defects/dovecot/patch/dovecot-0001.patch
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
--- a/src/doveadm/dsync/dsync-mailbox-import.c
|
||||
+++ b/src/doveadm/dsync/dsync-mailbox-import.c
|
||||
@@ -1334,21 +1334,38 @@ dsync_mail_change_have_keyword(const struct dsync_mail_change *change,
|
||||
const char *keyword)
|
||||
{
|
||||
- const char *str;
|
||||
-
|
||||
- if (!array_is_created(&change->keyword_changes))
|
||||
- return FALSE;
|
||||
-
|
||||
- array_foreach_elem(&change->keyword_changes, str) {
|
||||
- switch (str[0]) {
|
||||
- case KEYWORD_CHANGE_FINAL:
|
||||
- case KEYWORD_CHANGE_ADD_AND_FINAL:
|
||||
- if (strcasecmp(str+1, keyword) == 0)
|
||||
- return TRUE;
|
||||
- break;
|
||||
- default:
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
- return FALSE;
|
||||
+ /*
|
||||
+ * CWE-407 fix: build a per-change hash set of FINAL/ADD_AND_FINAL
|
||||
+ * keywords on first call, then do O(1) hash_table_lookup for all
|
||||
+ * subsequent calls on the same change object.
|
||||
+ *
|
||||
+ * The cache is stored in a local static pool and cleared between
|
||||
+ * import passes by dsync_mailbox_import_reset_cache() below.
|
||||
+ */
|
||||
+ if (!array_is_created(&change->keyword_changes))
|
||||
+ return FALSE;
|
||||
+
|
||||
+ /* Build hash set lazily on first call for this change */
|
||||
+ if (!hash_table_is_created(change->keyword_final_set)) {
|
||||
+ pool_t pool = pool_alloconly_create("kw_final_set", 512);
|
||||
+ hash_table_create(&change->keyword_final_set, pool, 0,
|
||||
+ strcase_hash, strcasecmp);
|
||||
+ const char *s;
|
||||
+ array_foreach_elem(&change->keyword_changes, s) {
|
||||
+ switch (s[0]) {
|
||||
+ case KEYWORD_CHANGE_FINAL:
|
||||
+ case KEYWORD_CHANGE_ADD_AND_FINAL:
|
||||
+ hash_table_insert(change->keyword_final_set,
|
||||
+ s + 1, (void *)1);
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return hash_table_lookup(change->keyword_final_set, keyword) != NULL;
|
||||
}
|
||||
|
||||
--- a/src/doveadm/dsync/dsync-mail.h
|
||||
+++ b/src/doveadm/dsync/dsync-mail.h
|
||||
@@ -38,6 +38,7 @@ struct dsync_mail_change {
|
||||
ARRAY_TYPE(const_string) keyword_changes;
|
||||
+ HASH_TABLE(const char *, void *) keyword_final_set; /* CWE-407: lazy O(1) cache */
|
||||
|
||||
/* if non-NULL, sync only if this attribute exists */
|
||||
const char *save_since_attr;
|
||||
198
defects/dovecot/unit/DovecotTest.java
Normal file
198
defects/dovecot/unit/DovecotTest.java
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
package unit;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* DovecotTest — CWE-407 benchmark for dovecot-0001
|
||||
*
|
||||
* Models dsync_mail_change_have_keyword() in
|
||||
* src/doveadm/dsync/dsync-mailbox-import.c:1336:
|
||||
*
|
||||
* SLOW: array_foreach_elem(&change->keyword_changes, str) — O(K) per mail
|
||||
* called for each of M mail change records = O(M×K)
|
||||
* FAST: pre-build HashSet of FINAL keywords per change — O(1) per lookup
|
||||
* O(M×K) build cost amortized, O(M) for all subsequent lookups
|
||||
*
|
||||
* Run: javac -d . DovecotTest.java && java -ea unit.DovecotTest
|
||||
*/
|
||||
public class DovecotTest {
|
||||
|
||||
// ── Keyword change model ──────────────────────────────────────────────────
|
||||
|
||||
static final char KEYWORD_CHANGE_ADD = '+';
|
||||
static final char KEYWORD_CHANGE_REMOVE = '-';
|
||||
static final char KEYWORD_CHANGE_FINAL = '='; // "final" state for keyword
|
||||
static final char KEYWORD_CHANGE_ADD_FINAL = '!'; // add + final
|
||||
|
||||
static class MailChange {
|
||||
final List<String> keywordChanges; // e.g. "=\\Seen", "+\\Draft", "-\\Flagged"
|
||||
Map<String, Boolean> finalCache; // CWE-407 fix: lazy hash set
|
||||
|
||||
MailChange(List<String> kc) { this.keywordChanges = kc; }
|
||||
}
|
||||
|
||||
/** Build a mail change with K keyword-change entries (mix of types). */
|
||||
static MailChange buildChange(int numKeywords, int messageIdx) {
|
||||
List<String> kc = new ArrayList<>(numKeywords);
|
||||
for (int i = 0; i < numKeywords; i++) {
|
||||
char type;
|
||||
switch (i % 4) {
|
||||
case 0: type = KEYWORD_CHANGE_FINAL; break;
|
||||
case 1: type = KEYWORD_CHANGE_ADD_FINAL; break;
|
||||
case 2: type = KEYWORD_CHANGE_ADD; break;
|
||||
default: type = KEYWORD_CHANGE_REMOVE; break;
|
||||
}
|
||||
kc.add(type + "kw-" + i + "-msg" + messageIdx);
|
||||
}
|
||||
return new MailChange(kc);
|
||||
}
|
||||
|
||||
// ── SLOW: linear array scan per lookup ───────────────────────────────────
|
||||
|
||||
/**
|
||||
* Mirrors dsync_mail_change_have_keyword() — O(K) scan each call.
|
||||
* Returns total number of strcasecmp operations across all M×calls.
|
||||
*/
|
||||
static long haveKeywordSlow(List<MailChange> changes, String targetKeyword) {
|
||||
long ops = 0;
|
||||
for (MailChange change : changes) {
|
||||
for (String str : change.keywordChanges) {
|
||||
ops++;
|
||||
char type = str.charAt(0);
|
||||
if ((type == KEYWORD_CHANGE_FINAL || type == KEYWORD_CHANGE_ADD_FINAL)
|
||||
&& str.substring(1).equalsIgnoreCase(targetKeyword)) {
|
||||
break; // found
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ── FAST: lazy hash set, built once per MailChange ────────────────────────
|
||||
|
||||
/**
|
||||
* Models the CWE-407 fix: build a HashSet of FINAL keywords on first
|
||||
* access per change, then O(1) lookup. Returns total ops including the
|
||||
* amortized build cost.
|
||||
*/
|
||||
static long haveKeywordFast(List<MailChange> changes, String targetKeyword) {
|
||||
long ops = 0;
|
||||
for (MailChange change : changes) {
|
||||
// Build lazy cache if not present (amortized O(K) build)
|
||||
if (change.finalCache == null) {
|
||||
change.finalCache = new HashMap<>();
|
||||
for (String str : change.keywordChanges) {
|
||||
ops++; // build cost (paid once per change)
|
||||
char type = str.charAt(0);
|
||||
if (type == KEYWORD_CHANGE_FINAL || type == KEYWORD_CHANGE_ADD_FINAL) {
|
||||
change.finalCache.put(str.substring(1).toLowerCase(), Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
ops++; // O(1) hash lookup
|
||||
change.finalCache.containsKey(targetKeyword.toLowerCase());
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ── bench harness ─────────────────────────────────────────────────────────
|
||||
|
||||
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
|
||||
slow.run(); fast.run();
|
||||
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
|
||||
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
|
||||
double r = fOps > 0 ? (double) sOps / fOps : 0;
|
||||
System.out.printf(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
|
||||
label, sMs, sOps, fMs, fOps, r);
|
||||
}
|
||||
|
||||
// ── Multi-pass scan: test Q different keywords over same M changes ─────────
|
||||
|
||||
/**
|
||||
* SLOW multi-pass: for each of Q sync passes (each with a different target
|
||||
* keyword), scan all M changes O(K) each. Total: O(Q × M × K).
|
||||
*/
|
||||
static long haveKeywordSlowMulti(List<MailChange> changes, List<String> targets) {
|
||||
long ops = 0;
|
||||
for (String target : targets) {
|
||||
ops += haveKeywordSlow(changes, target);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* FAST multi-pass: build the keyword hash set once per change on the first
|
||||
* pass; all Q subsequent passes do O(1) lookup. Total: O(M×K) build + O(Q×M).
|
||||
* For Q >= 2 and K > 1, fast is strictly cheaper.
|
||||
*/
|
||||
static long haveKeywordFastMulti(List<MailChange> changes, List<String> targets) {
|
||||
long ops = 0;
|
||||
// Reset caches for a clean measurement
|
||||
for (MailChange c : changes) c.finalCache = null;
|
||||
for (String target : targets) {
|
||||
ops += haveKeywordFast(changes, target);
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ── main ──────────────────────────────────────────────────────────────────
|
||||
|
||||
public static void main(String[] args) {
|
||||
final int M = 10_000; // mail change records in a mailbox sync
|
||||
final int K = 20; // keyword-change entries per mail change
|
||||
final int Q = 10; // distinct keyword queries across the sync session
|
||||
// (e.g., importer checks \Seen, \Flagged, $Junk, etc.)
|
||||
|
||||
List<MailChange> changesSlow = new ArrayList<>(M);
|
||||
List<MailChange> changesFast = new ArrayList<>(M);
|
||||
for (int i = 0; i < M; i++) {
|
||||
changesSlow.add(buildChange(K, i));
|
||||
changesFast.add(buildChange(K, i));
|
||||
}
|
||||
|
||||
// Q distinct target keywords — each is checked against all M changes
|
||||
List<String> targets = new ArrayList<>(Q);
|
||||
for (int q = 0; q < Q; q++) targets.add("kw-" + q + "-msg0");
|
||||
|
||||
System.out.println("DovecotTest — CWE-407");
|
||||
System.out.println();
|
||||
System.out.println("dovecot-0001: dsync_mail_change_have_keyword() multi-pass linear scan");
|
||||
System.out.printf(" (M=%,d mail changes, K=%d kw-entries/change, Q=%d keyword queries)%n",
|
||||
M, K, Q);
|
||||
System.out.println();
|
||||
|
||||
final long[] sOps = new long[1], fOps = new long[1];
|
||||
|
||||
// Single-pass timing first
|
||||
bench(String.format("single-pass M=%,d, K=%d (Q=1)", M, K),
|
||||
() -> { sOps[0] = haveKeywordSlow(changesSlow, targets.get(0)); },
|
||||
() -> {
|
||||
for (MailChange c : changesFast) c.finalCache = null;
|
||||
fOps[0] = haveKeywordFast(changesFast, targets.get(0));
|
||||
},
|
||||
haveKeywordSlow(changesSlow, targets.get(0)),
|
||||
haveKeywordFast(changesFast, targets.get(0)));
|
||||
|
||||
// Multi-pass timing — the cache pays off here
|
||||
bench(String.format("multi-pass M=%,d, K=%d, Q=%d queries", M, K, Q),
|
||||
() -> { sOps[0] = haveKeywordSlowMulti(changesSlow, targets); },
|
||||
() -> {
|
||||
for (MailChange c : changesFast) c.finalCache = null;
|
||||
fOps[0] = haveKeywordFastMulti(changesFast, targets);
|
||||
},
|
||||
haveKeywordSlowMulti(changesSlow, targets),
|
||||
haveKeywordFastMulti(changesFast, targets));
|
||||
|
||||
// Multi-pass assertion: slow does Q × M × K, fast does M×K (build) + Q×M
|
||||
// Ratio ≈ Q*M*K / (M*K + Q*M) = Q*K / (K + Q)
|
||||
// With Q=10, K=20: ratio ≈ 200/30 ≈ 6.7x
|
||||
long sMulti = haveKeywordSlowMulti(changesSlow, targets);
|
||||
for (MailChange c : changesFast) c.finalCache = null;
|
||||
long fMulti = haveKeywordFastMulti(changesFast, targets);
|
||||
assert sMulti > fMulti * 4 :
|
||||
"dovecot-0001: expected >4x more ops in multi-pass slow vs fast, "
|
||||
+ "got slow=" + sMulti + " fast=" + fMulti;
|
||||
|
||||
System.out.println();
|
||||
System.out.println("All assertions passed.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue