net/flow_dissector: switch to siphash

UDP IPv6 packets auto flowlabels are using a 32bit secret
(static u32 hashrnd in net/core/flow_dissector.c) and
apply jhash() over fields known by the receivers.

Attackers can easily infer the 32bit secret and use this information
to identify a device and/or user, since this 32bit secret is only
set at boot time.

Really, using jhash() to generate cookies sent on the wire
is a serious security concern.

Trying to change the rol32(hash, 16) in ip6_make_flowlabel() would be
a dead end. Trying to periodically change the secret (like in sch_sfq.c)
could change paths taken in the network for long lived flows.

Let's switch to siphash, as we did in commit df453700e8
("inet: switch IP ID generator to siphash")

Using a cryptographically strong pseudo random function will solve this
privacy issue and more generally remove other weak points in the stack.

Packet schedulers using skb_get_hash_perturb() benefit from this change.

Fixes: b56774163f ("ipv6: Enable auto flow labels by default")
Fixes: 42240901f7 ("ipv6: Implement different admin modes for automatic flow labels")
Fixes: 67800f9b1f ("ipv6: Call skb_get_hash_flowi6 to get skb->hash in ip6_make_flowlabel")
Fixes: cb1ce2ef38 ("ipv6: Implement automatic flow label generation on transmit")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Jonathan Berger <jonathann1@walla.com>
Reported-by: Amit Klein <aksecurity@gmail.com>
Reported-by: Benny Pinkas <benny@pinkas.net>
Cc: Tom Herbert <tom@herbertland.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Eric Dumazet 2019-10-22 07:57:46 -07:00 committed by David S. Miller
parent 6c5d9c2a6b
commit 55667441c8
8 changed files with 42 additions and 43 deletions

View File

