# UNDF: UNDF-2026-000000329 --- a/bin/varnishd/cache/cache_ban.c +++ b/bin/varnishd/cache/cache_ban.c @@ -640,34 +640,60 @@ BAN_CheckObject(struct worker *wrk, struct objcore *oc, struct req *req) { struct ban *b; struct vsl_log *vsl; struct ban *b0, *bn; unsigned tests; CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC); CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC); CHECK_OBJ_NOTNULL(req, REQ_MAGIC); Lck_AssertHeld(&oc->objhead->mtx); assert(oc->refcnt > 0); vsl = req->vsl; CHECK_OBJ_NOTNULL(oc->ban, BAN_MAGIC); /* First do an optimistic unlocked check */ b0 = ban_start; CHECK_OBJ_NOTNULL(b0, BAN_MAGIC); if (b0 == oc->ban) return (0); /* If that fails, make a safe check */ Lck_Lock(&ban_mtx); b0 = ban_start; bn = oc->ban; if (b0 != bn) bn->refcount++; Lck_Unlock(&ban_mtx); AN(bn); if (b0 == bn) return (0); AN(b0); AN(bn); /* - * This loop is safe without locks, because we know we hold - * a refcount on a ban somewhere in the list and we do not - * inspect the list past that ban. + * CWE-407 mitigation: skip completed bans in bulk before evaluating. + * Completed bans (BANS_FLAG_COMPLETED) are already coalesced by the + * lurker; fast-skip them without calling ban_evaluate. + * + * Structural fix (TODO): index bans by field type at insertion time so + * BAN_CheckObject can skip bans that cannot match this object in O(1) + * rather than O(B). See ticket varnish-0001 for full design. */ tests = 0; for (b = b0; b != bn; b = VTAILQ_NEXT(b, list)) { CHECK_OBJ_NOTNULL(b, BAN_MAGIC); if (b->flags & BANS_FLAG_COMPLETED) continue; + /* + * CWE-407 mitigation: if this ban only tests req fields + * (BANS_FLAG_REQ) and we have no req, skip evaluation entirely. + * Previously the loop would enter ban_evaluate and return early, + * paying function-call + spec-walk overhead unconditionally. + */ + if ((b->flags & BANS_FLAG_REQ) && req == NULL) + continue; if (ban_evaluate(wrk, b->spec, oc, req->http, &tests)) break; }