aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2009-09-09 21:11:23 -0400
committerDavid S. Miller <davem@davemloft.net>2009-09-09 21:11:23 -0400
commit23bcf634c8bc0d84607a5b863333191d58baee4c (patch)
treebc988ab6230bebf47e1dc22db70f94901ac2eeb5
parentea6a634ef7f0ab1d1f48ba0ad4f50e96d6065312 (diff)
net_sched: fix estimator lock selection for mq child qdiscs
When new child qdiscs are attached to the mq qdisc, they are actually attached as root qdiscs to the device queues. The lock selection for new estimators incorrectly picks the root lock of the existing and to be replaced qdisc, which results in a use-after-free once the old qdisc has been destroyed. Mark mq qdisc instances with a new flag and treat qdiscs attached to mq as children similar to regular root qdiscs. Additionally prevent estimators from being attached to the mq qdisc itself since it only updates its byte and packet counters during dumps. Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/net/sch_generic.h1
-rw-r--r--net/sched/sch_api.c42
-rw-r--r--net/sched/sch_mq.c1
3 files changed, 28 insertions, 16 deletions
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index 9c69585a1be8..88eb9de095de 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -46,6 +46,7 @@ struct Qdisc
46#define TCQ_F_THROTTLED 2 46#define TCQ_F_THROTTLED 2
47#define TCQ_F_INGRESS 4 47#define TCQ_F_INGRESS 4
48#define TCQ_F_CAN_BYPASS 8 48#define TCQ_F_CAN_BYPASS 8
49#define TCQ_F_MQROOT 16
49#define TCQ_F_WARN_NONWC (1 << 16) 50#define TCQ_F_WARN_NONWC (1 << 16)
50 int padded; 51 int padded;
51 struct Qdisc_ops *ops; 52 struct Qdisc_ops *ops;
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 2a78d5410154..3af106140f35 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -733,7 +733,8 @@ static struct lock_class_key qdisc_rx_lock;
733 733
734static struct Qdisc * 734static struct Qdisc *
735qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue, 735qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue,
736 u32 parent, u32 handle, struct nlattr **tca, int *errp) 736 struct Qdisc *p, u32 parent, u32 handle,
737 struct nlattr **tca, int *errp)
737{ 738{
738 int err; 739 int err;
739 struct nlattr *kind = tca[TCA_KIND]; 740 struct nlattr *kind = tca[TCA_KIND];
@@ -810,24 +811,21 @@ qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue,
810 if (tca[TCA_RATE]) { 811 if (tca[TCA_RATE]) {
811 spinlock_t *root_lock; 812 spinlock_t *root_lock;
812 813
814 err = -EOPNOTSUPP;
815 if (sch->flags & TCQ_F_MQROOT)
816 goto err_out4;
817
813 if ((sch->parent != TC_H_ROOT) && 818 if ((sch->parent != TC_H_ROOT) &&
814 !(sch->flags & TCQ_F_INGRESS)) 819 !(sch->flags & TCQ_F_INGRESS) &&
820 (!p || !(p->flags & TCQ_F_MQROOT)))
815 root_lock = qdisc_root_sleeping_lock(sch); 821 root_lock = qdisc_root_sleeping_lock(sch);
816 else 822 else
817 root_lock = qdisc_lock(sch); 823 root_lock = qdisc_lock(sch);
818 824
819 err = gen_new_estimator(&sch->bstats, &sch->rate_est, 825 err = gen_new_estimator(&sch->bstats, &sch->rate_est,
820 root_lock, tca[TCA_RATE]); 826 root_lock, tca[TCA_RATE]);
821 if (err) { 827 if (err)
822 /* 828 goto err_out4;
823 * Any broken qdiscs that would require
824 * a ops->reset() here? The qdisc was never
825 * in action so it shouldn't be necessary.
826 */
827 if (ops->destroy)
828 ops->destroy(sch);
829 goto err_out3;
830 }
831 } 829 }
832 830
833 qdisc_list_add(sch); 831 qdisc_list_add(sch);
@@ -843,6 +841,15 @@ err_out2:
843err_out: 841err_out:
844 *errp = err; 842 *errp = err;
845 return NULL; 843 return NULL;
844
845err_out4:
846 /*
847 * Any broken qdiscs that would require a ops->reset() here?
848 * The qdisc was never in action so it shouldn't be necessary.
849 */
850 if (ops->destroy)
851 ops->destroy(sch);
852 goto err_out3;
846} 853}
847 854
848static int qdisc_change(struct Qdisc *sch, struct nlattr **tca) 855static int qdisc_change(struct Qdisc *sch, struct nlattr **tca)
@@ -867,13 +874,16 @@ static int qdisc_change(struct Qdisc *sch, struct nlattr **tca)
867 qdisc_put_stab(sch->stab); 874 qdisc_put_stab(sch->stab);
868 sch->stab = stab; 875 sch->stab = stab;
869 876
870 if (tca[TCA_RATE]) 877 if (tca[TCA_RATE]) {
871 /* NB: ignores errors from replace_estimator 878 /* NB: ignores errors from replace_estimator
872 because change can't be undone. */ 879 because change can't be undone. */
880 if (sch->flags & TCQ_F_MQROOT)
881 goto out;
873 gen_replace_estimator(&sch->bstats, &sch->rate_est, 882 gen_replace_estimator(&sch->bstats, &sch->rate_est,
874 qdisc_root_sleeping_lock(sch), 883 qdisc_root_sleeping_lock(sch),
875 tca[TCA_RATE]); 884 tca[TCA_RATE]);
876 885 }
886out:
877 return 0; 887 return 0;
878} 888}
879 889
@@ -1097,7 +1107,7 @@ create_n_graft:
1097 if (!(n->nlmsg_flags&NLM_F_CREATE)) 1107 if (!(n->nlmsg_flags&NLM_F_CREATE))
1098 return -ENOENT; 1108 return -ENOENT;
1099 if (clid == TC_H_INGRESS) 1109 if (clid == TC_H_INGRESS)
1100 q = qdisc_create(dev, &dev->rx_queue, 1110 q = qdisc_create(dev, &dev->rx_queue, p,
1101 tcm->tcm_parent, tcm->tcm_parent, 1111 tcm->tcm_parent, tcm->tcm_parent,
1102 tca, &err); 1112 tca, &err);
1103 else { 1113 else {
@@ -1106,7 +1116,7 @@ create_n_graft:
1106 if (p && p->ops->cl_ops && p->ops->cl_ops->select_queue) 1116 if (p && p->ops->cl_ops && p->ops->cl_ops->select_queue)
1107 ntx = p->ops->cl_ops->select_queue(p, tcm); 1117 ntx = p->ops->cl_ops->select_queue(p, tcm);
1108 1118
1109 q = qdisc_create(dev, netdev_get_tx_queue(dev, ntx), 1119 q = qdisc_create(dev, netdev_get_tx_queue(dev, ntx), p,
1110 tcm->tcm_parent, tcm->tcm_handle, 1120 tcm->tcm_parent, tcm->tcm_handle,
1111 tca, &err); 1121 tca, &err);
1112 } 1122 }
diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c
index c84dec9c8c7d..dd5ee022f1f7 100644
--- a/net/sched/sch_mq.c
+++ b/net/sched/sch_mq.c
@@ -64,6 +64,7 @@ static int mq_init(struct Qdisc *sch, struct nlattr *opt)
64 priv->qdiscs[ntx] = qdisc; 64 priv->qdiscs[ntx] = qdisc;
65 } 65 }
66 66
67 sch->flags |= TCQ_F_MQROOT;
67 return 0; 68 return 0;
68 69
69err: 70err: