Skip to content

Commit

Permalink
tcp: Fix MD5 signatures for non-linear skbs
Browse files Browse the repository at this point in the history
Currently, the MD5 code assumes that the SKBs are linear and, in the case
that they aren't, happily goes off and hashes off the end of the SKB and
into random memory.

Reported by Stephen Hemminger in [1]. Advice thanks to Stephen and Evgeniy
Polyakov. Also includes a couple of missed route_caps from Stephen's patch
in [2].

[1] http://marc.info/?l=linux-netdev&m=121445989106145&w=2
[2] http://marc.info/?l=linux-netdev&m=121459157816964&w=2

Signed-off-by: Adam Langley <agl@imperialviolet.org>
Acked-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Adam Langley authored and David S. Miller committed Jul 19, 2008
1 parent 845525a commit 49a72df
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 195 deletions.
29 changes: 12 additions & 17 deletions include/net/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1112,20 +1112,12 @@ struct tcp_md5sig_pool {
#define TCP_MD5SIG_MAXKEYS (~(u32)0) /* really?! */

/* - functions */
extern int tcp_calc_md5_hash(char *md5_hash,
struct tcp_md5sig_key *key,
int bplen,
struct tcphdr *th,
unsigned int tcplen,
struct tcp_md5sig_pool *hp);

extern int tcp_v4_calc_md5_hash(char *md5_hash,
struct tcp_md5sig_key *key,
struct sock *sk,
struct dst_entry *dst,
struct request_sock *req,
struct tcphdr *th,
unsigned int tcplen);
extern int tcp_v4_md5_hash_skb(char *md5_hash,
struct tcp_md5sig_key *key,
struct sock *sk,
struct request_sock *req,
struct sk_buff *skb);

extern struct tcp_md5sig_key *tcp_v4_md5_lookup(struct sock *sk,
struct sock *addr_sk);

Expand All @@ -1152,6 +1144,11 @@ extern void tcp_free_md5sig_pool(void);

extern struct tcp_md5sig_pool *__tcp_get_md5sig_pool(int cpu);
extern void __tcp_put_md5sig_pool(void);
extern int tcp_md5_hash_header(struct tcp_md5sig_pool *, struct tcphdr *);
extern int tcp_md5_hash_skb_data(struct tcp_md5sig_pool *, struct sk_buff *,
unsigned header_len);
extern int tcp_md5_hash_key(struct tcp_md5sig_pool *hp,
struct tcp_md5sig_key *key);

static inline
struct tcp_md5sig_pool *tcp_get_md5sig_pool(void)
Expand Down Expand Up @@ -1381,10 +1378,8 @@ struct tcp_sock_af_ops {
int (*calc_md5_hash) (char *location,
struct tcp_md5sig_key *md5,
struct sock *sk,
struct dst_entry *dst,
struct request_sock *req,
struct tcphdr *th,
unsigned int len);
struct sk_buff *skb);
int (*md5_add) (struct sock *sk,
struct sock *addr_sk,
u8 *newkey,
Expand Down
127 changes: 57 additions & 70 deletions net/ipv4/tcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2465,76 +2465,6 @@ static unsigned long tcp_md5sig_users;
static struct tcp_md5sig_pool **tcp_md5sig_pool;
static DEFINE_SPINLOCK(tcp_md5sig_pool_lock);

int tcp_calc_md5_hash(char *md5_hash, struct tcp_md5sig_key *key,
int bplen,
struct tcphdr *th, unsigned int tcplen,
struct tcp_md5sig_pool *hp)
{
struct scatterlist sg[4];
__u16 data_len;
int block = 0;
__sum16 cksum;
struct hash_desc *desc = &hp->md5_desc;
int err;
unsigned int nbytes = 0;

sg_init_table(sg, 4);

/* 1. The TCP pseudo-header */
sg_set_buf(&sg[block++], &hp->md5_blk, bplen);
nbytes += bplen;

/* 2. The TCP header, excluding options, and assuming a
* checksum of zero
*/
cksum = th->check;
th->check = 0;
sg_set_buf(&sg[block++], th, sizeof(*th));
nbytes += sizeof(*th);

/* 3. The TCP segment data (if any) */
data_len = tcplen - (th->doff << 2);
if (data_len > 0) {
u8 *data = (u8 *)th + (th->doff << 2);
sg_set_buf(&sg[block++], data, data_len);
nbytes += data_len;
}

/* 4. an independently-specified key or password, known to both
* TCPs and presumably connection-specific
*/
sg_set_buf(&sg[block++], key->key, key->keylen);
nbytes += key->keylen;

sg_mark_end(&sg[block - 1]);

/* Now store the hash into the packet */
err = crypto_hash_init(desc);
if (err) {
if (net_ratelimit())
printk(KERN_WARNING "%s(): hash_init failed\n", __func__);
return -1;
}
err = crypto_hash_update(desc, sg, nbytes);
if (err) {
if (net_ratelimit())
printk(KERN_WARNING "%s(): hash_update failed\n", __func__);
return -1;
}
err = crypto_hash_final(desc, md5_hash);
if (err) {
if (net_ratelimit())
printk(KERN_WARNING "%s(): hash_final failed\n", __func__);
return -1;
}

/* Reset header */
th->check = cksum;

return 0;
}
EXPORT_SYMBOL(tcp_calc_md5_hash);

static void __tcp_free_md5sig_pool(struct tcp_md5sig_pool **pool)
{
int cpu;
Expand Down Expand Up @@ -2658,6 +2588,63 @@ void __tcp_put_md5sig_pool(void)
}

EXPORT_SYMBOL(__tcp_put_md5sig_pool);

int tcp_md5_hash_header(struct tcp_md5sig_pool *hp,
struct tcphdr *th)
{
struct scatterlist sg;
int err;

__sum16 old_checksum = th->check;
th->check = 0;
/* options aren't included in the hash */
sg_init_one(&sg, th, sizeof(struct tcphdr));
err = crypto_hash_update(&hp->md5_desc, &sg, sizeof(struct tcphdr));
th->check = old_checksum;
return err;
}

EXPORT_SYMBOL(tcp_md5_hash_header);

int tcp_md5_hash_skb_data(struct tcp_md5sig_pool *hp,
struct sk_buff *skb, unsigned header_len)
{
struct scatterlist sg;
const struct tcphdr *tp = tcp_hdr(skb);
struct hash_desc *desc = &hp->md5_desc;
unsigned i;
const unsigned head_data_len = skb_headlen(skb) > header_len ?
skb_headlen(skb) - header_len : 0;
const struct skb_shared_info *shi = skb_shinfo(skb);

sg_init_table(&sg, 1);

sg_set_buf(&sg, ((u8 *) tp) + header_len, head_data_len);
if (crypto_hash_update(desc, &sg, head_data_len))
return 1;

for (i = 0; i < shi->nr_frags; ++i) {
const struct skb_frag_struct *f = &shi->frags[i];
sg_set_page(&sg, f->page, f->size, f->page_offset);
if (crypto_hash_update(desc, &sg, f->size))
return 1;
}

return 0;
}

EXPORT_SYMBOL(tcp_md5_hash_skb_data);

int tcp_md5_hash_key(struct tcp_md5sig_pool *hp, struct tcp_md5sig_key *key)
{
struct scatterlist sg;

sg_init_one(&sg, key->key, key->keylen);
return crypto_hash_update(&hp->md5_desc, &sg, key->keylen);
}

EXPORT_SYMBOL(tcp_md5_hash_key);

#endif

void tcp_done(struct sock *sk)
Expand Down
Loading

0 comments on commit 49a72df

Please sign in to comment.