From: Numan Siddique <[email protected]>

When OVN's DNS caching 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 configured on the
same switch that has DNS caching configured.

This patch fixes the issue by setting a register bit when OVN's DNS
responder replies to an incoming request. Then the flow that allows
egress ACL bypass only applies to packets that have this register bit
set. This gives the intended effect of allowing internally-generated DNS
responses to not be blocked by user-defined ACLs without potentially
compromising the security of the switch.

Signed-off-by: Numan Siddique <[email protected]>
Signed-off-by: Mark Michelson <[email protected]>
---
 controller/pinctrl.c         | 27 ++++++++++
 include/ovn/logical-fields.h |  1 +
 lib/logical-fields.c         |  3 ++
 northd/northd.c              |  3 +-
 tests/ovn.at                 | 97 ++++++++++++++++++++++++++++++++++++
 5 files changed, 130 insertions(+), 1 deletion(-)

diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 12fb708c1..18341a151 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -379,6 +379,8 @@ static void pinctrl_handle_put_fdb(const struct flow *md,
                                    const struct flow *headers)
                                    OVS_REQUIRES(pinctrl_mutex);
 
+static void set_from_ctrl_flag_in_pkt_metadata(struct ofputil_packet_in *);
+
 COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
 COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
 COVERAGE_DEFINE(pinctrl_drop_controller_event);
@@ -3136,6 +3138,10 @@ exit:
         union mf_subvalue sv;
         sv.u8_val = success;
         mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+
+        /* Indicate that this packet is from ovn-controller. */
+        set_from_ctrl_flag_in_pkt_metadata(pin);
+
     }
     queue_msg(swconn, ofputil_encode_resume(pin, continuation, proto));
     dp_packet_uninit(pkt_out_ptr);
@@ -8116,3 +8122,24 @@ pinctrl_handle_put_fdb(const struct flow *md, const 
struct flow *headers)
     ovn_fdb_add(&put_fdbs, dp_key, headers->dl_src, port_key);
     notify_pinctrl_main();
 }
+
+/* This function sets the register bit 'MLF_FROM_CTRL_BIT'
+ * in the register 'MFF_LOG_FLAGS' to indicate that this packet
+ * is generated/sent by ovn-controller.
+ * ovn-northd can add logical flows to match on "flags.from_ctrl".
+ */
+static void
+set_from_ctrl_flag_in_pkt_metadata(struct ofputil_packet_in *pin)
+{
+    const struct mf_field *f = mf_from_id(MFF_LOG_FLAGS);
+
+    struct mf_subfield dst = {
+        .field = f,
+        .ofs = MLF_FROM_CTRL_BIT,
+        .n_bits = 1,
+    };
+
+    union mf_subvalue sv;
+    sv.u8_val = 1;
+    mf_write_subfield(&dst, &sv, &pin->flow_metadata);
+}
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index d733a15f8..71bd8d59c 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -71,6 +71,7 @@ enum mff_log_flags_bits {
     MLF_USE_SNAT_ZONE = 11,
     MLF_CHECK_PORT_SEC_BIT = 12,
     MLF_LOOKUP_COMMIT_ECMP_NH_BIT = 13,
+    MLF_FROM_CTRL_BIT = 14,
 };
 
 /* MFF_LOG_FLAGS_REG flag assignments */
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index 381a66000..e3e8ae09f 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -130,6 +130,9 @@ ovn_init_symtab(struct shash *symtab)
     expr_symtab_add_subfield(symtab, "flags.use_snat_zone", NULL,
                              flags_str);
 
+    snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_FROM_CTRL_BIT);
+    expr_symtab_add_subfield(symtab, "flags.from_ctrl", NULL, flags_str);
+
     /* Connection tracking state. */
     expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
                                  WR_CT_COMMIT);
