diff options
author | Eric Dumazet <eric.dumazet@gmail.com> | 2012-01-04 01:22:24 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-01-04 14:12:48 -0500 |
commit | bd16a6cce2a7f169b559abc5672fd2c66e91fb36 (patch) | |
tree | a4407fbec170a8ed06bf52a6667d26c4fdd2a124 /net/sched/sch_sfq.c | |
parent | 6cfb5e759d47f037cbd0953ec2c3ceb220ed9e96 (diff) |
net_sched: sfq: fix mem alloc error recovery
Since commit 817fb15dfd98 (net_sched: sfq: allow divisor to be a
parameter), we can leave perturbation timer armed if a memory allocation
error aborts sfq_init().
Memory containing active struct timer_list is freed and kernel can
crash.
Call sfq_destroy() from sfq_init() to properly dismantle qdisc.
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched/sch_sfq.c')
-rw-r--r-- | net/sched/sch_sfq.c | 52 |
1 files changed, 33 insertions, 19 deletions
diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index e9d5c911576d..16feb88503af 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c | |||
@@ -544,10 +544,38 @@ static int sfq_change(struct Qdisc *sch, struct nlattr *opt) | |||
544 | return 0; | 544 | return 0; |
545 | } | 545 | } |
546 | 546 | ||
547 | static void *sfq_alloc(size_t sz) | ||
548 | { | ||
549 | void *ptr = kmalloc(sz, GFP_KERNEL | __GFP_NOWARN); | ||
550 | |||
551 | if (!ptr) | ||
552 | ptr = vmalloc(sz); | ||
553 | return ptr; | ||
554 | } | ||
555 | |||
556 | static void sfq_free(void *addr) | ||
557 | { | ||
558 | if (addr) { | ||
559 | if (is_vmalloc_addr(addr)) | ||
560 | vfree(addr); | ||
561 | else | ||
562 | kfree(addr); | ||
563 | } | ||
564 | } | ||
565 | |||
566 | static void sfq_destroy(struct Qdisc *sch) | ||
567 | { | ||
568 | struct sfq_sched_data *q = qdisc_priv(sch); | ||
569 | |||
570 | tcf_destroy_chain(&q->filter_list); | ||
571 | q->perturb_period = 0; | ||
572 | del_timer_sync(&q->perturb_timer); | ||
573 | sfq_free(q->ht); | ||
574 | } | ||
575 | |||
547 | static int sfq_init(struct Qdisc *sch, struct nlattr *opt) | 576 | static int sfq_init(struct Qdisc *sch, struct nlattr *opt) |
548 | { | 577 | { |
549 | struct sfq_sched_data *q = qdisc_priv(sch); | 578 | struct sfq_sched_data *q = qdisc_priv(sch); |
550 | size_t sz; | ||
551 | int i; | 579 | int i; |
552 | 580 | ||
553 | q->perturb_timer.function = sfq_perturbation; | 581 | q->perturb_timer.function = sfq_perturbation; |
@@ -574,12 +602,11 @@ static int sfq_init(struct Qdisc *sch, struct nlattr *opt) | |||
574 | return err; | 602 | return err; |
575 | } | 603 | } |
576 | 604 | ||
577 | sz = sizeof(q->ht[0]) * q->divisor; | 605 | q->ht = sfq_alloc(sizeof(q->ht[0]) * q->divisor); |
578 | q->ht = kmalloc(sz, GFP_KERNEL); | 606 | if (!q->ht) { |
579 | if (!q->ht && sz > PAGE_SIZE) | 607 | sfq_destroy(sch); |
580 | q->ht = vmalloc(sz); | ||
581 | if (!q->ht) | ||
582 | return -ENOMEM; | 608 | return -ENOMEM; |
609 | } | ||
583 | for (i = 0; i < q->divisor; i++) | 610 | for (i = 0; i < q->divisor; i++) |
584 | q->ht[i] = SFQ_EMPTY_SLOT; | 611 | q->ht[i] = SFQ_EMPTY_SLOT; |
585 | 612 | ||
@@ -594,19 +621,6 @@ static int sfq_init(struct Qdisc *sch, struct nlattr *opt) | |||
594 | return 0; | 621 | return 0; |
595 | } | 622 | } |
596 | 623 | ||
597 | static void sfq_destroy(struct Qdisc *sch) | ||
598 | { | ||
599 | struct sfq_sched_data *q = qdisc_priv(sch); | ||
600 | |||
601 | tcf_destroy_chain(&q->filter_list); | ||
602 | q->perturb_period = 0; | ||
603 | del_timer_sync(&q->perturb_timer); | ||
604 | if (is_vmalloc_addr(q->ht)) | ||
605 | vfree(q->ht); | ||
606 | else | ||
607 | kfree(q->ht); | ||
608 | } | ||
609 | |||
610 | static int sfq_dump(struct Qdisc *sch, struct sk_buff *skb) | 624 | static int sfq_dump(struct Qdisc *sch, struct sk_buff *skb) |
611 | { | 625 | { |
612 | struct sfq_sched_data *q = qdisc_priv(sch); | 626 | struct sfq_sched_data *q = qdisc_priv(sch); |