Fix a crash reported and analyzed by Bertrand PROVOST. When a HTTP
authorbluhm <bluhm@openbsd.org>
Mon, 18 May 2015 16:57:20 +0000 (16:57 +0000)
committerbluhm <bluhm@openbsd.org>
Mon, 18 May 2015 16:57:20 +0000 (16:57 +0000)
client or server writes multiple requests or chunks in a single
transfer, relayd invokes the libevent callback manually for the
next data.  If the callback closes the session, this resulted in
an use after free.
Instead of the more complicated fix suggested by Bertrand PROVOST,
just move the invocation of the callback to the end of the function.
So in case the callback frees any structures, they are not accessed.
OK benno@ reyk@

usr.sbin/relayd/relay.c
usr.sbin/relayd/relay_http.c

index 1fd32dc..3314622 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: relay.c,v 1.193 2015/04/29 08:41:24 bluhm Exp $       */
+/*     $OpenBSD: relay.c,v 1.194 2015/05/18 16:57:20 bluhm Exp $       */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -829,6 +829,12 @@ relay_read(struct bufferevent *bev, void *arg)
        relay_close(con, strerror(errno));
 }
 
+/*
+ * Splice sockets from cre to cre->dst if applicable.  Returns:
+ * -1 socket splicing has failed
+ * 0 socket splicing is currently not possible
+ * 1 socket splicing was successful
+ */
 int
 relay_splice(struct ctl_relay_event *cre)
 {
@@ -878,7 +884,7 @@ relay_splice(struct ctl_relay_event *cre)
        DPRINTF("%s: session %d: splice dir %d, maximum %lld, successful",
            __func__, con->se_id, cre->dir, cre->toread);
 
-       return (0);
+       return (1);
 }
 
 int
index 4e3af60..2fdb2d5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: relay_http.c,v 1.45 2015/05/18 16:45:16 bluhm Exp $   */
+/*     $OpenBSD: relay_http.c,v 1.46 2015/05/18 16:57:20 bluhm Exp $   */
 
 /*
  * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -434,11 +434,18 @@ relay_read_http(struct bufferevent *bev, void *arg)
                relay_close(con, "last http read (done)");
                return;
        }
+       switch (relay_splice(cre)) {
+       case -1:
+               relay_close(con, strerror(errno));
+       case 1:
+               return;
+       case 0:
+               break;
+       }
+       bufferevent_enable(bev, EV_READ);
        if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http)
                bev->readcb(bev, arg);
-       bufferevent_enable(bev, EV_READ);
-       if (relay_splice(cre) == -1)
-               relay_close(con, strerror(errno));
+       /* The callback readcb() might have freed the session. */
        return;
  fail:
        relay_abort_http(con, 500, strerror(errno), 0);
@@ -488,9 +495,10 @@ relay_read_httpcontent(struct bufferevent *bev, void *arg)
        }
        if (con->se_done)
                goto done;
+       bufferevent_enable(bev, EV_READ);
        if (bev->readcb != relay_read_httpcontent)
                bev->readcb(bev, arg);
-       bufferevent_enable(bev, EV_READ);
+       /* The callback readcb() might have freed the session. */
        return;
  done:
        relay_close(con, "last http content read");
@@ -605,9 +613,10 @@ relay_read_httpchunks(struct bufferevent *bev, void *arg)
  next:
        if (con->se_done)
                goto done;
+       bufferevent_enable(bev, EV_READ);
        if (EVBUFFER_LENGTH(src))
                bev->readcb(bev, arg);
-       bufferevent_enable(bev, EV_READ);
+       /* The callback readcb() might have freed the session. */
        return;
 
  done: