Note: This release had to be fast-tracked because the security issue was made public before an embargo could be lifted. We are awaiting a CVE assignment and will update this advisory with the CVE number once it is assigned.

Description
===========
Multiple versions of OVN (Open Virtual Network) are vulnerable to allowing crafted UDP packets to bypass egress access control list (ACL) rules. This can result in unauthorized access to virtual machines and containers running on the OVN network.

OVN provides rudimentary DNS caching as an optional feature to speed up lookups of frequently-used domains. When this feature is enabled, due to the OpenFlow rules that OVN installs in Open vSwitch, it is possible for an attacker to craft a UDP packet that can bypass egress ACL rules. Egress ACL rules are those that have the "direction" set to "to-lport".

The OVN installation is vulnerable if a logical switch has DNS records set on it AND if the same switch has any egress ACLs configured on it. The switch is considered to have egress ACLs configured if the switch has an egress ACL configured directly on it using the "acls" column of the logical switch. A switch is also considered to have egress ACLs configured if any of its logical switch ports are part of a port group that has egress ACLs configured in its "acls" column.

A python script (vuln_test.py) is attached to this advisory and can be used to determine if your installation is vulnerable. Run it in a location where "ovn-nbctl" is installed and can access the northbound database. The script will print to the console whether the installation is vulnerable.

Mitigation
==========
If the DNS records being used in OVN are accessible over the internet without using the built-in cache, then DNS caching can be disabled in OVN by clearing the "dns_records" column of all logical switches in the northbound database. Here is a command that can be used to accomplish this:

$ for s in $(ovn-nbctl list logical_switch | grep uuid | cut -f 2 -d ':') ; do ovn-nbctl clear logical_switch "$s" dns_records ; done

Doing this will incur a latency penalty for DNS lookups since the lookup will be carried out over the internet instead of being looked up in a local cache.

If OVN's DNS caching is required on the deployment, then a second mitigation might be to adjust ACLs from being evaluated on egress to being evaluated on ingress. ACLs whose "direction" column is set to "to-lport" are evaluated on egress and are vulnerable to being bypassed if OVN's DNS caching is enabled. ACLs whose "direction" column is set to "from-lport" are evaluated on ingress and are not vulnerable to being bypassed if OVN's DNS caching is enabled. An ACL can be evaluated on ingress as long as it does not attempt to match on the "outport" field. Ingress ACLs are capable of matching based on the results of load balancing if "options:apply-after-lb" is set to "true".

There is no one-size fits all command line solution for changing egress ACLs to ingress ACLs. Each ACL rule will need to be evaluated and changed over if possible.

If DNS caching is required and egress ACLs are required, then the only mitigation is to adjust the topology of the virtual network so that DNS caching and egress ACLs are not on the same logical switch.

Fix
===
Patches to fix this vulnerability in currently supported versions of OVN are as follows:

* 22.03.x: https://github.com/ovn-org/ovn/commit/f22a1ba9c127795bebcfbd41d772bb071f893a6d * 24.03.x: https://github.com/ovn-org/ovn/commit/70618a65fd49f1d1d5498927c0bed63e296dafb7 * 24.09.x: https://github.com/ovn-org/ovn/commit/249c52ad011cacb4c182dc64e88977ac7c61f668

The original patch is located at:
https://mail.openvswitch.org/pipermail/ovs-dev/2025-January/419993.html

Recommendation
==============
We recommend that users of OVN apply the linked patches, or upgrade to a known patched version of OVN. These include:

v22.03.8
v24.03.5
v24.09.2

Acknowledgments
===============

The OVN team wishes to thank the reporters:

     Marius Berntsberg - mar...@redpill-linpro.com
     Trygve Vea - t...@redpill-linpro.com
     Tore Anderson - t...@redpill-linpro.com
     Rodolfo Alonso - ralon...@redhat.com
     Jay Faulkner (Openstack VMT) - j...@jvf.cc
     Brian Haley (Openstack/Neutron) - haleyb....@gmail.com

In addition, a special acknowledgment is due to the Firewall Misconfiguration security research team at the University of California, Riverside - firewallresea...@ucr.edu - https://firewall-research.cs.ucr.edu/misconfiguration/
#!/usr/bin/env python3

import subprocess
import shlex

# This script uses the `ovn-nbctl` command to determine if the deployment is
# vulnerable to <CVE number>. It does this by finding the set of
# switches that have DNS records and that have egress ACLs. If this set has any
# members, then the deployment is vulnerable.


def build_obj(text: str):
    objs = dict()
    obj = dict()
    cur_uuid = None
    for line in text.split("\n"):
        line = line.strip()
        if len(line) == 0:
            if cur_uuid:
                objs[cur_uuid] = obj
            obj = dict()
            continue
        key, _, value = line.partition(":")
        key = key.strip()
        value = value.strip()
        if value.startswith("["):
            value = value[1:-1]
            value = [item for item in value.split(",")]
        obj[key.strip()] = value
        if key == "_uuid":
            cur_uuid = value

    return objs


def build_obj_from_db(cmd: str):
    cmd_list = shlex.split(cmd)
    sub = subprocess.run(cmd_list, capture_output=True)
    return build_obj(sub.stdout.decode())


switches = build_obj_from_db("ovn-nbctl list logical_switch")
dns = build_obj_from_db("ovn-nbctl find dns records!={}")
egress_acls = build_obj_from_db("ovn-nbctl find acl direction=to-lport")
port_groups = build_obj_from_db("ovn-nbctl list port_group")

# Build the set of switches with DNS records
switches_with_dns = set()
for uuid, vals in switches.items():
    for record in vals["dns_records"]:
        if record in dns:
            switches_with_dns.add(uuid)

# Build the set of switches with ACLs. Step one is simple, get the switches that
# directly have ACLs set on them.
switches_with_acls = set()
for uuid, vals in switches.items():
    for record in vals["acls"]:
        if record in egress_acls:
            switches_with_acls.add(uuid)
            break

# Now we need to check if any of the switches' ports belong to a port group with
# an egress ACL. Start by creating a map of ports to switches. This way, when we
# come across a port in the port group, we can find the switch easily.
port_switch_map = dict()
for uuid, vals in switches.items():
    for port in vals["ports"]:
        if not port:
            continue
        port_switch_map[port] = uuid

# Create a filtered dict of port groups that have egress ACLs.
port_groups_with_egress_acls = dict()
for uuid, vals in port_groups.items():
    for record in vals["acls"]:
        if record in egress_acls:
            port_groups_with_egress_acls[uuid] = vals
            break

# Now find switches that have ports in the port groups with egress ACLs. Add
# these to the switches_with_acls set.
for uuid, vals in port_groups_with_egress_acls.items():
    for port in vals["ports"]:
        if port in port_switch_map:
            switches_with_acls.add(port_switch_map[port])

# Switches with DNS and egress ACLs mean the system is vulnerable.
if switches_with_dns & switches_with_acls:
    print("This installation is vulnerable")
else:
    print("This installation is NOT vulnerable")

Reply via email to