untrusted comment: signature from openbsd 5.7 base secret key
RWSvUZXnw9gUb305RQbAZ3BNddG21lovpLcx/MxcRtwVkmTLMM3EO5tS5H8DVYUocvaFTDE31T/Ff2DeJJEaP/3qH88rtEaL+ww=

OpenBSD 5.7 errata 15, Sept 28, 2015:

Various problems were identified in relayd and merged back from
current to 5.7 in this maintanance update.

Apply by doing:
    signify -Vep /etc/signify/openbsd-57-base.pub -x 015_relayd.patch.sig \
        -m - | (cd /usr/src && patch -p0)

And then rebuild and install the patch utility:
    cd /usr/src/usr.sbin/relayd
    make obj
    make depend
    make
    make install

Index: usr.sbin/relayd/ca.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/ca.c,v
retrieving revision 1.12
diff -u -p -r1.12 ca.c
--- usr.sbin/relayd/ca.c	22 Jan 2015 17:42:09 -0000	1.12
+++ usr.sbin/relayd/ca.c	28 Sep 2015 17:43:06 -0000
@@ -417,11 +417,14 @@ rsae_keygen(RSA *rsa, int bits, BIGNUM *
 void
 ca_engine_init(struct relayd *x_env)
 {
-	ENGINE		*e;
+	ENGINE		*e = NULL;
 	const char	*errstr, *name;
 
 	if (env == NULL)
 		env = x_env;
+
+	if (rsa_default != NULL)
+		return;
 
 	if ((e = ENGINE_get_default_RSA()) == NULL) {
 		if ((e = ENGINE_new()) == NULL) {
Index: usr.sbin/relayd/config.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/config.c,v
retrieving revision 1.24
diff -u -p -r1.24 config.c
--- usr.sbin/relayd/config.c	22 Jan 2015 17:42:09 -0000	1.24
+++ usr.sbin/relayd/config.c	28 Sep 2015 17:43:06 -0000
@@ -142,7 +142,7 @@ config_purge(struct relayd *env, u_int r
 
 	if (what & CONFIG_TABLES && env->sc_tables != NULL) {
 		while ((table = TAILQ_FIRST(env->sc_tables)) != NULL)
-			purge_table(env->sc_tables, table);
+			purge_table(env, env->sc_tables, table);
 		env->sc_tablecount = 0;
 	}
 	if (what & CONFIG_RDRS && env->sc_rdrs != NULL) {
Index: usr.sbin/relayd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/parse.y,v
retrieving revision 1.203
diff -u -p -r1.203 parse.y
--- usr.sbin/relayd/parse.y	8 Feb 2015 04:50:32 -0000	1.203
+++ usr.sbin/relayd/parse.y	28 Sep 2015 17:43:06 -0000
@@ -531,12 +531,12 @@ rdroptsl	: forwardmode TO tablespec inte
 
 			if ($3->conf.check == CHECK_NOCHECK) {
 				yyerror("table %s has no check", $3->conf.name);
-				purge_table(conf->sc_tables, $3);
+				purge_table(conf, conf->sc_tables, $3);
 				YYERROR;
 			}
 			if (rdr->backup) {
 				yyerror("only one backup table is allowed");
-				purge_table(conf->sc_tables, $3);
+				purge_table(conf, conf->sc_tables, $3);
 				YYERROR;
 			}
 			if (rdr->table) {
@@ -1930,7 +1930,7 @@ routeoptsl	: ROUTE address '/' NUMBER {
 			if (router->rt_gwtable) {
 				yyerror("router %s table already specified",
 				    router->rt_conf.name);
-				purge_table(conf->sc_tables, $3);
+				purge_table(conf, conf->sc_tables, $3);
 				YYERROR;
 			}
 			router->rt_gwtable = $3;
@@ -3091,7 +3091,7 @@ table_inherit(struct table *tb)
 		goto fail;
 	}
 	if ((oldtb = table_findbyconf(conf, tb)) != NULL) {
-		purge_table(NULL, tb);
+		purge_table(conf, NULL, tb);
 		return (oldtb);
 	}
 
@@ -3134,7 +3134,7 @@ table_inherit(struct table *tb)
 	return (tb);
 
  fail:
-	purge_table(NULL, tb);
+	purge_table(conf, NULL, tb);
 	return (NULL);
 }
 
Index: usr.sbin/relayd/pfe.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/pfe.c,v
retrieving revision 1.79
diff -u -p -r1.79 pfe.c
--- usr.sbin/relayd/pfe.c	8 Feb 2015 01:39:06 -0000	1.79
+++ usr.sbin/relayd/pfe.c	28 Sep 2015 17:43:06 -0000
@@ -289,8 +289,11 @@ pfe_dispatch_relay(int fd, struct privse
 			return (0);		/* XXX */
 		memcpy(s, imsg->data, sizeof(*s));
 		TAILQ_FOREACH(t, &env->sc_sessions, se_entry) {
-			if (t->se_id == s->se_id)	/* duplicate registration */
+			/* duplicate registration */
+			if (t->se_id == s->se_id) {
+				free(s);
 				return (0);
+			}
 			if (t->se_id > s->se_id)
 				break;
 		}
Index: usr.sbin/relayd/relay.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relay.c,v
retrieving revision 1.191
diff -u -p -r1.191 relay.c
--- usr.sbin/relayd/relay.c	6 Feb 2015 01:37:11 -0000	1.191
+++ usr.sbin/relayd/relay.c	28 Sep 2015 17:43:06 -0000
@@ -829,6 +829,12 @@ relay_read(struct bufferevent *bev, void
 	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
@@ -988,7 +994,7 @@ relay_error(struct bufferevent *bev, sho
 			dst = EVBUFFER_OUTPUT(cre->dst->bev);
 			if (EVBUFFER_LENGTH(dst))
 				return;
-		} else
+		} else if (cre->toread == TOREAD_UNLIMITED || cre->toread == 0)
 			return;
 
 		relay_close(con, "done");
@@ -1041,6 +1047,12 @@ relay_accept(int fd, short event, void *
 	if ((con = calloc(1, sizeof(*con))) == NULL)
 		goto err;
 
+	/* Pre-allocate log buffer */
+	con->se_haslog = 0;
+	con->se_log = evbuffer_new();
+	if (con->se_log == NULL)
+		goto err;
+
 	con->se_in.s = s;
 	con->se_in.ssl = NULL;
 	con->se_out.s = -1;
@@ -1094,14 +1106,6 @@ relay_accept(int fd, short event, void *
 		return;
 	}
 
-	/* Pre-allocate log buffer */
-	con->se_haslog = 0;
-	con->se_log = evbuffer_new();
-	if (con->se_log == NULL) {
-		relay_close(con, "failed to allocate log buffer");
-		return;
-	}
-
 	if (rlay->rl_conf.flags & F_DIVERT) {
 		slen = sizeof(con->se_out.ss);
 		if (getsockname(s, (struct sockaddr *)&con->se_out.ss,
@@ -1265,7 +1269,7 @@ relay_from_table(struct rsession *con)
 			return (-1);
 	}
 	host = rlt->rlt_host[idx];
-	DPRINTF("%s: session %d: table %s host %s, p 0x%08x, idx %d",
+	DPRINTF("%s: session %d: table %s host %s, p 0x%016llx, idx %d",
 	    __func__, con->se_id, table->conf.name, host->conf.name, p, idx);
 	while (host != NULL) {
 		DPRINTF("%s: session %d: host %s", __func__,
@@ -1404,8 +1408,10 @@ relay_connect_retry(int fd, short sig, v
 	struct relay	*rlay = con->se_relay;
 	int		 bnds = -1;
 
-	if (relay_inflight < 1)
-		fatalx("relay_connect_retry: no connection in flight");
+	if (relay_inflight < 1) {
+		log_warnx("relay_connect_retry: no connection in flight");
+		relay_inflight = 1;
+	}
 
 	DPRINTF("%s: retry %d of %d, inflight: %d",__func__,
 	    con->se_retrycount, con->se_retry, relay_inflight);
@@ -1462,6 +1468,10 @@ relay_connect_retry(int fd, short sig, v
 		return;
 	}
 
+	if (rlay->rl_conf.flags & F_TLSINSPECT)
+		con->se_out.state = STATE_PRECONNECT;
+	else
+		con->se_out.state = STATE_CONNECTED;
 	relay_inflight--;
 	DPRINTF("%s: inflight decremented, now %d",__func__, relay_inflight);
 
@@ -1480,9 +1490,14 @@ relay_connect_retry(int fd, short sig, v
 int
 relay_preconnect(struct rsession *con)
 {
+	int rv;
+
 	log_debug("%s: session %d: process %d", __func__,
 	    con->se_id, privsep_process);
-	return (relay_connect(con));
+	rv = relay_connect(con);
+	if (con->se_out.state == STATE_CONNECTED)
+		con->se_out.state = STATE_PRECONNECT;
+	return (rv);
 }
 
 int
@@ -1492,18 +1507,28 @@ relay_connect(struct rsession *con)
 	struct timeval	 evtpause = { 1, 0 };
 	int		 bnds = -1, ret;
 
+	/* relay_connect should only be called once per relay */
+	if (con->se_out.state == STATE_CONNECTED) {
+		log_debug("%s: connect already called once", __func__);
+		return (0);
+	}
+
 	/* Connection is already established but session not active */
-	if ((rlay->rl_conf.flags & F_TLSINSPECT) && con->se_out.s != -1) {
+	if ((rlay->rl_conf.flags & F_TLSINSPECT) &&
+	    con->se_out.state == STATE_PRECONNECT) {
 		if (con->se_out.ssl == NULL) {
 			log_debug("%s: tls connect failed", __func__);
 			return (-1);
 		}
 		relay_connected(con->se_out.s, EV_WRITE, con);
+		con->se_out.state = STATE_CONNECTED;
 		return (0);
 	}
 
-	if (relay_inflight < 1)
-		fatalx("relay_connect: no connection in flight");
+	if (relay_inflight < 1) {
+		log_warnx("relay_connect: no connection in flight");
+		relay_inflight = 1;
+	}
 
 	getmonotime(&con->se_tv_start);
 
@@ -1551,6 +1576,9 @@ relay_connect(struct rsession *con)
 			event_del(&rlay->rl_ev);
 			evtimer_add(&con->se_inflightevt, &evtpause);
 			evtimer_add(&rlay->rl_evt, &evtpause);
+
+			/* this connect is pending */
+			con->se_out.state = STATE_PENDING;
 			return (0);
 		} else {
 			if (con->se_retry) {
@@ -1568,6 +1596,7 @@ relay_connect(struct rsession *con)
 		}
 	}
 
+	con->se_out.state = STATE_CONNECTED;
 	relay_inflight--;
 	DPRINTF("%s: inflight decremented, now %d",__func__,
 	    relay_inflight);
@@ -1669,6 +1698,7 @@ relay_close(struct rsession *con, const 
 			event_add(&rlay->rl_ev, NULL);
 		}
 	}
+	con->se_out.state = STATE_INIT;
 
 	if (con->se_out.buf != NULL)
 		free(con->se_out.buf);
Index: usr.sbin/relayd/relay_http.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relay_http.c,v
retrieving revision 1.43
diff -u -p -r1.43 relay_http.c
--- usr.sbin/relayd/relay_http.c	22 Jan 2015 17:42:09 -0000	1.43
+++ usr.sbin/relayd/relay_http.c	28 Sep 2015 17:43:06 -0000
@@ -35,6 +35,7 @@
 #include <fnmatch.h>
 #include <siphash.h>
 #include <imsg.h>
+#include <unistd.h>
 
 #include "relayd.h"
 #include "http.h"
@@ -146,6 +147,7 @@ relay_httpdesc_free(struct http_descript
 		desc->query_val = NULL;
 	}
 	kv_purge(&desc->http_headers);
+	desc->http_lastheader = NULL;
 }
 
 void
@@ -210,7 +212,7 @@ relay_read_http(struct bufferevent *bev,
 		else
 			value = strchr(key, ':');
 		if (value == NULL) {
-			if (cre->line == 1) {
+			if (cre->line <= 2) {
 				free(line);
 				relay_abort_http(con, 400, "malformed", 0);
 				return;
@@ -271,8 +273,10 @@ relay_read_http(struct bufferevent *bev,
 			goto lookup;
 		} else if (cre->line == 1 && cre->dir == RELAY_DIR_REQUEST) {
 			if ((desc->http_method = relay_httpmethod_byname(key))
-			    == HTTP_METHOD_NONE)
+			    == HTTP_METHOD_NONE) {
+				free(line);
 				goto fail;
+			}
 			/*
 			 * Decode request path and query
 			 */
@@ -415,7 +419,7 @@ relay_read_http(struct bufferevent *bev,
 		relay_reset_http(cre);
  done:
 		if (cre->dir == RELAY_DIR_REQUEST && cre->toread <= 0 &&
-		    cre->dst->bev == NULL) {
+		    cre->dst->state != STATE_CONNECTED) {
 			if (rlay->rl_conf.fwdmode == FWD_TRANS) {
 				relay_bindanyreq(con, 0, IPPROTO_TCP);
 				return;
@@ -430,11 +434,18 @@ relay_read_http(struct bufferevent *bev,
 		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);
@@ -484,9 +495,10 @@ relay_read_httpcontent(struct buffereven
 	}
 	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");
@@ -601,9 +613,10 @@ relay_read_httpchunks(struct bufferevent
  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:
@@ -1363,7 +1376,7 @@ relay_match_actions(struct ctl_relay_eve
     struct kvlist *matches, struct kvlist *actions)
 {
 	struct rsession		*con = cre->con;
-	struct kv		*kv;
+	struct kv		*kv, *tmp;
 
 	/*
 	 * Apply the following options instantly (action per match).
@@ -1382,7 +1395,7 @@ relay_match_actions(struct ctl_relay_eve
 	 */
 	if (matches == NULL) {
 		/* 'pass' or 'block' rule */
-		TAILQ_FOREACH(kv, &rule->rule_kvlist, kv_rule_entry) {
+		TAILQ_FOREACH_SAFE(kv, &rule->rule_kvlist, kv_rule_entry, tmp) {
 			TAILQ_INSERT_TAIL(actions, kv, kv_action_entry);
 			TAILQ_REMOVE(&rule->rule_kvlist, kv, kv_rule_entry);
 		}
Index: usr.sbin/relayd/relayd.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.c,v
retrieving revision 1.138
diff -u -p -r1.138 relayd.c
--- usr.sbin/relayd/relayd.c	22 Jan 2015 17:42:09 -0000	1.138
+++ usr.sbin/relayd/relayd.c	28 Sep 2015 17:43:06 -0000
@@ -546,12 +546,13 @@ parent_dispatch_ca(int fd, struct privse
 }
 
 void
-purge_table(struct tablelist *head, struct table *table)
+purge_table(struct relayd *env, struct tablelist *head, struct table *table)
 {
 	struct host		*host;
 
 	while ((host = TAILQ_FIRST(&table->hosts)) != NULL) {
 		TAILQ_REMOVE(&table->hosts, host, entry);
+		TAILQ_REMOVE(&env->sc_hosts, host, globalentry);
 		if (event_initialized(&host->cte.ev)) {
 			event_del(&host->cte.ev);
 			close(host->cte.s);
@@ -766,18 +767,13 @@ kv_purge(struct kvtree *keys)
 void
 kv_free(struct kv *kv)
 {
-	if (kv->kv_type == KEY_TYPE_NONE)
-		return;
-	if (kv->kv_key != NULL) {
-		free(kv->kv_key);
-	}
-	kv->kv_key = NULL;
-	if (kv->kv_value != NULL) {
-		free(kv->kv_value);
-	}
-	kv->kv_value = NULL;
-	kv->kv_matchtree = NULL;
-	kv->kv_match = NULL;
+	/*
+	 * This function does not clear memory referenced by
+	 * kv_children or stuff on the tailqs. Use kv_delete() instead.
+	 */
+
+	free(kv->kv_key);
+	free(kv->kv_value);
 	memset(kv, 0, sizeof(*kv));
 }
 
Index: usr.sbin/relayd/relayd.h
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.h,v
retrieving revision 1.207
diff -u -p -r1.207 relayd.h
--- usr.sbin/relayd/relayd.h	22 Jan 2015 17:42:09 -0000	1.207
+++ usr.sbin/relayd/relayd.h	28 Sep 2015 17:43:06 -0000
@@ -180,6 +180,13 @@ enum tlsreneg_state {
 	TLSRENEG_ABORT		= 3	/* the connection should be aborted */
 };
 
+enum relay_state {
+	STATE_INIT,
+	STATE_PENDING,
+	STATE_PRECONNECT,
+	STATE_CONNECTED
+};
+
 struct ctl_relay_event {
 	int			 s;
 	in_port_t		 port;
@@ -200,6 +207,7 @@ struct ctl_relay_event {
 	int			 line;
 	int			 done;
 	int			 timedout;
+	enum relay_state	 state;
 	enum direction		 dir;
 
 	u_int8_t		*buf;
@@ -1253,7 +1261,8 @@ struct ca_pkey	*pkey_add(struct relayd *
 int		 expand_string(char *, size_t, const char *, const char *);
 void		 translate_string(char *);
 void		 purge_key(char **, off_t);
-void		 purge_table(struct tablelist *, struct table *);
+void		 purge_table(struct relayd *, struct tablelist *,
+		    struct table *);
 void		 purge_relay(struct relayd *, struct relay *);
 char		*digeststr(enum digest_type, const u_int8_t *, size_t, char *);
 const char	*canonicalize_host(const char *, char *, size_t);
Index: usr.sbin/relayd/ssl.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/ssl.c,v
retrieving revision 1.28
diff -u -p -r1.28 ssl.c
--- usr.sbin/relayd/ssl.c	22 Jan 2015 17:42:09 -0000	1.28
+++ usr.sbin/relayd/ssl.c	28 Sep 2015 17:43:06 -0000
@@ -454,6 +454,7 @@ ssl_load_pkey(const void *data, size_t d
 		EVP_PKEY_free(pkey);
 	if (x509 != NULL)
 		X509_free(x509);
+	free(exdata);
 
 	return (0);
 }
