Skip to content

Commit

Permalink
Merge branch 'net-openvswitch-limit-the-recursions-from-action-sets'
Browse files Browse the repository at this point in the history
Aaron Conole says:

====================
net: openvswitch: limit the recursions from action sets

Open vSwitch module accepts actions as a list from the netlink socket
and then creates a copy which it uses in the action set processing.
During processing of the action list on a packet, the module keeps a
count of the execution depth and exits processing if the action depth
goes too high.

However, during netlink processing the recursion depth isn't checked
anywhere, and the copy trusts that kernel has large enough stack to
accommodate it.  The OVS sample action was the original action which
could perform this kinds of recursion, and it originally checked that
it didn't exceed the sample depth limit.  However, when sample became
optimized to provide the clone() semantics, the recursion limit was
dropped.

This series adds a depth limit during the __ovs_nla_copy_actions() call
that will ensure we don't exceed the max that the OVS userspace could
generate for a clone().

Additionally, this series provides a selftest in 2/2 that can be used to
determine if the OVS module is allowing unbounded access.  It can be
safely omitted where the ovs selftest framework isn't available.
====================

Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Jakub Kicinski <[email protected]>
  • Loading branch information
kuba-moo committed Feb 9, 2024
2 parents d02bfae + bd128f6 commit 6a12401
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 31 deletions.
49 changes: 33 additions & 16 deletions net/openvswitch/flow_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct ovs_len_tbl {

#define OVS_ATTR_NESTED -1
#define OVS_ATTR_VARIABLE -2
#define OVS_COPY_ACTIONS_MAX_DEPTH 16

static bool actions_may_change_flow(const struct nlattr *actions)
{
Expand Down Expand Up @@ -2545,13 +2546,15 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
const struct sw_flow_key *key,
struct sw_flow_actions **sfa,
__be16 eth_type, __be16 vlan_tci,
u32 mpls_label_count, bool log);
u32 mpls_label_count, bool log,
u32 depth);

static int validate_and_copy_sample(struct net *net, const struct nlattr *attr,
const struct sw_flow_key *key,
struct sw_flow_actions **sfa,
__be16 eth_type, __be16 vlan_tci,
u32 mpls_label_count, bool log, bool last)
u32 mpls_label_count, bool log, bool last,
u32 depth)
{
const struct nlattr *attrs[OVS_SAMPLE_ATTR_MAX + 1];
const struct nlattr *probability, *actions;
Expand Down Expand Up @@ -2602,7 +2605,8 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr,
return err;

err = __ovs_nla_copy_actions(net, actions, key, sfa,
eth_type, vlan_tci, mpls_label_count, log);
eth_type, vlan_tci, mpls_label_count, log,
depth + 1);

if (err)
return err;
Expand All @@ -2617,7 +2621,8 @@ static int validate_and_copy_dec_ttl(struct net *net,
const struct sw_flow_key *key,
struct sw_flow_actions **sfa,
__be16 eth_type, __be16 vlan_tci,
u32 mpls_label_count, bool log)
u32 mpls_label_count, bool log,
u32 depth)
{
const struct nlattr *attrs[OVS_DEC_TTL_ATTR_MAX + 1];
int start, action_start, err, rem;
Expand Down Expand Up @@ -2660,7 +2665,8 @@ static int validate_and_copy_dec_ttl(struct net *net,
return action_start;

err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type,
vlan_tci, mpls_label_count, log);
vlan_tci, mpls_label_count, log,
depth + 1);
if (err)
return err;

Expand All @@ -2674,7 +2680,8 @@ static int validate_and_copy_clone(struct net *net,
const struct sw_flow_key *key,
struct sw_flow_actions **sfa,
__be16 eth_type, __be16 vlan_tci,
u32 mpls_label_count, bool log, bool last)
u32 mpls_label_count, bool log, bool last,
u32 depth)
{
int start, err;
u32 exec;
Expand All @@ -2694,7 +2701,8 @@ static int validate_and_copy_clone(struct net *net,
return err;

err = __ovs_nla_copy_actions(net, attr, key, sfa,
eth_type, vlan_tci, mpls_label_count, log);
eth_type, vlan_tci, mpls_label_count, log,
depth + 1);
if (err)
return err;

Expand Down Expand Up @@ -3063,7 +3071,7 @@ static int validate_and_copy_check_pkt_len(struct net *net,
struct sw_flow_actions **sfa,
__be16 eth_type, __be16 vlan_tci,
u32 mpls_label_count,
bool log, bool last)
bool log, bool last, u32 depth)
{
const struct nlattr *acts_if_greater, *acts_if_lesser_eq;
struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1];
Expand Down Expand Up @@ -3111,7 +3119,8 @@ static int validate_and_copy_check_pkt_len(struct net *net,
return nested_acts_start;

err = __ovs_nla_copy_actions(net, acts_if_lesser_eq, key, sfa,
eth_type, vlan_tci, mpls_label_count, log);
eth_type, vlan_tci, mpls_label_count, log,
depth + 1);

