On Mon, Oct 26, 2020 at 01:07:48PM +0100, Pascal Mathis wrote:
> >Synopsis:    OpenBGPD crashes when reloading with duplicate neighbor
> >Category:    system
> >Environment:
>       System      : OpenBSD 6.8
>       Details     : OpenBSD 6.8 (GENERIC) #97: Sun Oct  4 18:00:46 MDT 2020
>                        
> [email protected]:/usr/src/sys/arch/amd64/compile/GENERIC
> 
>       Architecture: OpenBSD.amd64
>       Machine     : amd64
> >Description:
>       When the OpenBGPD configuration mistakenly contains a neighbor
>       with a duplicate address, executing "bgpctl reload" results in
>       bgpd exiting with a fatal error:
> 
>       bgpd[75744]: fatal in bgpd: yyparse: peer tree is corrupt
>       bgpd[44477]: peer closed imsg connection
>       bgpd[73636]: peer closed imsg connection
>       bgpd[73636]: fatal in RDE: Lost connection to parent
>       bgpd[44477]: SE: Lost connection to parent
> 
>       While having two neighbors with the same address and port
>       configuration is an obvious mistake and probably not useful
>       under any circumstances, the daemon should not crash when
>       executing "bgpctl reload" and gracefully reject reloading the
>       configuration. Interestingly enough, bgpd is able to start up
>       from scratch with a duplicate neighbor configuration, resulting
>       in the daemon showing both neighbors as active:
> 
>       Neighbor                   AS    MsgRcvd    MsgSent  OutQ Up/Down  
> State/PrfRcvd
>       192.0.2.1                   2          0          0     0 Never    
> Connect
>       192.0.2.1                   2          0          0     0 Never    
> Connect
> 
>       If I am not missing any specific use case, this is probably also
>       a bug / undesired behavior, as having the same neighbor twice
>       does not seem like a good choice either. I would expect (1) bgpd
>       not to crash when reloading with a configuration mistake and (2)
>       bgpd to not start in the first place when a duplicate neighbor
>       exists, although that one might be up for debate.
> >How-To-Repeat:
>       This issue can be reproduced using this /etc/bgpd.conf:
> 
>       AS 1
>       neighbor 192.0.2.1 { remote-as 2 }
>       neighbor 192.0.2.1 { remote-as 2 }
> 
>       (Re-)starting bgpd using rcctl works fine and "bgpctl show"
>       shows the duplicate neighbor twice. However when now executing
>       "bgpctl reload", bgpd crashes with a fatal error. It can be
>       reliably reproduced every single time and does not seem to
>       matter if the remote-as is different.
> >Fix:
>       No specific fix known. The issue does not appear when the
>       configuration has no duplicates while reloading, but this is
>       exactly what this bug is about - catching such a mistake.

Indeed, the parser needs to detect duplicate peer early on. 
This diff should do that. It also fixes a few mem-leaks (of curpeer and
curgroup) and brings getid more in sync with the getpeerbyip version of
session.c.

With this duplicate peers will cause a syntax error on startup and
reloads.

-- 
:wq Claudio

Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/parse.y,v
retrieving revision 1.408
diff -u -p -r1.408 parse.y
--- parse.y     10 May 2020 13:38:46 -0000      1.408
+++ parse.y     26 Oct 2020 15:30:34 -0000
@@ -1178,8 +1178,10 @@ neighbor : {     curpeer = new_peer(); }
                        curpeer_filter[0] = NULL;
                        curpeer_filter[1] = NULL;
 
-                       if (neighbor_consistent(curpeer) == -1)
+                       if (neighbor_consistent(curpeer) == -1) {
+                               free(curpeer);
                                YYERROR;
+                       }
                        if (RB_INSERT(peer_head, new_peers, curpeer) != NULL)
                                fatalx("%s: peer tree is corrupt", __func__);
                        curpeer = curgroup;
@@ -1194,11 +1196,13 @@ group           : GROUP string                  {
                                yyerror("group name \"%s\" too long: max %zu",
                                    $2, sizeof(curgroup->conf.group) - 1);
                                free($2);
+                               free(curgroup);
                                YYERROR;
                        }
                        free($2);
                        if (get_id(curgroup)) {
                                yyerror("get_id failed");
+                               free(curgroup);
                                YYERROR;
                        }
                } '{' groupopts_l '}'           {
@@ -3987,7 +3991,9 @@ get_id(struct peer *newpeer)
                /* neighbor */
                if (cur_peers)
                        RB_FOREACH(p, peer_head, cur_peers)
-                               if (memcmp(&p->conf.remote_addr,
+                               if (p->conf.remote_masklen ==
+                                   newpeer->conf.remote_masklen &&
+                                   memcmp(&p->conf.remote_addr,
                                    &newpeer->conf.remote_addr,
                                    sizeof(p->conf.remote_addr)) == 0)
                                        break;
@@ -4196,6 +4202,7 @@ int
 neighbor_consistent(struct peer *p)
 {
        struct bgpd_addr *local_addr;
+       struct peer *xp;
 
        switch (p->conf.remote_addr.aid) {
        case AID_INET:
@@ -4251,6 +4258,21 @@ neighbor_consistent(struct peer *p)
        if (p->conf.reflector_client && p->conf.ebgp) {
                yyerror("EBGP neighbors are not allowed in route "
                    "reflector clusters");
+               return (-1);
+       }
+
+       /* check for duplicate peer definitions */
+       RB_FOREACH(xp, peer_head, new_peers)
+               if (xp->conf.remote_masklen ==
+                   p->conf.remote_masklen &&
+                   memcmp(&xp->conf.remote_addr,
+                   &p->conf.remote_addr,
+                   sizeof(p->conf.remote_addr)) == 0)
+                       break;
+       if (xp != NULL) {
+               char *descr = log_fmt_peer(&p->conf);
+               yyerror("duplicate %s", descr);
+               free(descr);
                return (-1);
        }
 

Reply via email to