@ -1354,7 +1354,8 @@ static inline __u32 skb_get_hash_flowi6(struct sk_buff *skb, const struct flowi6
return skb->hash; return skb->hash;
} }
__u32 skb_get_hash_perturb(const struct sk_buff *skb, u32 perturb); __u32 skb_get_hash_perturb(const struct sk_buff *skb,
const siphash_key_t *perturb);
static inline __u32 skb_get_hash_raw(const struct sk_buff *skb) static inline __u32 skb_get_hash_raw(const struct sk_buff *skb)
{ {

View File

@ -4,6 +4,7 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/in6.h> #include <linux/in6.h>
#include <linux/siphash.h>
#include <uapi/linux/if_ether.h> #include <uapi/linux/if_ether.h>
/** /**
@ -276,7 +277,7 @@ struct flow_keys_basic {
struct flow_keys { struct flow_keys {
struct flow_dissector_key_control control; struct flow_dissector_key_control control;
#define FLOW_KEYS_HASH_START_FIELD basic #define FLOW_KEYS_HASH_START_FIELD basic
struct flow_dissector_key_basic basic; struct flow_dissector_key_basic basic __aligned(SIPHASH_ALIGNMENT);
struct flow_dissector_key_tags tags; struct flow_dissector_key_tags tags;
struct flow_dissector_key_vlan vlan; struct flow_dissector_key_vlan vlan;
struct flow_dissector_key_vlan cvlan; struct flow_dissector_key_vlan cvlan;

View File

@ -69,7 +69,7 @@ struct fq {
struct list_head backlogs; struct list_head backlogs;
spinlock_t lock; spinlock_t lock;
u32 flows_cnt; u32 flows_cnt;
u32 perturbation; siphash_key_t perturbation;
u32 limit; u32 limit;
u32 memory_limit; u32 memory_limit;
u32 memory_usage; u32 memory_usage;

View File

@ -108,7 +108,7 @@ begin:
static u32 fq_flow_idx(struct fq *fq, struct sk_buff *skb) static u32 fq_flow_idx(struct fq *fq, struct sk_buff *skb)
{ {
u32 hash = skb_get_hash_perturb(skb, fq->perturbation); u32 hash = skb_get_hash_perturb(skb, &fq->perturbation);
return reciprocal_scale(hash, fq->flows_cnt); return reciprocal_scale(hash, fq->flows_cnt);
} }
@ -308,7 +308,7 @@ static int fq_init(struct fq *fq, int flows_cnt)
INIT_LIST_HEAD(&fq->backlogs); INIT_LIST_HEAD(&fq->backlogs);
spin_lock_init(&fq->lock); spin_lock_init(&fq->lock);
fq->flows_cnt = max_t(u32, flows_cnt, 1); fq->flows_cnt = max_t(u32, flows_cnt, 1);
fq->perturbation = prandom_u32(); get_random_bytes(&fq->perturbation, sizeof(fq->perturbation));
fq->quantum = 300; fq->quantum = 300;
fq->limit = 8192; fq->limit = 8192;
fq->memory_limit = 16 << 20; /* 16 MBytes */ fq->memory_limit = 16 << 20; /* 16 MBytes */

View File

@ -1350,30 +1350,21 @@ out_bad:
} }
EXPORT_SYMBOL(__skb_flow_dissect); EXPORT_SYMBOL(__skb_flow_dissect);
static u32 hashrnd __read_mostly; static siphash_key_t hashrnd __read_mostly;
static __always_inline void __flow_hash_secret_init(void) static __always_inline void __flow_hash_secret_init(void)
{ {
net_get_random_once(&hashrnd, sizeof(hashrnd)); net_get_random_once(&hashrnd, sizeof(hashrnd));
} }
static __always_inline u32 __flow_hash_words(const u32 *words, u32 length, static const void *flow_keys_hash_start(const struct flow_keys *flow)
u32 keyval)
{ {
return jhash2(words, length, keyval); BUILD_BUG_ON(FLOW_KEYS_HASH_OFFSET % SIPHASH_ALIGNMENT);
} return &flow->FLOW_KEYS_HASH_START_FIELD;
static inline const u32 *flow_keys_hash_start(const struct flow_keys *flow)
{
const void *p = flow;
BUILD_BUG_ON(FLOW_KEYS_HASH_OFFSET % sizeof(u32));
return (const u32 *)(p + FLOW_KEYS_HASH_OFFSET);
} }
static inline size_t flow_keys_hash_length(const struct flow_keys *flow) static inline size_t flow_keys_hash_length(const struct flow_keys *flow)
{ {
size_t diff = FLOW_KEYS_HASH_OFFSET + sizeof(flow->addrs); size_t diff = FLOW_KEYS_HASH_OFFSET + sizeof(flow->addrs);
BUILD_BUG_ON((sizeof(*flow) - FLOW_KEYS_HASH_OFFSET) % sizeof(u32));
BUILD_BUG_ON(offsetof(typeof(*flow), addrs) != BUILD_BUG_ON(offsetof(typeof(*flow), addrs) !=
sizeof(*flow) - sizeof(flow->addrs)); sizeof(*flow) - sizeof(flow->addrs));
@ -1388,7 +1379,7 @@ static inline size_t flow_keys_hash_length(const struct flow_keys *flow)
diff -= sizeof(flow->addrs.tipckey); diff -= sizeof(flow->addrs.tipckey);
break; break;
} }
return (sizeof(*flow) - diff) / sizeof(u32); return sizeof(*flow) - diff;
} }
__be32 flow_get_u32_src(const struct flow_keys *flow) __be32 flow_get_u32_src(const struct flow_keys *flow)
@ -1454,13 +1445,14 @@ static inline void __flow_hash_consistentify(struct flow_keys *keys)
} }
} }
static inline u32 __flow_hash_from_keys(struct flow_keys *keys, u32 keyval) static inline u32 __flow_hash_from_keys(struct flow_keys *keys,
const siphash_key_t *keyval)
{ {
u32 hash; u32 hash;
__flow_hash_consistentify(keys); __flow_hash_consistentify(keys);
hash = __flow_hash_words(flow_keys_hash_start(keys), hash = siphash(flow_keys_hash_start(keys),
flow_keys_hash_length(keys), keyval); flow_keys_hash_length(keys), keyval);
if (!hash) if (!hash)
hash = 1; hash = 1;
@ -1471,12 +1463,13 @@ static inline u32 __flow_hash_from_keys(struct flow_keys *keys, u32 keyval)
u32 flow_hash_from_keys(struct flow_keys *keys) u32 flow_hash_from_keys(struct flow_keys *keys)
{ {
__flow_hash_secret_init(); __flow_hash_secret_init();
return __flow_hash_from_keys(keys, hashrnd); return __flow_hash_from_keys(keys, &hashrnd);
} }
EXPORT_SYMBOL(flow_hash_from_keys); EXPORT_SYMBOL(flow_hash_from_keys);
static inline u32 ___skb_get_hash(const struct sk_buff *skb, static inline u32 ___skb_get_hash(const struct sk_buff *skb,
struct flow_keys *keys, u32 keyval) struct flow_keys *keys,
const siphash_key_t *keyval)
{ {
skb_flow_dissect_flow_keys(skb, keys, skb_flow_dissect_flow_keys(skb, keys,
FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL); FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL);
@ -1524,7 +1517,7 @@ u32 __skb_get_hash_symmetric(const struct sk_buff *skb)
&keys, NULL, 0, 0, 0, &keys, NULL, 0, 0, 0,
FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL); FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL);
return __flow_hash_from_keys(&keys, hashrnd); return __flow_hash_from_keys(&keys, &hashrnd);
} }
EXPORT_SYMBOL_GPL(__skb_get_hash_symmetric); EXPORT_SYMBOL_GPL(__skb_get_hash_symmetric);
@ -1544,13 +1537,14 @@ void __skb_get_hash(struct sk_buff *skb)
__flow_hash_secret_init(); __flow_hash_secret_init();
hash = ___skb_get_hash(skb, &keys, hashrnd); hash = ___skb_get_hash(skb, &keys, &hashrnd);
__skb_set_sw_hash(skb, hash, flow_keys_have_l4(&keys)); __skb_set_sw_hash(skb, hash, flow_keys_have_l4(&keys));
} }
EXPORT_SYMBOL(__skb_get_hash); EXPORT_SYMBOL(__skb_get_hash);
__u32 skb_get_hash_perturb(const struct sk_buff *skb, u32 perturb) __u32 skb_get_hash_perturb(const struct sk_buff *skb,
const siphash_key_t *perturb)
{ {
struct flow_keys keys; struct flow_keys keys;

View File

@ -5,11 +5,11 @@
* Copyright (C) 2013 Nandita Dukkipati <nanditad@google.com> * Copyright (C) 2013 Nandita Dukkipati <nanditad@google.com>
*/ */
#include <linux/jhash.h>
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/siphash.h>
#include <net/pkt_sched.h> #include <net/pkt_sched.h>
#include <net/sock.h> #include <net/sock.h>
@ -126,7 +126,7 @@ struct wdrr_bucket {
struct hhf_sched_data { struct hhf_sched_data {
struct wdrr_bucket buckets[WDRR_BUCKET_CNT]; struct wdrr_bucket buckets[WDRR_BUCKET_CNT];
u32 perturbation; /* hash perturbation */ siphash_key_t perturbation; /* hash perturbation */
u32 quantum; /* psched_mtu(qdisc_dev(sch)); */ u32 quantum; /* psched_mtu(qdisc_dev(sch)); */
u32 drop_overlimit; /* number of times max qdisc packet u32 drop_overlimit; /* number of times max qdisc packet
* limit was hit * limit was hit
@ -264,7 +264,7 @@ static enum wdrr_bucket_idx hhf_classify(struct sk_buff *skb, struct Qdisc *sch)
} }
/* Get hashed flow-id of the skb. */ /* Get hashed flow-id of the skb. */
hash = skb_get_hash_perturb(skb, q->perturbation); hash = skb_get_hash_perturb(skb, &q->perturbation);
/* Check if this packet belongs to an already established HH flow. */ /* Check if this packet belongs to an already established HH flow. */
flow_pos = hash & HHF_BIT_MASK; flow_pos = hash & HHF_BIT_MASK;
@ -582,7 +582,7 @@ static int hhf_init(struct Qdisc *sch, struct nlattr *opt,
sch->limit = 1000; sch->limit = 1000;
q->quantum = psched_mtu(qdisc_dev(sch)); q->quantum = psched_mtu(qdisc_dev(sch));
q->perturbation = prandom_u32(); get_random_bytes(&q->perturbation, sizeof(q->perturbation));
INIT_LIST_HEAD(&q->new_buckets); INIT_LIST_HEAD(&q->new_buckets);
INIT_LIST_HEAD(&q->old_buckets); INIT_LIST_HEAD(&q->old_buckets);

View File

@ -18,7 +18,7 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/random.h> #include <linux/random.h>
#include <linux/jhash.h> #include <linux/siphash.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/pkt_sched.h> #include <net/pkt_sched.h>
#include <net/pkt_cls.h> #include <net/pkt_cls.h>
@ -45,7 +45,7 @@ struct sfb_bucket {
* (Section 4.4 of SFB reference : moving hash functions) * (Section 4.4 of SFB reference : moving hash functions)
*/ */
struct sfb_bins { struct sfb_bins {
u32 perturbation; /* jhash perturbation */ siphash_key_t perturbation; /* siphash key */
struct sfb_bucket bins[SFB_LEVELS][SFB_NUMBUCKETS]; struct sfb_bucket bins[SFB_LEVELS][SFB_NUMBUCKETS];
}; };
@ -217,7 +217,8 @@ static u32 sfb_compute_qlen(u32 *prob_r, u32 *avgpm_r, const struct sfb_sched_da
static void sfb_init_perturbation(u32 slot, struct sfb_sched_data *q) static void sfb_init_perturbation(u32 slot, struct sfb_sched_data *q)
{ {
q->bins[slot].perturbation = prandom_u32(); get_random_bytes(&q->bins[slot].perturbation,
sizeof(q->bins[slot].perturbation));
} }
static void sfb_swap_slot(struct sfb_sched_data *q) static void sfb_swap_slot(struct sfb_sched_data *q)
@ -314,9 +315,9 @@ static int sfb_enqueue(struct sk_buff *skb, struct Qdisc *sch,
/* If using external classifiers, get result and record it. */ /* If using external classifiers, get result and record it. */
if (!sfb_classify(skb, fl, &ret, &salt)) if (!sfb_classify(skb, fl, &ret, &salt))
goto other_drop; goto other_drop;
sfbhash = jhash_1word(salt, q->bins[slot].perturbation); sfbhash = siphash_1u32(salt, &q->bins[slot].perturbation);
} else { } else {
sfbhash = skb_get_hash_perturb(skb, q->bins[slot].perturbation); sfbhash = skb_get_hash_perturb(skb, &q->bins[slot].perturbation);
} }
@ -352,7 +353,7 @@ static int sfb_enqueue(struct sk_buff *skb, struct Qdisc *sch,
/* Inelastic flow */ /* Inelastic flow */
if (q->double_buffering) { if (q->double_buffering) {
sfbhash = skb_get_hash_perturb(skb, sfbhash = skb_get_hash_perturb(skb,
q->bins[slot].perturbation); &q->bins[slot].perturbation);
if (!sfbhash) if (!sfbhash)
sfbhash = 1; sfbhash = 1;
sfb_skb_cb(skb)->hashes[slot] = sfbhash; sfb_skb_cb(skb)->hashes[slot] = sfbhash;

View File

@ -14,7 +14,7 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/jhash.h> #include <linux/siphash.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <net/netlink.h> #include <net/netlink.h>
@ -117,7 +117,7 @@ struct sfq_sched_data {
u8 headdrop; u8 headdrop;
u8 maxdepth; /* limit of packets per flow */ u8 maxdepth; /* limit of packets per flow */
u32 perturbation; siphash_key_t perturbation;
u8 cur_depth; /* depth of longest slot */ u8 cur_depth; /* depth of longest slot */
u8 flags; u8 flags;
unsigned short scaled_quantum; /* SFQ_ALLOT_SIZE(quantum) */ unsigned short scaled_quantum; /* SFQ_ALLOT_SIZE(quantum) */
@ -157,7 +157,7 @@ static inline struct sfq_head *sfq_dep_head(struct sfq_sched_data *q, sfq_index
static unsigned int sfq_hash(const struct sfq_sched_data *q, static unsigned int sfq_hash(const struct sfq_sched_data *q,
const struct sk_buff *skb) const struct sk_buff *skb)
{ {
return skb_get_hash_perturb(skb, q->perturbation) & (q->divisor - 1); return skb_get_hash_perturb(skb, &q->perturbation) & (q->divisor - 1);
} }
static unsigned int sfq_classify(struct sk_buff *skb, struct Qdisc *sch, static unsigned int sfq_classify(struct sk_buff *skb, struct Qdisc *sch,
@ -607,9 +607,11 @@ static void sfq_perturbation(struct timer_list *t)
struct sfq_sched_data *q = from_timer(q, t, perturb_timer); struct sfq_sched_data *q = from_timer(q, t, perturb_timer);
struct Qdisc *sch = q->sch; struct Qdisc *sch = q->sch;
spinlock_t *root_lock = qdisc_lock(qdisc_root_sleeping(sch)); spinlock_t *root_lock = qdisc_lock(qdisc_root_sleeping(sch));
siphash_key_t nkey;
get_random_bytes(&nkey, sizeof(nkey));
spin_lock(root_lock); spin_lock(root_lock);
q->perturbation = prandom_u32(); q->perturbation = nkey;
if (!q->filter_list && q->tail) if (!q->filter_list && q->tail)
sfq_rehash(sch); sfq_rehash(sch);
spin_unlock(root_lock); spin_unlock(root_lock);
@ -688,7 +690,7 @@ static int sfq_change(struct Qdisc *sch, struct nlattr *opt)
del_timer(&q->perturb_timer); del_timer(&q->perturb_timer);
if (q->perturb_period) { if (q->perturb_period) {
mod_timer(&q->perturb_timer, jiffies + q->perturb_period); mod_timer(&q->perturb_timer, jiffies + q->perturb_period);
q->perturbation = prandom_u32(); get_random_bytes(&q->perturbation, sizeof(q->perturbation));
} }
sch_tree_unlock(sch); sch_tree_unlock(sch);
kfree(p); kfree(p);
@ -745,7 +747,7 @@ static int sfq_init(struct Qdisc *sch, struct nlattr *opt,
q->quantum = psched_mtu(qdisc_dev(sch)); q->quantum = psched_mtu(qdisc_dev(sch));
q->scaled_quantum = SFQ_ALLOT_SIZE(q->quantum); q->scaled_quantum = SFQ_ALLOT_SIZE(q->quantum);
q->perturb_period = 0; q->perturb_period = 0;
q->perturbation = prandom_u32(); get_random_bytes(&q->perturbation, sizeof(q->perturbation));
if (opt) { if (opt) {
int err = sfq_change(sch, opt); int err = sfq_change(sch, opt);