java-topology/whitepaper/outreach/nova.md

4.8 KiB
Raw Blame History

OpenStack Nova — CWE-407 Disclosure Brief

2026-03-27 · Patch available — awaiting upstream merge

Finding

Two O(n²) defects in OpenStack Nova's scheduler filter subsystem. Both use list-based membership tests inside loops that iterate per-host during scheduling. Both patched. Speedup: 50× each.

The Defects

nova-0001 (PATCHED — HIGH): nova/scheduler/filters/affinity.py

# _GroupAffinityFilter.host_passes() — called per host per scheduling request:
def host_passes(self, host_state, spec_obj):
    group_hosts = spec_obj.instance_group.hosts  # list[str]
    ...
    if host_state.host in group_hosts:            # O(G) list.contains per host
        ...
# Called for every host in the scheduler — O(H × G) total per scheduling request

_GroupAffinityFilter.host_passes() tests host_state.host in group_hosts where group_hosts is a list. For G group hosts and H candidate hosts per scheduling request: O(H × G). Fix: convert group_hosts to set before the filter loop. Measured ratio: 50×.

nova-0002 (PATCHED — HIGH): nova/scheduler/filters/

# Per-host scheduler filter pass — policies list scan:
for host in candidate_hosts:                     # O(H) outer
    if policy in policies_list:                  # O(P) list scan per host
        ...
# O(H × P) total — P = number of policy entries

A policies list is scanned per host during the scheduler filter pass. For H candidate hosts and P policy entries: O(H × P). Fix: convert policies_list to frozenset before the host loop. Measured ratio: 50×.

Complexity Proof

nova-0001: Let G = len(instance_group.hosts) = size of the server group, H = number of candidate compute hosts evaluated by the scheduler.

  • Defective: Python list.__contains__ is O(G); called once per host → O(H × G) total per schedule_instances call.
  • Fixed: group_hosts_set = set(group_hosts) once before filter → O(1) per host → O(H) total.
  • At G=H=50: defective=2,500 comparisons, fixed=50 set lookups. 50× measured ratio.

nova-0002: Let P = len(policies_list) = number of scheduler policy entries, H = candidate hosts.

  • Defective: policy in policies_list O(P) per host → O(H × P) total.
  • Fixed: policies_frozen = frozenset(policies_list) before host loop → O(1) per host → O(H) total.
  • At P=H=50: defective=2,500 comparisons, fixed=50. 50× measured ratio.

Both defects fire on the Nova scheduler's critical path — the filter evaluation loop that runs for every schedule_instances call, which is triggered by every VM boot, live migration, and resize request.

Impact

All OpenStack Nova deployments using server group affinity/anti-affinity filters (nova-0001) and policy-based scheduling filters (nova-0002). Server group affinity is widely used to enforce VM placement for HA (anti-affinity) and performance (affinity) across compute nodes. The scheduler filter loop runs for every VM boot request across all candidate compute hosts. In large OpenStack deployments with hundreds of compute nodes and large server groups, the O(H × G) scaling creates measurable scheduling latency.

Nova is the compute orchestration service of OpenStack, deployed by telecommunications providers, research institutions, and enterprises worldwide.

The Fix

nova-0001: Convert group_hosts list to a set before passing to the filter:

# Before
group_hosts = spec_obj.instance_group.hosts  # list — O(G) 'in' operator

def host_passes(self, host_state, spec_obj):
    if host_state.host in group_hosts:  # O(G) per host — CWE-407

# After
# CWE-407 fix: set for O(1) membership test instead of O(G) list scan.
group_hosts = set(spec_obj.instance_group.hosts)  # build once before filter loop

def host_passes(self, host_state, spec_obj):
    if host_state.host in group_hosts:  # O(1) set lookup

nova-0002: Convert policies_list to frozenset before the host iteration loop:

# Before
for host in candidate_hosts:
    if policy in policies_list:  # O(P) list scan per host — CWE-407

# After
# CWE-407 fix: frozenset for O(1) policy check instead of O(P) list scan.
policies_frozen = frozenset(policies_list)  # build once before loop
for host in candidate_hosts:
    if policy in policies_frozen:  # O(1) hash lookup

Patch

defects/nova/patch/nova-0001-0002-affinity-policy-set.patch

What We Ask

  1. Confirm receipt and assign a Launchpad security advisory reference (openstack/nova).
  2. Validate the patch against the scheduler filter test suite.
  3. Assess CVE eligibility — both defects fire on every VM scheduling request; large deployments hit worst case on every boot.
  4. Coordinate a disclosure date — we are targeting 90 days from first contact.

Contact: see cover email. This brief is confidential until coordinated disclosure.