if (err)
return err;
Expand All @@ -3124,7 +3133,8 @@ static int validate_and_copy_check_pkt_len(struct net *net,
return nested_acts_start;

err = __ovs_nla_copy_actions(net, acts_if_greater, key, sfa,
eth_type, vlan_tci, mpls_label_count, log);
eth_type, vlan_tci, mpls_label_count, log,
depth + 1);

if (err)
return err;
Expand Down Expand Up @@ -3152,12 +3162,16 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
const struct sw_flow_key *key,
struct sw_flow_actions **sfa,
__be16 eth_type, __be16 vlan_tci,
u32 mpls_label_count, bool log)
u32 mpls_label_count, bool log,
u32 depth)
{
u8 mac_proto = ovs_key_mac_proto(key);
const struct nlattr *a;
int rem, err;

if (depth > OVS_COPY_ACTIONS_MAX_DEPTH)
return -EOVERFLOW;

nla_for_each_nested(a, attr, rem) {
/* Expected argument lengths, (u32)-1 for variable length. */
static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
Expand Down Expand Up @@ -3355,7 +3369,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
err = validate_and_copy_sample(net, a, key, sfa,
eth_type, vlan_tci,
mpls_label_count,
log, last);
log, last, depth);
if (err)
return err;
skip_copy = true;
Expand Down Expand Up @@ -3426,7 +3440,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
err = validate_and_copy_clone(net, a, key, sfa,
eth_type, vlan_tci,
mpls_label_count,
log, last);
log, last, depth);
if (err)
return err;
skip_copy = true;
Expand All @@ -3440,7 +3454,8 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
eth_type,
vlan_tci,
mpls_label_count,
log, last);
log, last,
depth);
if (err)
return err;
skip_copy = true;
Expand All @@ -3450,7 +3465,8 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
case OVS_ACTION_ATTR_DEC_TTL:
err = validate_and_copy_dec_ttl(net, a, key, sfa,
eth_type, vlan_tci,
mpls_label_count, log);
mpls_label_count, log,
depth);
if (err)
return err;
skip_copy = true;
Expand Down Expand Up @@ -3495,7 +3511,8 @@ int ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,

(*sfa)->orig_len = nla_len(attr);
err = __ovs_nla_copy_actions(net, attr, key, sfa, key->eth.type,
key->eth.vlan.tci, mpls_label_count, log);
key->eth.vlan.tci, mpls_label_count, log,
0);
if (err)
ovs_nla_free_flow_actions(*sfa);

Expand Down
13 changes: 13 additions & 0 deletions tools/testing/selftests/net/openvswitch/openvswitch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,20 @@ test_netlink_checks () {
wc -l) == 2 ] || \
return 1

info "Checking clone depth"
ERR_MSG="Flow actions may not be safe on all matching packets"
PRE_TEST=$(dmesg | grep -c "${ERR_MSG}")
ovs_add_flow "test_netlink_checks" nv0 \
'in_port(1),eth(),eth_type(0x800),ipv4()' \
'clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(clone(drop)))))))))))))))))' \
>/dev/null 2>&1 && return 1
POST_TEST=$(dmesg | grep -c "${ERR_MSG}")

if [ "$PRE_TEST" == "$POST_TEST" ]; then
info "failed - clone depth too large"
return 1
fi

