Merge branch 'net-openvswitch-limit-the-recursions-from-action-sets'
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/20240207132416.1488485-1-aconole@redhat.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
6a12401b65
@ -48,6 +48,7 @@ struct ovs_len_tbl {
|
|||||||
|
|
||||||
#define OVS_ATTR_NESTED -1
|
#define OVS_ATTR_NESTED -1
|
||||||
#define OVS_ATTR_VARIABLE -2
|
#define OVS_ATTR_VARIABLE -2
|
||||||
|
#define OVS_COPY_ACTIONS_MAX_DEPTH 16
|
||||||
|
|
||||||
static bool actions_may_change_flow(const struct nlattr *actions)
|
static bool actions_may_change_flow(const struct nlattr *actions)
|
||||||
{
|
{
|
||||||
@ -2545,13 +2546,15 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
|
|||||||
const struct sw_flow_key *key,
|
const struct sw_flow_key *key,
|
||||||
struct sw_flow_actions **sfa,
|
struct sw_flow_actions **sfa,
|
||||||
__be16 eth_type, __be16 vlan_tci,
|
__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,
|
static int validate_and_copy_sample(struct net *net, const struct nlattr *attr,
|
||||||
const struct sw_flow_key *key,
|
const struct sw_flow_key *key,
|
||||||
struct sw_flow_actions **sfa,
|
struct sw_flow_actions **sfa,
|
||||||
__be16 eth_type, __be16 vlan_tci,
|
__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 *attrs[OVS_SAMPLE_ATTR_MAX + 1];
|
||||||
const struct nlattr *probability, *actions;
|
const struct nlattr *probability, *actions;
|
||||||
@ -2602,7 +2605,8 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr,
|
|||||||
return err;
|
return err;
|
||||||
|
|
||||||
err = __ovs_nla_copy_actions(net, actions, key, sfa,
|
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)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@ -2617,7 +2621,8 @@ static int validate_and_copy_dec_ttl(struct net *net,
|
|||||||
const struct sw_flow_key *key,
|
const struct sw_flow_key *key,
|
||||||
struct sw_flow_actions **sfa,
|
struct sw_flow_actions **sfa,
|
||||||
__be16 eth_type, __be16 vlan_tci,
|
__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];
|
const struct nlattr *attrs[OVS_DEC_TTL_ATTR_MAX + 1];
|
||||||
int start, action_start, err, rem;
|
int start, action_start, err, rem;
|
||||||
@ -2660,7 +2665,8 @@ static int validate_and_copy_dec_ttl(struct net *net,
|
|||||||
return action_start;
|
return action_start;
|
||||||
|
|
||||||
err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type,
|
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)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
@ -2674,7 +2680,8 @@ static int validate_and_copy_clone(struct net *net,
|
|||||||
const struct sw_flow_key *key,
|
const struct sw_flow_key *key,
|
||||||
struct sw_flow_actions **sfa,
|
struct sw_flow_actions **sfa,
|
||||||
__be16 eth_type, __be16 vlan_tci,
|
__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;
|
int start, err;
|
||||||
u32 exec;
|
u32 exec;
|
||||||
@ -2694,7 +2701,8 @@ static int validate_and_copy_clone(struct net *net,
|
|||||||
return err;
|
return err;
|
||||||
|
|
||||||
err = __ovs_nla_copy_actions(net, attr, key, sfa,
|
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)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
@ -3063,7 +3071,7 @@ static int validate_and_copy_check_pkt_len(struct net *net,
|
|||||||
struct sw_flow_actions **sfa,
|
struct sw_flow_actions **sfa,
|
||||||
__be16 eth_type, __be16 vlan_tci,
|
__be16 eth_type, __be16 vlan_tci,
|
||||||
u32 mpls_label_count,
|
u32 mpls_label_count,
|
||||||
bool log, bool last)
|
bool log, bool last, u32 depth)
|
||||||
{
|
{
|
||||||
const struct nlattr *acts_if_greater, *acts_if_lesser_eq;
|
const struct nlattr *acts_if_greater, *acts_if_lesser_eq;
|
||||||
struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1];
|
struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1];
|
||||||
@ -3111,7 +3119,8 @@ static int validate_and_copy_check_pkt_len(struct net *net,
|
|||||||
return nested_acts_start;
|
return nested_acts_start;
|
||||||
|
|
||||||
err = __ovs_nla_copy_actions(net, acts_if_lesser_eq, key, sfa,
|
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)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@ -3124,7 +3133,8 @@ static int validate_and_copy_check_pkt_len(struct net *net,
|
|||||||
return nested_acts_start;
|
return nested_acts_start;
|
||||||
|
|
||||||
err = __ovs_nla_copy_actions(net, acts_if_greater, key, sfa,
|
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)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@ -3152,12 +3162,16 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
|
|||||||
const struct sw_flow_key *key,
|
const struct sw_flow_key *key,
|
||||||
struct sw_flow_actions **sfa,
|
struct sw_flow_actions **sfa,
|
||||||
__be16 eth_type, __be16 vlan_tci,
|
__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);
|
u8 mac_proto = ovs_key_mac_proto(key);
|
||||||
const struct nlattr *a;
|
const struct nlattr *a;
|
||||||
int rem, err;
|
int rem, err;
|
||||||
|
|
||||||
|
if (depth > OVS_COPY_ACTIONS_MAX_DEPTH)
|
||||||
|
return -EOVERFLOW;
|
||||||
|
|
||||||
nla_for_each_nested(a, attr, rem) {
|
nla_for_each_nested(a, attr, rem) {
|
||||||
/* Expected argument lengths, (u32)-1 for variable length. */
|
/* Expected argument lengths, (u32)-1 for variable length. */
|
||||||
static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
|
static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
|
||||||
@ -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,
|
err = validate_and_copy_sample(net, a, key, sfa,
|
||||||
eth_type, vlan_tci,
|
eth_type, vlan_tci,
|
||||||
mpls_label_count,
|
mpls_label_count,
|
||||||
log, last);
|
log, last, depth);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
skip_copy = true;
|
skip_copy = true;
|
||||||
@ -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,
|
err = validate_and_copy_clone(net, a, key, sfa,
|
||||||
eth_type, vlan_tci,
|
eth_type, vlan_tci,
|
||||||
mpls_label_count,
|
mpls_label_count,
|
||||||
log, last);
|
log, last, depth);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
skip_copy = true;
|
skip_copy = true;
|
||||||
@ -3440,7 +3454,8 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
|
|||||||
eth_type,
|
eth_type,
|
||||||
vlan_tci,
|
vlan_tci,
|
||||||
mpls_label_count,
|
mpls_label_count,
|
||||||
log, last);
|
log, last,
|
||||||
|
depth);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
skip_copy = true;
|
skip_copy = true;
|
||||||
@ -3450,7 +3465,8 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
|
|||||||
case OVS_ACTION_ATTR_DEC_TTL:
|
case OVS_ACTION_ATTR_DEC_TTL:
|
||||||
err = validate_and_copy_dec_ttl(net, a, key, sfa,
|
err = validate_and_copy_dec_ttl(net, a, key, sfa,
|
||||||
eth_type, vlan_tci,
|
eth_type, vlan_tci,
|
||||||
mpls_label_count, log);
|
mpls_label_count, log,
|
||||||
|
depth);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
skip_copy = true;
|
skip_copy = true;
|
||||||
@ -3495,7 +3511,8 @@ int ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
|
|||||||
|
|
||||||
(*sfa)->orig_len = nla_len(attr);
|
(*sfa)->orig_len = nla_len(attr);
|
||||||
err = __ovs_nla_copy_actions(net, attr, key, sfa, key->eth.type,
|
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)
|
if (err)
|
||||||
ovs_nla_free_flow_actions(*sfa);
|
ovs_nla_free_flow_actions(*sfa);
|
||||||
|
|
||||||
|
@ -502,7 +502,20 @@ test_netlink_checks () {
|
|||||||
wc -l) == 2 ] || \
|
wc -l) == 2 ] || \
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
info "Checking clone depth"
|
||||||
ERR_MSG="Flow actions may not be safe on all matching packets"
|
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}")
|
PRE_TEST=$(dmesg | grep -c "${ERR_MSG}")
|
||||||
ovs_add_flow "test_netlink_checks" nv0 \
|
ovs_add_flow "test_netlink_checks" nv0 \
|
||||||
'in_port(1),eth(),eth_type(0x0806),arp()' 'drop(0),2' \
|
'in_port(1),eth(),eth_type(0x0806),arp()' 'drop(0),2' \
|
||||||
|
@ -299,7 +299,7 @@ class ovsactions(nla):
|
|||||||
("OVS_ACTION_ATTR_PUSH_NSH", "none"),
|
("OVS_ACTION_ATTR_PUSH_NSH", "none"),
|
||||||
("OVS_ACTION_ATTR_POP_NSH", "flag"),
|
("OVS_ACTION_ATTR_POP_NSH", "flag"),
|
||||||
("OVS_ACTION_ATTR_METER", "none"),
|
("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_CHECK_PKT_LEN", "none"),
|
||||||
("OVS_ACTION_ATTR_ADD_MPLS", "none"),
|
("OVS_ACTION_ATTR_ADD_MPLS", "none"),
|
||||||
("OVS_ACTION_ATTR_DEC_TTL", "none"),
|
("OVS_ACTION_ATTR_DEC_TTL", "none"),
|
||||||
@ -465,29 +465,42 @@ class ovsactions(nla):
|
|||||||
print_str += "pop_mpls"
|
print_str += "pop_mpls"
|
||||||
else:
|
else:
|
||||||
datum = self.get_attr(field[0])
|
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
|
return print_str
|
||||||
|
|
||||||
def parse(self, actstr):
|
def parse(self, actstr):
|
||||||
|
totallen = len(actstr)
|
||||||
while len(actstr) != 0:
|
while len(actstr) != 0:
|
||||||
parsed = False
|
parsed = False
|
||||||
|
parencount = 0
|
||||||
if actstr.startswith("drop"):
|
if actstr.startswith("drop"):
|
||||||
# If no reason is provided, the implicit drop is used (i.e no
|
# If no reason is provided, the implicit drop is used (i.e no
|
||||||
# action). If some reason is given, an explicit action is used.
|
# action). If some reason is given, an explicit action is used.
|
||||||
actstr, reason = parse_extract_field(
|
reason = None
|
||||||
actstr,
|
if actstr.startswith("drop("):
|
||||||
"drop(",
|
parencount += 1
|
||||||
"([0-9]+)",
|
|
||||||
lambda x: int(x, 0),
|
actstr, reason = parse_extract_field(
|
||||||
False,
|
actstr,
|
||||||
None,
|
"drop(",
|
||||||
)
|
"([0-9]+)",
|
||||||
|
lambda x: int(x, 0),
|
||||||
|
False,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
if reason is not None:
|
if reason is not None:
|
||||||
self["attrs"].append(["OVS_ACTION_ATTR_DROP", reason])
|
self["attrs"].append(["OVS_ACTION_ATTR_DROP", reason])
|
||||||
parsed = True
|
parsed = True
|
||||||
else:
|
else:
|
||||||
return
|
actstr = actstr[len("drop"): ]
|
||||||
|
return (totallen - len(actstr))
|
||||||
|
|
||||||
elif parse_starts_block(actstr, "^(\d+)", False, True):
|
elif parse_starts_block(actstr, "^(\d+)", False, True):
|
||||||
actstr, output = parse_extract_field(
|
actstr, output = parse_extract_field(
|
||||||
@ -504,6 +517,7 @@ class ovsactions(nla):
|
|||||||
False,
|
False,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
parencount += 1
|
||||||
self["attrs"].append(["OVS_ACTION_ATTR_RECIRC", recircid])
|
self["attrs"].append(["OVS_ACTION_ATTR_RECIRC", recircid])
|
||||||
parsed = True
|
parsed = True
|
||||||
|
|
||||||
@ -516,12 +530,22 @@ class ovsactions(nla):
|
|||||||
|
|
||||||
for flat_act in parse_flat_map:
|
for flat_act in parse_flat_map:
|
||||||
if parse_starts_block(actstr, flat_act[0], False):
|
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]])
|
self["attrs"].append([flat_act[1]])
|
||||||
actstr = actstr[strspn(actstr, ", ") :]
|
actstr = actstr[strspn(actstr, ", ") :]
|
||||||
parsed = True
|
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(") :]
|
actstr = actstr[len("ct(") :]
|
||||||
ctact = ovsactions.ctact()
|
ctact = ovsactions.ctact()
|
||||||
|
|
||||||
@ -553,6 +577,7 @@ class ovsactions(nla):
|
|||||||
natact = ovsactions.ctact.natattr()
|
natact = ovsactions.ctact.natattr()
|
||||||
|
|
||||||
if actstr.startswith("("):
|
if actstr.startswith("("):
|
||||||
|
parencount += 1
|
||||||
t = None
|
t = None
|
||||||
actstr = actstr[1:]
|
actstr = actstr[1:]
|
||||||
if actstr.startswith("src"):
|
if actstr.startswith("src"):
|
||||||
@ -607,15 +632,29 @@ class ovsactions(nla):
|
|||||||
actstr = actstr[strspn(actstr, ", ") :]
|
actstr = actstr[strspn(actstr, ", ") :]
|
||||||
|
|
||||||
ctact["attrs"].append(["OVS_CT_ATTR_NAT", natact])
|
ctact["attrs"].append(["OVS_CT_ATTR_NAT", natact])
|
||||||
actstr = actstr[strspn(actstr, ",) ") :]
|
actstr = actstr[strspn(actstr, ", ") :]
|
||||||
|
|
||||||
self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact])
|
self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact])
|
||||||
parsed = True
|
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:
|
if not parsed:
|
||||||
raise ValueError("Action str: '%s' not supported" % actstr)
|
raise ValueError("Action str: '%s' not supported" % actstr)
|
||||||
|
|
||||||
|
return (totallen - len(actstr))
|
||||||
|
|
||||||
|
|
||||||
class ovskey(nla):
|
class ovskey(nla):
|
||||||
nla_flags = NLA_F_NESTED
|
nla_flags = NLA_F_NESTED
|
||||||
@ -2111,6 +2150,8 @@ def main(argv):
|
|||||||
ovsflow = OvsFlow()
|
ovsflow = OvsFlow()
|
||||||
ndb = NDB()
|
ndb = NDB()
|
||||||
|
|
||||||
|
sys.setrecursionlimit(100000)
|
||||||
|
|
||||||
if hasattr(args, "showdp"):
|
if hasattr(args, "showdp"):
|
||||||
found = False
|
found = False
|
||||||
for iface in ndb.interfaces:
|
for iface in ndb.interfaces:
|
||||||
|
Loading…
Reference in New Issue
Block a user