diff mbox

[ovs-dev,01/41] ofproto: Fix memory leak and memory exhaustion bugs in group_mod.

Message ID 1453188448-2437-2-git-send-email-blp@ovn.org
State Accepted
Headers show

Commit Message

Ben Pfaff Jan. 19, 2016, 7:26 a.m. UTC
In handle_group_mod() cases where adding a group failed, nothing freed the
list of buckets, causing a leak.  The same was true in every case of
modifying a group.  This commit fixes the problem by changing add_group()
to never steal or free the buckets (modify_group() already acted this way)
and then making handle_group_mod() always free the buckets when it's done.

This approach might at first raise objections, because it makes add_group()
copy the buckets instead of just take the existing ones.  But it actually
fixes a worse problem too: when OF1.4+ REQUESTFORWARD is enabled, the
group_mod is reused for the request forwarding.  Until now, for a group_mod
that adds a new group and that has some buckets, the previous stealing of
buckets in add_group() meant that the group_mod's buckets were no longer
valid; in practice, the list of buckets became linked in a way that
iteration never terminated, which caused memory to be exhausted while
composing the requestforward message.  By making add_group() no longer
modify the group_mod, we also fix this problem.

The requestforward test in the testsuite did not find the latter problem
because it only added a group without any buckets.  This commit also
updates the testsuite to include a bucket in its group_mod, which would
have found the problem.

Found by pain and suffering.

Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 ofproto/ofproto.c | 17 +++++++++++------
 tests/ofproto.at  | 16 ++++++++--------
 2 files changed, 19 insertions(+), 14 deletions(-)

Comments

Jarno Rajahalme Jan. 19, 2016, 7:25 p.m. UTC | #1
Acked-by: Jarno Rajahalme <jarno@ovn.org>