diff --git a/northd/northd.c b/northd/northd.c
index 76e18acec..80d87c40b 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -6779,7 +6779,8 @@ build_acls(struct ovn_datapath *od, const struct 
chassis_features *features,
     if (ls_has_dns_records(od->nbs)) {
         const char *dns_actions = has_stateful ? "ct_commit; next;" : "next;";
         ovn_lflow_add(
-            lflows, od, S_SWITCH_OUT_ACL, 34000, "udp.src == 53",
+            lflows, od, S_SWITCH_OUT_ACL, 34000,
+            "flags.from_ctrl && udp.src == 53",
             dns_actions);
     }
 
diff --git a/tests/ovn.at b/tests/ovn.at
index b89a9b44b..a54d0e542 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -11070,6 +11070,15 @@ echo ${dns_reply} > expected
 as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req}
 OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv1/vif1-tx.pcap], [expected])
 
+AS_BOX([Add ACL to drop udp.src == 53 in egress stage])
+
+# DNS reply from ovn-controller should still be delivered.
+check ovn-nbctl --wait=hv acl-add ls to-lport 1002 "udp.src == 53" drop
+as hv1 reset_pcap_file  hv1-vif1 hv1/vif1
+
+as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 ${dns_req}
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
@@ -33136,3 +33145,91 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows 
br-int table=0 |grep priority=1
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DNS reply packet with ACL to drop it])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+net_add n1
+sim_add hv1
+
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.11
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=sw0p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=sw0p2 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=2
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lsp-add sw0 sw0p1 -- \
+  lsp-set-addresses sw0p1 "50:54:00:00:00:03 10.0.0.3"
+check ovn-nbctl --wait=hv lsp-add sw0 sw0p2 -- \
+  lsp-set-addresses sw0p2 "50:54:00:00:00:04 10.0.0.4"
+
+wait_for_ports_up
+
+# Send 2 UDP packets from sw0p1 to sw0p2.
+# One with udp.src = 53 and the other with udp.src = 54.
+# If dropped is yes, then the udp pkt with udp.src == 53 should
+# be dropped.
+send_udp_packets() {
+    rm -f expected
+    dropped=$1
+    packet=$(fmt_pkt "
+            Ether(dst='50:54:00:00:00:04', src='50:54:00:00:00:03') /
+            IP(src='10.0.0.3', dst='10.0.0.4') /
+            UDP(sport=53, dport=1235)
+           ")
+    ovs-appctl netdev-dummy/receive vif1 $packet
+    if [[ "$dropped" != "yes" ]]; then
+        echo $packet > expected
+    fi
+    packet=$(fmt_pkt "
+            Ether(dst='50:54:00:00:00:04', src='50:54:00:00:00:03') /
+            IP(src='10.0.0.3', dst='10.0.0.4') /
+            UDP(sport=54, dport=1235)
+           ")
+    ovs-appctl netdev-dummy/receive vif1 $packet
+    echo $packet >> expected
+}
+
+# Send UDP packets with src port 53 and 54 from sw0p1 to sw0p2.  Both should be
+# delivered to sw0p2.
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file vif2 hv1/vif2
+
+send_udp_packets no
+
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
+
+AS_BOX([Create DNS records, add ACL to drop DNS reply packet])
+DNS1=$(ovn-nbctl create DNS records={vm1=10.0.0.3})
+check ovn-nbctl set logical_switch sw0 dns_records=$DNS1
+check ovn-nbctl --wait=hv acl-add sw0 to-lport 1002 "udp.src == 53" drop
+ovn-sbctl dump-flows sw0 > sw0flows
+AT_CAPTURE_FILE([sw0flows])
+
+AT_CHECK([grep "ls_out_acl" sw0flows | sed 's/table=../table=??/' | grep 
"udp.src" | sort], [0], [dnl
+  table=??(ls_out_acl         ), priority=2002 , match=(udp.src == 53), 
action=(/* drop */)
+  table=??(ls_out_acl         ), priority=34000, match=(flags.from_ctrl && 
udp.src == 53), action=(next;)
+])
+
+AS_BOX([Send UDP packets from sw0p1 to sw0p2 and pkt with udp.src == 53 should 
be dropped])
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file vif2 hv1/vif2
+# Send UDP packets with src port 53 and 54 from sw0p1 to sw0p2.  Only with 
udp.src = 54
+# should be delivered to sw0p2.
+send_udp_packets yes
+
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
-- 
2.45.2

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to