ipvs: fix oops in backup for fwmark conn templates

Fixes bug http://bugzilla.kernel.org/show_bug.cgi?id=10556
where conn templates with protocol=IPPROTO_IP can oops backup box.

        Result from ip_vs_proto_get() should be checked because
protocol value can be invalid or unsupported in backup. But
for valid message we should not fail for templates which use
IPPROTO_IP. Also, add checks to validate message limits and
connection state. Show state NONE for templates using IPPROTO_IP.

Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Julian Anastasov 2008-04-29 03:21:23 -07:00 committed by David S. Miller
parent d69efb1689
commit 2ad17defd5
7 changed files with 66 additions and 23 deletions

View file

@ -405,7 +405,8 @@ struct sk_buff;
struct ip_vs_protocol {
struct ip_vs_protocol *next;
char *name;
__u16 protocol;
u16 protocol;
u16 num_states;
int dont_defrag;
atomic_t appcnt; /* counter of proto app incs */
int *timeout_table; /* protocol timeout table */

View file

@ -148,7 +148,7 @@ const char * ip_vs_state_name(__u16 proto, int state)
struct ip_vs_protocol *pp = ip_vs_proto_get(proto);
if (pp == NULL || pp->state_name == NULL)
return "ERR!";
return (IPPROTO_IP == proto) ? "NONE" : "ERR!";
return pp->state_name(state);
}

View file

@ -160,6 +160,7 @@ static void ah_exit(struct ip_vs_protocol *pp)
struct ip_vs_protocol ip_vs_protocol_ah = {
.name = "AH",
.protocol = IPPROTO_AH,
.num_states = 1,
.dont_defrag = 1,
.init = ah_init,
.exit = ah_exit,

View file

@ -159,6 +159,7 @@ static void esp_exit(struct ip_vs_protocol *pp)
struct ip_vs_protocol ip_vs_protocol_esp = {
.name = "ESP",
.protocol = IPPROTO_ESP,
.num_states = 1,
.dont_defrag = 1,
.init = esp_init,
.exit = esp_exit,

View file

@ -594,6 +594,7 @@ static void ip_vs_tcp_exit(struct ip_vs_protocol *pp)
struct ip_vs_protocol ip_vs_protocol_tcp = {
.name = "TCP",
.protocol = IPPROTO_TCP,
.num_states = IP_VS_TCP_S_LAST,
.dont_defrag = 0,
.appcnt = ATOMIC_INIT(0),
.init = ip_vs_tcp_init,

View file

@ -409,6 +409,7 @@ static void udp_exit(struct ip_vs_protocol *pp)
struct ip_vs_protocol ip_vs_protocol_udp = {
.name = "UDP",
.protocol = IPPROTO_UDP,
.num_states = IP_VS_UDP_S_LAST,
.dont_defrag = 0,
.init = udp_init,
.exit = udp_exit,

View file

@ -288,11 +288,16 @@ static void ip_vs_process_message(const char *buffer, const size_t buflen)
char *p;
int i;
if (buflen < sizeof(struct ip_vs_sync_mesg)) {
IP_VS_ERR_RL("sync message header too short\n");
return;
}
/* Convert size back to host byte order */
m->size = ntohs(m->size);
if (buflen != m->size) {
IP_VS_ERR("bogus message\n");
IP_VS_ERR_RL("bogus sync message size\n");
return;
}
@ -307,9 +312,48 @@ static void ip_vs_process_message(const char *buffer, const size_t buflen)
for (i=0; i<m->nr_conns; i++) {
unsigned flags, state;
s = (struct ip_vs_sync_conn *)p;
if (p + SIMPLE_CONN_SIZE > buffer+buflen) {
IP_VS_ERR_RL("bogus conn in sync message\n");
return;
}
s = (struct ip_vs_sync_conn *) p;
flags = ntohs(s->flags) | IP_VS_CONN_F_SYNC;
flags &= ~IP_VS_CONN_F_HASHED;
if (flags & IP_VS_CONN_F_SEQ_MASK) {
opt = (struct ip_vs_sync_conn_options *)&s[1];
p += FULL_CONN_SIZE;
if (p > buffer+buflen) {
IP_VS_ERR_RL("bogus conn options in sync message\n");
return;
}
} else {
opt = NULL;
p += SIMPLE_CONN_SIZE;
}
state = ntohs(s->state);
if (!(flags & IP_VS_CONN_F_TEMPLATE)) {
pp = ip_vs_proto_get(s->protocol);
if (!pp) {
IP_VS_ERR_RL("Unsupported protocol %u in sync msg\n",
s->protocol);
continue;
}
if (state >= pp->num_states) {
IP_VS_DBG(2, "Invalid %s state %u in sync msg\n",
pp->name, state);
continue;
}
} else {
/* protocol in templates is not used for state/timeout */
pp = NULL;
if (state > 0) {
IP_VS_DBG(2, "Invalid template state %u in sync msg\n",
state);
state = 0;
}
}
if (!(flags & IP_VS_CONN_F_TEMPLATE))
cp = ip_vs_conn_in_get(s->protocol,
s->caddr, s->cport,
@ -345,14 +389,9 @@ static void ip_vs_process_message(const char *buffer, const size_t buflen)
IP_VS_ERR("ip_vs_conn_new failed\n");
return;
}
cp->state = state;
} else if (!cp->dest) {
dest = ip_vs_try_bind_dest(cp);
if (!dest) {
/* it is an unbound entry created by
* synchronization */
cp->flags = flags | IP_VS_CONN_F_HASHED;
} else
if (dest)
atomic_dec(&dest->refcnt);
} else if ((cp->dest) && (cp->protocol == IPPROTO_TCP) &&
(cp->state != state)) {
@ -371,23 +410,22 @@ static void ip_vs_process_message(const char *buffer, const size_t buflen)
}
}
if (flags & IP_VS_CONN_F_SEQ_MASK) {
opt = (struct ip_vs_sync_conn_options *)&s[1];
if (opt)
memcpy(&cp->in_seq, opt, sizeof(*opt));
p += FULL_CONN_SIZE;
} else
p += SIMPLE_CONN_SIZE;
atomic_set(&cp->in_pkts, sysctl_ip_vs_sync_threshold[0]);
cp->state = state;
pp = ip_vs_proto_get(s->protocol);
cp->timeout = pp->timeout_table[cp->state];
cp->old_state = cp->state;
/*
* We can not recover the right timeout for templates
* in all cases, we can not find the right fwmark
* virtual service. If needed, we can do it for
* non-fwmark persistent services.
*/
if (!(flags & IP_VS_CONN_F_TEMPLATE) && pp->timeout_table)
cp->timeout = pp->timeout_table[state];
else
cp->timeout = (3*60*HZ);
ip_vs_conn_put(cp);
if (p > buffer+buflen) {
IP_VS_ERR("bogus message\n");
return;
}
}
}