On 6/6/23 11:38, Frode Nordahl wrote:
> The tc module combines the use of the `tc_transact` helper
> function for communication with the in-kernel tc infrastructure
> with assertions on the reply data by `ofpbuf_at_assert` on the
> received data prior to further processing.
> 
> With the presence of bugs on the kernel side, we need to treat
> the kernel as an unreliable service provider and replace assertions
> on the reply from it with checks to avoid a fatal crash of OVS.
> 
> For the record, the symptom of the crash is this in the log:
> EMER|../include/openvswitch/ofpbuf.h:194: assertion offset + size <= b->size 
> failed in ofpbuf_at_assert()
> 
> And an excerpt of the backtrace looks like this:
> 0x0000561dac1396d1 in ofpbuf_at_assert (b=0x7fb650005d20, b=0x7fb650005d20, 
> offset=16, size=20) at ../include/openvswitch/ofpbuf.h:194
> tc_replace_flower (id=<optimized out>, flower=<optimized out>) at 
> ../lib/tc.c:3223
> 0x0000561dac128155 in netdev_tc_flow_put (netdev=0x561dacf91840, 
> match=<optimized out>, actions=<optimized out>, actions_len=<optimized out>,
> ufid=<optimized out>, info=<optimized out>, stats=<optimized out>) at 
> ../lib/netdev-offload-tc.c:2096
> 0x0000561dac117541 in netdev_flow_put (stats=<optimized out>, 
> info=0x7fb65b7ba780, ufid=<optimized out>, act_len=<optimized out>, 
> actions=<optimized out>,
> match=0x7fb65b7ba980, netdev=0x561dacf91840) at ../lib/netdev-offload.c:257
> parse_flow_put (put=0x7fb65b7bcc50, dpif=0x561dad0ad550) at 
> ../lib/dpif-netlink.c:2297
> try_send_to_netdev (op=0x7fb65b7bcc48, dpif=0x561dad0ad550) at 
> ../lib/dpif-netlink.c:2384
> 
> Reported-At: https://launchpad.net/bugs/2018500
> Fixes: 5c039ddc64ff ("netdev-linux: Add functions to manipulate tc police 
> action")
> Fixes: e7f6ba220e10 ("lib/tc: add ingress ratelimiting support for 
> tc-offload")
> Fixes: f98e418fbdb6 ("tc: Add tc flower functions")
> Fixes: c1c9c9c4b636 ("Implement QoS framework.")
> Signed-off-by: Frode Nordahl <[email protected]>
> ---
>  lib/netdev-linux.c | 32 +++++++++++++++++++++---------
>  lib/tc.c           | 49 ++++++++++++++++++++++++++++++++--------------
>  2 files changed, 57 insertions(+), 24 deletions(-)
> 
> diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
> index 36620199e..1efc837ac 100644
> --- a/lib/netdev-linux.c
> +++ b/lib/netdev-linux.c
> @@ -2714,8 +2714,15 @@ tc_add_matchall_policer(struct netdev *netdev, 
> uint32_t kbits_rate,
>  
>      err = tc_transact(&request, &reply);
>      if (!err) {
> -        struct tcmsg *tc =
> -            ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tc);
> +        struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
> +        struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
> +        struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
> +
> +        if (!nlmsg || !tc) {
> +            VLOG_ERR_RL(&rl, "Failed to add match all policer, "
> +                        "malformed reply");

We will leak a 'reply' here if this condition ever happens.
But I'm still not sure what is the point of having and parsing
a reply for this request at all.  If it didn't fail, then we
can assume it succeeded?

We can't assume that cls_flower is working correctly, it has
issues.  Even more on older kernels.  That's why we request
to echo everythig back and comparing that kernel configured
what we asked.  I hope, we can trust at least some of the
interfaces and don't need to re-check everything.

Are there any known issues with the matchall classifier?
If not, I'd suggest we just don't ask for reply.  An error
code should be enough.

> +            return EPROTO;
> +        }
>          ofpbuf_delete(reply);
>      }
>  
> @@ -5744,26 +5751,27 @@ static int
>  tc_update_policer_action_stats(struct ofpbuf *msg,
>                                 struct ofputil_meter_stats *stats)
>  {
> +    struct ofpbuf b = ofpbuf_const_initializer(msg->data, msg->size);
> +    struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
> +    struct tcamsg *tca = ofpbuf_try_pull(&b, sizeof *tca);
>      struct ovs_flow_stats stats_dropped;
>      struct ovs_flow_stats stats_hw;
>      struct ovs_flow_stats stats_sw;
>      const struct nlattr *act;
>      struct nlattr *prio;
> -    struct tcamsg *tca;
>      int error = 0;
>  
>      if (!stats) {
>          goto exit;
>      }
>  
> -    if (NLMSG_HDRLEN + sizeof *tca > msg->size) {
> +    if (!nlmsg || !tca) {
>          VLOG_ERR_RL(&rl, "Failed to get action stats, size error");
>          error = EPROTO;
>          goto exit;
>      }
>  
> -    tca = ofpbuf_at_assert(msg, NLMSG_HDRLEN, sizeof *tca);
> -    act = nl_attr_find(msg, NLMSG_HDRLEN + sizeof *tca, TCA_ACT_TAB);
> +    act = nl_attr_find(&b, 0, TCA_ACT_TAB);
>      if (!act) {
>          VLOG_ERR_RL(&rl, "Failed to get action stats, can't find attribute");
>          error = EPROTO;
> @@ -6028,20 +6036,26 @@ static int
>  tc_parse_class(const struct ofpbuf *msg, unsigned int *handlep,
>                 struct nlattr **options, struct netdev_queue_stats *stats)
>  {
> +    struct ofpbuf b = ofpbuf_const_initializer(msg->data, msg->size);
> +    struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
> +    struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
>      static const struct nl_policy tca_policy[] = {
>          [TCA_OPTIONS] = { .type = NL_A_NESTED, .optional = false },
>          [TCA_STATS2] = { .type = NL_A_NESTED, .optional = false },
>      };
>      struct nlattr *ta[ARRAY_SIZE(tca_policy)];
>  
> -    if (!nl_policy_parse(msg, NLMSG_HDRLEN + sizeof(struct tcmsg),
> -                         tca_policy, ta, ARRAY_SIZE(ta))) {
> +    if (!nlmsg || !tc) {
> +        VLOG_ERR_RL(&rl, "failed to parse class message, malformed reply");
> +        goto error;
> +    }
> +
> +    if (!nl_policy_parse(&b, 0, tca_policy, ta, ARRAY_SIZE(ta))) {
>          VLOG_WARN_RL(&rl, "failed to parse class message");
>          goto error;
>      }
>  
>      if (handlep) {
> -        struct tcmsg *tc = ofpbuf_at_assert(msg, NLMSG_HDRLEN, sizeof *tc);
>          *handlep = tc->tcm_handle;
>      }
>  
> diff --git a/lib/tc.c b/lib/tc.c
> index 5c32c6f97..07975f9db 100644
> --- a/lib/tc.c
> +++ b/lib/tc.c
> @@ -36,6 +36,7 @@
>  #include <unistd.h>
>  
>  #include "byte-order.h"
> +#include "coverage.h"
>  #include "netlink-socket.h"
>  #include "netlink.h"
>  #include "openvswitch/ofpbuf.h"
> @@ -67,6 +68,8 @@
>  
>  VLOG_DEFINE_THIS_MODULE(tc);
>  
> +COVERAGE_DEFINE(tc_netlink_malformed_reply);
> +
>  static struct vlog_rate_limit error_rl = VLOG_RATE_LIMIT_INIT(60, 5);
>  
>  static enum tc_offload_policy tc_policy = TC_POLICY_NONE;
> @@ -2190,18 +2193,19 @@ int
>  parse_netlink_to_tc_flower(struct ofpbuf *reply, struct tcf_id *id,
>                             struct tc_flower *flower, bool terse)
>  {
> -    struct tcmsg *tc;
> +    struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
> +    struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
> +    struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
>      struct nlattr *ta[ARRAY_SIZE(tca_policy)];
>      const char *kind;
>  
> -    if (NLMSG_HDRLEN + sizeof *tc > reply->size) {
> +    if (!nlmsg || !tc) {
> +        COVERAGE_INC(tc_netlink_malformed_reply);
>          return EPROTO;
>      }
>  
>      memset(flower, 0, sizeof *flower);
>  
> -    tc = ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tc);
> -
>      flower->key.eth_type = (OVS_FORCE ovs_be16) tc_get_minor(tc->tcm_info);
>      flower->mask.eth_type = OVS_BE16_MAX;
>      id->prio = tc_get_major(tc->tcm_info);
> @@ -2215,8 +2219,7 @@ parse_netlink_to_tc_flower(struct ofpbuf *reply, struct 
> tcf_id *id,
>          return EAGAIN;
>      }
>  
> -    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof *tc,
> -                         tca_policy, ta, ARRAY_SIZE(ta))) {
> +    if (!nl_policy_parse(&b, 0, tca_policy, ta, ARRAY_SIZE(ta))) {
>          VLOG_ERR_RL(&error_rl, "failed to parse tca policy");
>          return EPROTO;
>      }
> @@ -2237,13 +2240,17 @@ parse_netlink_to_tc_flower(struct ofpbuf *reply, 
> struct tcf_id *id,
>  int
>  parse_netlink_to_tc_chain(struct ofpbuf *reply, uint32_t *chain)
>  {
> +    struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
> +    struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
> +    struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
>      struct nlattr *ta[ARRAY_SIZE(tca_chain_policy)];
> -    struct tcmsg *tc;
>  
> -    tc = ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tc);
> +    if (!nlmsg || !tc) {
> +        COVERAGE_INC(tc_netlink_malformed_reply);
> +        return EPROTO;
> +    }
>  
> -    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof *tc,
> -                         tca_chain_policy, ta, ARRAY_SIZE(ta))) {
> +    if (!nl_policy_parse(&b, 0, tca_chain_policy, ta, ARRAY_SIZE(ta))) {
>          VLOG_ERR_RL(&error_rl, "failed to parse tca chain policy");
>          return EINVAL;
>      }
> @@ -2307,21 +2314,27 @@ int
>  parse_netlink_to_tc_policer(struct ofpbuf *reply, uint32_t police_idx[])
>  {
>      static struct nl_policy actions_orders_policy[TCA_ACT_MAX_PRIO] = {};
> +    struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
>      struct nlattr *actions_orders[ARRAY_SIZE(actions_orders_policy)];
> +    struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
>      const int max_size = ARRAY_SIZE(actions_orders_policy);
> +    struct tcamsg *tca = ofpbuf_try_pull(&b, sizeof *tca);
>      const struct nlattr *actions;
>      struct tc_flower flower;
> -    struct tcamsg *tca;
>      int i, cnt = 0;
>      int err;
>  
> +    if (!nlmsg || !tca) {
> +        COVERAGE_INC(tc_netlink_malformed_reply);
> +        return EPROTO;
> +    }
> +
>      for (i = 0; i < max_size; i++) {
>          actions_orders_policy[i].type = NL_A_NESTED;
>          actions_orders_policy[i].optional = true;
>      }
>  
> -    tca = ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tca);
> -    actions = nl_attr_find(reply, NLMSG_HDRLEN + sizeof *tca, TCA_ACT_TAB);
> +    actions = nl_attr_find(&b, 0, TCA_ACT_TAB);
>      if (!actions || !nl_parse_nested(actions, actions_orders_policy,
>                                       actions_orders, max_size)) {
>          VLOG_ERR_RL(&error_rl,
> @@ -3823,8 +3836,14 @@ tc_replace_flower(struct tcf_id *id, struct tc_flower 
> *flower)
>  
>      error = tc_transact(&request, &reply);
>      if (!error) {
> -        struct tcmsg *tc =
> -            ofpbuf_at_assert(reply, NLMSG_HDRLEN, sizeof *tc);
> +        struct ofpbuf b = ofpbuf_const_initializer(reply->data, reply->size);
> +        struct nlmsghdr *nlmsg = ofpbuf_try_pull(&b, sizeof *nlmsg);
> +        struct tcmsg *tc = ofpbuf_try_pull(&b, sizeof *tc);
> +
> +        if (!nlmsg || !tc) {
> +            COVERAGE_INC(tc_netlink_malformed_reply);

We're leaking a reply here.

> +            return EPROTO;
> +        }
>  
>          id->prio = tc_get_major(tc->tcm_info);
>          id->handle = tc->tcm_handle;

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

Reply via email to