PRE_TEST=$(dmesg | grep -c "${ERR_MSG}")
ovs_add_flow "test_netlink_checks" nv0 \
'in_port(1),eth(),eth_type(0x0806),arp()' 'drop(0),2' \
Expand Down
71 changes: 56 additions & 15 deletions tools/testing/selftests/net/openvswitch/ovs-dpctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ class ovsactions(nla):
("OVS_ACTION_ATTR_PUSH_NSH", "none"),
("OVS_ACTION_ATTR_POP_NSH", "flag"),
("OVS_ACTION_ATTR_METER", "none"),
("OVS_ACTION_ATTR_CLONE", "none"),
("OVS_ACTION_ATTR_CLONE", "recursive"),
("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"),
("OVS_ACTION_ATTR_ADD_MPLS", "none"),
("OVS_ACTION_ATTR_DEC_TTL", "none"),
Expand Down Expand Up @@ -465,29 +465,42 @@ def dpstr(self, more=False):
print_str += "pop_mpls"
else:
datum = self.get_attr(field[0])
print_str += datum.dpstr(more)
if field[0] == "OVS_ACTION_ATTR_CLONE":
print_str += "clone("
print_str += datum.dpstr(more)
print_str += ")"
else:
print_str += datum.dpstr(more)

return print_str

def parse(self, actstr):
totallen = len(actstr)
while len(actstr) != 0:
parsed = False
parencount = 0
if actstr.startswith("drop"):
# If no reason is provided, the implicit drop is used (i.e no
# action). If some reason is given, an explicit action is used.
actstr, reason = parse_extract_field(
actstr,
"drop(",
"([0-9]+)",
lambda x: int(x, 0),
False,
None,
)
reason = None
if actstr.startswith("drop("):
parencount += 1

actstr, reason = parse_extract_field(
actstr,
"drop(",
"([0-9]+)",
lambda x: int(x, 0),
False,
None,
)

if reason is not None:
self["attrs"].append(["OVS_ACTION_ATTR_DROP", reason])
parsed = True
else:
return
actstr = actstr[len("drop"): ]
return (totallen - len(actstr))

elif parse_starts_block(actstr, "^(\d+)", False, True):
actstr, output = parse_extract_field(
Expand All @@ -504,6 +517,7 @@ def parse(self, actstr):
False,
0,
)
parencount += 1
self["attrs"].append(["OVS_ACTION_ATTR_RECIRC", recircid])
parsed = True

Expand All @@ -516,12 +530,22 @@ def parse(self, actstr):

for flat_act in parse_flat_map:
if parse_starts_block(actstr, flat_act[0], False):
actstr += len(flat_act[0])
actstr = actstr[len(flat_act[0]):]
self["attrs"].append([flat_act[1]])
actstr = actstr[strspn(actstr, ", ") :]
parsed = True

if parse_starts_block(actstr, "ct(", False):
if parse_starts_block(actstr, "clone(", False):
parencount += 1
subacts = ovsactions()
actstr = actstr[len("clone("):]
parsedLen = subacts.parse(actstr)
lst = []
self["attrs"].append(("OVS_ACTION_ATTR_CLONE", subacts))
actstr = actstr[parsedLen:]
parsed = True
elif parse_starts_block(actstr, "ct(", False):
parencount += 1
actstr = actstr[len("ct(") :]
ctact = ovsactions.ctact()

Expand Down Expand Up @@ -553,6 +577,7 @@ def parse(self, actstr):
natact = ovsactions.ctact.natattr()

if actstr.startswith("("):
parencount += 1
t = None
actstr = actstr[1:]
if actstr.startswith("src"):
Expand Down Expand Up @@ -607,15 +632,29 @@ def parse(self, actstr):
actstr = actstr[strspn(actstr, ", ") :]

ctact["attrs"].append(["OVS_CT_ATTR_NAT", natact])
actstr = actstr[strspn(actstr, ",) ") :]
actstr = actstr[strspn(actstr, ", ") :]

self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact])
parsed = True

actstr = actstr[strspn(actstr, "), ") :]
actstr = actstr[strspn(actstr, ", ") :]
while parencount > 0:
parencount -= 1
actstr = actstr[strspn(actstr, " "):]
if len(actstr) and actstr[0] != ")":
raise ValueError("Action str: '%s' unbalanced" % actstr)
actstr = actstr[1:]

if len(actstr) and actstr[0] == ")":
return (totallen - len(actstr))

actstr = actstr[strspn(actstr, ", ") :]

if not parsed:
raise ValueError("Action str: '%s' not supported" % actstr)

return (totallen - len(actstr))


class ovskey(nla):
nla_flags = NLA_F_NESTED
Expand Down Expand Up @@ -2111,6 +2150,8 @@ def main(argv):
ovsflow = OvsFlow()
ndb = NDB()

sys.setrecursionlimit(100000)

if hasattr(args, "showdp"):
found = False
for iface in ndb.interfaces:
Expand Down

0 comments on commit 6a12401

Please sign in to comment.