> On Jan 18, 2016, at 11:26 PM, Ben Pfaff <blp@ovn.org> wrote:
> 
> In handle_group_mod() cases where adding a group failed, nothing freed the
> list of buckets, causing a leak.  The same was true in every case of
> modifying a group.  This commit fixes the problem by changing add_group()
> to never steal or free the buckets (modify_group() already acted this way)
> and then making handle_group_mod() always free the buckets when it's done.
> 
> This approach might at first raise objections, because it makes add_group()
> copy the buckets instead of just take the existing ones.  But it actually
> fixes a worse problem too: when OF1.4+ REQUESTFORWARD is enabled, the
> group_mod is reused for the request forwarding.  Until now, for a group_mod
> that adds a new group and that has some buckets, the previous stealing of
> buckets in add_group() meant that the group_mod's buckets were no longer
> valid; in practice, the list of buckets became linked in a way that
> iteration never terminated, which caused memory to be exhausted while
> composing the requestforward message.  By making add_group() no longer
> modify the group_mod, we also fix this problem.
> 
> The requestforward test in the testsuite did not find the latter problem
> because it only added a group without any buckets.  This commit also
> updates the testsuite to include a bucket in its group_mod, which would
> have found the problem.
> 
> Found by pain and suffering.
> 
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
> ofproto/ofproto.c | 17 +++++++++++------
> tests/ofproto.at  | 16 ++++++++--------
> 2 files changed, 19 insertions(+), 14 deletions(-)
> 
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index 528d5ac..386009e 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -297,7 +297,8 @@ static bool ofproto_group_exists__(const struct ofproto *ofproto,
> static bool ofproto_group_exists(const struct ofproto *ofproto,
>                                  uint32_t group_id)
>     OVS_EXCLUDED(ofproto->groups_rwlock);
> -static enum ofperr add_group(struct ofproto *, struct ofputil_group_mod *);
> +static enum ofperr add_group(struct ofproto *,
> +                             const struct ofputil_group_mod *);
> static void handle_openflow(struct ofconn *, const struct ofpbuf *);
> static enum ofperr ofproto_flow_mod_start(struct ofproto *,
>                                           struct ofproto_flow_mod *)
> @@ -6280,7 +6281,7 @@ handle_queue_get_config_request(struct ofconn *ofconn,
> }
> 
> static enum ofperr
> -init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
> +init_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm,
>            struct ofgroup **ofgroup)
> {
>     enum ofperr error;
> @@ -6306,7 +6307,9 @@ init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
>     *CONST_CAST(long long int *, &((*ofgroup)->modified)) = now;
>     ovs_refcount_init(&(*ofgroup)->ref_count);
> 
> -    list_move(&(*ofgroup)->buckets, &gm->buckets);
> +    list_init(&(*ofgroup)->buckets);
> +    ofputil_bucket_clone_list(&(*ofgroup)->buckets, &gm->buckets, NULL);
> +
>     *CONST_CAST(uint32_t *, &(*ofgroup)->n_buckets) =
>         list_size(&(*ofgroup)->buckets);
> 
> @@ -6326,7 +6329,7 @@ init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
>  * 'ofproto''s group table.  Returns 0 on success or an OpenFlow error code on
>  * failure. */
> static enum ofperr
> -add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
> +add_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
> {
>     struct ofgroup *ofgroup;
>     enum ofperr error;
> @@ -6474,7 +6477,7 @@ copy_buckets_for_remove_bucket(const struct ofgroup *ofgroup,
>  * ofproto's ofgroup hash map. Thus, the group is never altered while users of
>  * the xlate module hold a pointer to the group. */
> static enum ofperr
> -modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
> +modify_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
> {
>     struct ofgroup *ofgroup, *new_ofgroup, *retiring;
>     enum ofperr error;
> @@ -6643,7 +6646,7 @@ handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
>             VLOG_INFO_RL(&rl, "%s: Invalid group_mod command type %d",
>                          ofproto->name, gm.command);
>         }
> -        return OFPERR_OFPGMFC_BAD_COMMAND;
> +        error = OFPERR_OFPGMFC_BAD_COMMAND;
>     }
> 
>     if (!error) {
> @@ -6653,6 +6656,8 @@ handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
>         rf.group_mod = &gm;
>         connmgr_send_requestforward(ofproto->connmgr, ofconn, &rf);
>     }
> +    ofputil_bucket_list_destroy(&gm.buckets);
> +
>     return error;
> }
> 
> diff --git a/tests/ofproto.at b/tests/ofproto.at
> index b3b8a0f..787def1 100644
> --- a/tests/ofproto.at
> +++ b/tests/ofproto.at
> @@ -3144,25 +3144,25 @@ check_async () {
>     shift
> 
>     # OFPGC_ADD
> -    ovs-appctl -t `pwd`/c2 ofctl/send 050f0010000000020000000000000001
> +    ovs-appctl -t `pwd`/c2 ofctl/send "050f0020000000020000000000000001 00100000 ffffffffffffffff 00000000"
>     if test X"$1" = X"OFPGC_ADD"; then shift;
>         echo >>expout2 "send: OFPT_GROUP_MOD (OF1.4):
> - ADD group_id=1,type=all"
> + ADD group_id=1,type=all,bucket=actions=drop"
>         echo >>expout1 "OFPT_REQUESTFORWARD (OF1.5): reason=group_mod
> - ADD group_id=1,type=all"
> + ADD group_id=1,type=all,bucket=bucket_id:0,actions=drop"
>         echo >>expout3 "OFPT_REQUESTFORWARD (OF1.4): reason=group_mod
> - ADD group_id=1,type=all"
> + ADD group_id=1,type=all,bucket=actions=drop"
>     fi
> 
>     # OFPGC_MODIFY
> -    ovs-appctl -t `pwd`/c2 ofctl/send 050f0010000000020001010000000001
> +    ovs-appctl -t `pwd`/c2 ofctl/send "050f0020000000020001010000000001 00100000 ffffffffffffffff 00000000"
>     if test X"$1" = X"OFPGC_MODIFY"; then shift;
>         echo >>expout2 "send: OFPT_GROUP_MOD (OF1.4):
> - MOD group_id=1,type=select"
> + MOD group_id=1,type=select,bucket=weight:0,actions=drop"
>         echo >>expout1 "OFPT_REQUESTFORWARD (OF1.5): reason=group_mod
> - MOD group_id=1,type=select"
> + MOD group_id=1,type=select,bucket=bucket_id:0,weight:0,actions=drop"
>         echo >>expout3 "OFPT_REQUESTFORWARD (OF1.4): reason=group_mod
> - MOD group_id=1,type=select"
> + MOD group_id=1,type=select,bucket=weight:0,actions=drop"
>     fi
> 
>     ovs-appctl -t `pwd`/c1 ofctl/barrier
> -- 
> 2.1.3
> 
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev
Ben Pfaff Jan. 20, 2016, 6:46 a.m. UTC | #2
Thanks.  I applied this to master, branch-2.5, and branch-2.4.

On Tue, Jan 19, 2016 at 11:25:03AM -0800, Jarno Rajahalme wrote:
> Acked-by: Jarno Rajahalme <jarno@ovn.org>
> 
> > On Jan 18, 2016, at 11:26 PM, Ben Pfaff <blp@ovn.org> wrote:
> > 
> > In handle_group_mod() cases where adding a group failed, nothing freed the
> > list of buckets, causing a leak.  The same was true in every case of
> > modifying a group.  This commit fixes the problem by changing add_group()
> > to never steal or free the buckets (modify_group() already acted this way)
> > and then making handle_group_mod() always free the buckets when it's done.
> > 
> > This approach might at first raise objections, because it makes add_group()
> > copy the buckets instead of just take the existing ones.  But it actually
> > fixes a worse problem too: when OF1.4+ REQUESTFORWARD is enabled, the
> > group_mod is reused for the request forwarding.  Until now, for a group_mod
> > that adds a new group and that has some buckets, the previous stealing of
> > buckets in add_group() meant that the group_mod's buckets were no longer
> > valid; in practice, the list of buckets became linked in a way that
> > iteration never terminated, which caused memory to be exhausted while
> > composing the requestforward message.  By making add_group() no longer
> > modify the group_mod, we also fix this problem.
> > 
> > The requestforward test in the testsuite did not find the latter problem
> > because it only added a group without any buckets.  This commit also
> > updates the testsuite to include a bucket in its group_mod, which would
> > have found the problem.
> > 
> > Found by pain and suffering.
> > 
> > Signed-off-by: Ben Pfaff <blp@ovn.org>
> > ---
> > ofproto/ofproto.c | 17 +++++++++++------
> > tests/ofproto.at  | 16 ++++++++--------
> > 2 files changed, 19 insertions(+), 14 deletions(-)
> > 
> > diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> > index 528d5ac..386009e 100644
> > --- a/ofproto/ofproto.c
> > +++ b/ofproto/ofproto.c
> > @@ -297,7 +297,8 @@ static bool ofproto_group_exists__(const struct ofproto *ofproto,
> > static bool ofproto_group_exists(const struct ofproto *ofproto,
> >                                  uint32_t group_id)
> >     OVS_EXCLUDED(ofproto->groups_rwlock);
> > -static enum ofperr add_group(struct ofproto *, struct ofputil_group_mod *);
> > +static enum ofperr add_group(struct ofproto *,
> > +                             const struct ofputil_group_mod *);
> > static void handle_openflow(struct ofconn *, const struct ofpbuf *);
> > static enum ofperr ofproto_flow_mod_start(struct ofproto *,
> >                                           struct ofproto_flow_mod *)
> > @@ -6280,7 +6281,7 @@ handle_queue_get_config_request(struct ofconn *ofconn,
> > }
> > 
> > static enum ofperr
> > -init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
> > +init_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm,
> >            struct ofgroup **ofgroup)
> > {
> >     enum ofperr error;
> > @@ -6306,7 +6307,9 @@ init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
> >     *CONST_CAST(long long int *, &((*ofgroup)->modified)) = now;
> >     ovs_refcount_init(&(*ofgroup)->ref_count);
> > 
> > -    list_move(&(*ofgroup)->buckets, &gm->buckets);
> > +    list_init(&(*ofgroup)->buckets);
> > +    ofputil_bucket_clone_list(&(*ofgroup)->buckets, &gm->buckets, NULL);
> > +
> >     *CONST_CAST(uint32_t *, &(*ofgroup)->n_buckets) =
> >         list_size(&(*ofgroup)->buckets);
> > 
> > @@ -6326,7 +6329,7 @@ init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
> >  * 'ofproto''s group table.  Returns 0 on success or an OpenFlow error code on
> >  * failure. */
> > static enum ofperr
> > -add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
> > +add_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
> > {
> >     struct ofgroup *ofgroup;
> >     enum ofperr error;
> > @@ -6474,7 +6477,7 @@ copy_buckets_for_remove_bucket(const struct ofgroup *ofgroup,
> >  * ofproto's ofgroup hash map. Thus, the group is never altered while users of
> >  * the xlate module hold a pointer to the group. */
> > static enum ofperr
> > -modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
> > +modify_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
> > {
> >     struct ofgroup *ofgroup, *new_ofgroup, *retiring;
> >     enum ofperr error;
> > @@ -6643,7 +6646,7 @@ handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
> >             VLOG_INFO_RL(&rl, "%s: Invalid group_mod command type %d",
> >                          ofproto->name, gm.command);
> >         }
> > -        return OFPERR_OFPGMFC_BAD_COMMAND;
> > +        error = OFPERR_OFPGMFC_BAD_COMMAND;
> >     }
> > 
> >     if (!error) {
> > @@ -6653,6 +6656,8 @@ handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
> >         rf.group_mod = &gm;
> >         connmgr_send_requestforward(ofproto->connmgr, ofconn, &rf);
> >     }
> > +    ofputil_bucket_list_destroy(&gm.buckets);
> > +
> >     return error;
> > }
> > 
> > diff --git a/tests/ofproto.at b/tests/ofproto.at
> > index b3b8a0f..787def1 100644
> > --- a/tests/ofproto.at
> > +++ b/tests/ofproto.at
> > @@ -3144,25 +3144,25 @@ check_async () {
> >     shift
> > 
> >     # OFPGC_ADD
> > -    ovs-appctl -t `pwd`/c2 ofctl/send 050f0010000000020000000000000001
> > +    ovs-appctl -t `pwd`/c2 ofctl/send "050f0020000000020000000000000001 00100000 ffffffffffffffff 00000000"
> >     if test X"$1" = X"OFPGC_ADD"; then shift;
> >         echo >>expout2 "send: OFPT_GROUP_MOD (OF1.4):
> > - ADD group_id=1,type=all"
> > + ADD group_id=1,type=all,bucket=actions=drop"
> >         echo >>expout1 "OFPT_REQUESTFORWARD (OF1.5): reason=group_mod
> > - ADD group_id=1,type=all"
> > + ADD group_id=1,type=all,bucket=bucket_id:0,actions=drop"
> >         echo >>expout3 "OFPT_REQUESTFORWARD (OF1.4): reason=group_mod
> > - ADD group_id=1,type=all"
> > + ADD group_id=1,type=all,bucket=actions=drop"
> >     fi
> > 
> >     # OFPGC_MODIFY
> > -    ovs-appctl -t `pwd`/c2 ofctl/send 050f0010000000020001010000000001
> > +    ovs-appctl -t `pwd`/c2 ofctl/send "050f0020000000020001010000000001 00100000 ffffffffffffffff 00000000"
> >     if test X"$1" = X"OFPGC_MODIFY"; then shift;
> >         echo >>expout2 "send: OFPT_GROUP_MOD (OF1.4):
> > - MOD group_id=1,type=select"
> > + MOD group_id=1,type=select,bucket=weight:0,actions=drop"
> >         echo >>expout1 "OFPT_REQUESTFORWARD (OF1.5): reason=group_mod
> > - MOD group_id=1,type=select"
> > + MOD group_id=1,type=select,bucket=bucket_id:0,weight:0,actions=drop"
> >         echo >>expout3 "OFPT_REQUESTFORWARD (OF1.4): reason=group_mod
> > - MOD group_id=1,type=select"
> > + MOD group_id=1,type=select,bucket=weight:0,actions=drop"
> >     fi
> > 
> >     ovs-appctl -t `pwd`/c1 ofctl/barrier
> > -- 
> > 2.1.3
> > 
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > http://openvswitch.org/mailman/listinfo/dev
>
diff mbox

Patch

diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 528d5ac..386009e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -297,7 +297,8 @@  static bool ofproto_group_exists__(const struct ofproto *ofproto,
 static bool ofproto_group_exists(const struct ofproto *ofproto,
                                  uint32_t group_id)
     OVS_EXCLUDED(ofproto->groups_rwlock);
-static enum ofperr add_group(struct ofproto *, struct ofputil_group_mod *);
+static enum ofperr add_group(struct ofproto *,
+                             const struct ofputil_group_mod *);
 static void handle_openflow(struct ofconn *, const struct ofpbuf *);
 static enum ofperr ofproto_flow_mod_start(struct ofproto *,
                                           struct ofproto_flow_mod *)
@@ -6280,7 +6281,7 @@  handle_queue_get_config_request(struct ofconn *ofconn,
 }
 
 static enum ofperr
-init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
+init_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm,
            struct ofgroup **ofgroup)
 {
     enum ofperr error;
@@ -6306,7 +6307,9 @@  init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
     *CONST_CAST(long long int *, &((*ofgroup)->modified)) = now;
     ovs_refcount_init(&(*ofgroup)->ref_count);
 
-    list_move(&(*ofgroup)->buckets, &gm->buckets);
+    list_init(&(*ofgroup)->buckets);
+    ofputil_bucket_clone_list(&(*ofgroup)->buckets, &gm->buckets, NULL);
+
     *CONST_CAST(uint32_t *, &(*ofgroup)->n_buckets) =
         list_size(&(*ofgroup)->buckets);
 
@@ -6326,7 +6329,7 @@  init_group(struct ofproto *ofproto, struct ofputil_group_mod *gm,
  * 'ofproto''s group table.  Returns 0 on success or an OpenFlow error code on
  * failure. */
 static enum ofperr
-add_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
+add_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
 {
     struct ofgroup *ofgroup;
     enum ofperr error;
@@ -6474,7 +6477,7 @@  copy_buckets_for_remove_bucket(const struct ofgroup *ofgroup,
  * ofproto's ofgroup hash map. Thus, the group is never altered while users of
  * the xlate module hold a pointer to the group. */
 static enum ofperr
-modify_group(struct ofproto *ofproto, struct ofputil_group_mod *gm)
+modify_group(struct ofproto *ofproto, const struct ofputil_group_mod *gm)
 {
     struct ofgroup *ofgroup, *new_ofgroup, *retiring;
     enum ofperr error;
@@ -6643,7 +6646,7 @@  handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
             VLOG_INFO_RL(&rl, "%s: Invalid group_mod command type %d",
                          ofproto->name, gm.command);
         }
-        return OFPERR_OFPGMFC_BAD_COMMAND;
+        error = OFPERR_OFPGMFC_BAD_COMMAND;
     }
 
     if (!error) {
@@ -6653,6 +6656,8 @@  handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
         rf.group_mod = &gm;
         connmgr_send_requestforward(ofproto->connmgr, ofconn, &rf);
     }
+    ofputil_bucket_list_destroy(&gm.buckets);
+
     return error;
 }
 
diff --git a/tests/ofproto.at b/tests/ofproto.at
index b3b8a0f..787def1 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -3144,25 +3144,25 @@  check_async () {
     shift
 
     # OFPGC_ADD
-    ovs-appctl -t `pwd`/c2 ofctl/send 050f0010000000020000000000000001
+    ovs-appctl -t `pwd`/c2 ofctl/send "050f0020000000020000000000000001 00100000 ffffffffffffffff 00000000"
     if test X"$1" = X"OFPGC_ADD"; then shift;
         echo >>expout2 "send: OFPT_GROUP_MOD (OF1.4):
- ADD group_id=1,type=all"
+ ADD group_id=1,type=all,bucket=actions=drop"
         echo >>expout1 "OFPT_REQUESTFORWARD (OF1.5): reason=group_mod
- ADD group_id=1,type=all"
+ ADD group_id=1,type=all,bucket=bucket_id:0,actions=drop"
         echo >>expout3 "OFPT_REQUESTFORWARD (OF1.4): reason=group_mod
- ADD group_id=1,type=all"
+ ADD group_id=1,type=all,bucket=actions=drop"
     fi
 
     # OFPGC_MODIFY
-    ovs-appctl -t `pwd`/c2 ofctl/send 050f0010000000020001010000000001
+    ovs-appctl -t `pwd`/c2 ofctl/send "050f0020000000020001010000000001 00100000 ffffffffffffffff 00000000"
     if test X"$1" = X"OFPGC_MODIFY"; then shift;
         echo >>expout2 "send: OFPT_GROUP_MOD (OF1.4):
- MOD group_id=1,type=select"
+ MOD group_id=1,type=select,bucket=weight:0,actions=drop"
         echo >>expout1 "OFPT_REQUESTFORWARD (OF1.5): reason=group_mod
- MOD group_id=1,type=select"
+ MOD group_id=1,type=select,bucket=bucket_id:0,weight:0,actions=drop"
         echo >>expout3 "OFPT_REQUESTFORWARD (OF1.4): reason=group_mod
- MOD group_id=1,type=select"
+ MOD group_id=1,type=select,bucket=weight:0,actions=drop"
     fi
 
     ovs-appctl -t `pwd`/c1 ofctl/barrier