/* net/sched/sch_ingress.c - Ingress and clsact qdisc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Authors: Jamal Hadi Salim 1999 */ #include #include #include #include #include #include #include #include static struct Qdisc *ingress_leaf(struct Qdisc *sch, unsigned long arg) { return NULL; } static unsigned long ingress_get(struct Qdisc *sch, u32 classid) { return TC_H_MIN(classid) + 1; } static bool ingress_cl_offload(u32 classid) { return true; } static unsigned long ingress_bind_filter(struct Qdisc *sch, unsigned long parent, u32 classid) { return ingress_get(sch, classid); } static void ingress_put(struct Qdisc *sch, unsigned long cl) { } static void ingress_walk(struct Qdisc *sch, struct qdisc_walker *walker) { } static struct tcf_proto __rcu **ingress_find_tcf(struct Qdisc *sch, unsigned long cl) { struct net_device *dev = qdisc_dev(sch); return &dev->ingress_cl_list; } static int ingress_init(struct Qdisc *sch, struct nlattr *opt) { net_inc_ingress_queue(); sch->flags |= TCQ_F_CPUSTATS; return 0; } static void ingress_destroy(struct Qdisc *sch) { struct net_device *dev = qdisc_dev(sch); tcf_destroy_chain(&dev->ingress_cl_list); net_dec_ingress_queue(); } static int ingress_dump(struct Qdisc *sch, struct sk_buff *skb) { struct nlattr *nest; nest = nla_nest_start(skb, TCA_OPTIONS); if (nest == NULL) goto nla_put_failure; return nla_nest_end(skb, nest); nla_put_failure: nla_nest_cancel(skb, nest); return -1; } static const struct Qdisc_class_ops ingress_class_ops = { .leaf = ingress_leaf, .get = ingress_get, .put = ingress_put, .walk = ingress_walk, .tcf_chain = ingress_find_tcf, .tcf_cl_offload = ingress_cl_offload, .bind_tcf = ingress_bind_filter, .unbind_tcf = ingress_put, }; static struct Qdisc_ops ingress_qdisc_ops __read_mostly = { .cl_ops = &ingress_class_ops, .id = "ingress", .init = ingress_init, .destroy = ingress_destroy, .dump = ingress_dump, .owner = THIS_MODULE, }; static unsigned long clsact_get(struct Qdisc *sch, u32 classid) { switch (TC_H_MIN(classid)) { case TC_H_MIN(TC_H_MIN_INGRESS): case TC_H_MIN(TC_H_MIN_EGRESS): return TC_H_MIN(classid); default: return 0; } } static bool clsact_cl_offload(u32 classid) { return TC_H_MIN(classid) == TC_H_MIN(TC_H_MIN_INGRESS); } static unsigned long clsact_bind_filter(struct Qdisc *sch, unsigned long parent, u32 classid) { return clsact_get(sch, classid); } static struct tcf_proto __rcu **clsact_find_tcf(struct Qdisc *sch, unsigned long cl) { struct net_device *dev = qdisc_dev(sch); switch (cl) { case TC_H_MIN(TC_H_MIN_INGRESS): return &dev->ingress_cl_list; case TC_H_MIN(TC_H_MIN_EGRESS): return &dev->egress_cl_list; default: return NULL; } } static int clsact_init(struct Qdisc *sch, struct nlattr *opt) { net_inc_ingress_queue(); net_inc_egress_queue(); sch->flags |= TCQ_F_CPUSTATS; return 0; } static void clsact_destroy(struct Qdisc *sch) { struct net_device *dev = qdisc_dev(sch); tcf_destroy_chain(&dev->ingress_cl_list); tcf_destroy_chain(&dev->egress_cl_list); net_dec_ingress_queue(); net_dec_egress_queue(); } static const struct Qdisc_class_ops clsact_class_ops = { .leaf = ingress_leaf, .get = clsact_get, .put = ingress_put, .walk = ingress_walk, .tcf_chain = clsact_find_tcf, .tcf_cl_offload = clsact_cl_offload, .bind_tcf = clsact_bind_filter, .unbind_tcf = ingress_put, }; static struct Qdisc_ops clsact_qdisc_ops __read_mostly = { .cl_ops = &clsact_class_ops, .id = "clsact", .init = clsact_init, .destroy = clsact_destroy, .dump = ingress_dump, .owner = THIS_MODULE, }; static int __init ingress_module_init(void) { int ret; ret = register_qdisc(&ingress_qdisc_ops); if (!ret) { ret = register_qdisc(&clsact_qdisc_ops); if (ret) unregister_qdisc(&ingress_qdisc_ops); } return ret; } static void __exit ingress_module_exit(void) { unregister_qdisc(&ingress_qdisc_ops); unregister_qdisc(&clsact_qdisc_ops); } module_init(ingress_module_init); module_exit(ingress_module_exit); MODULE_ALIAS("sch_clsact"); MODULE_LICENSE("GPL"); ommit-info'> authorDouglas Miller <dougmill@linux.vnet.ibm.com>2017-01-28 06:42:20 -0600 committerTejun Heo <tj@kernel.org>2017-01-28 07:49:42 -0500 commit966d2b04e070bc040319aaebfec09e0144dc3341 (patch) tree4b96156e3d1dd4dfd6039b7c219c9dc4616da52d /sound/sparc/Makefile parent1b1bc42c1692e9b62756323c675a44cb1a1f9dbd (diff)
percpu-refcount: fix reference leak during percpu-atomic transition
percpu_ref_tryget() and percpu_ref_tryget_live() should return "true" IFF they acquire a reference. But the return value from atomic_long_inc_not_zero() is a long and may have high bits set, e.g. PERCPU_COUNT_BIAS, and the return value of the tryget routines is bool so the reference may actually be acquired but the routines return "false" which results in a reference leak since the caller assumes it does not need to do a corresponding percpu_ref_put(). This was seen when performing CPU hotplug during I/O, as hangs in blk_mq_freeze_queue_wait where percpu_ref_kill (blk_mq_freeze_queue_start) raced with percpu_ref_tryget (blk_mq_timeout_work). Sample stack trace: __switch_to+0x2c0/0x450 __schedule+0x2f8/0x970 schedule+0x48/0xc0 blk_mq_freeze_queue_wait+0x94/0x120 blk_mq_queue_reinit_work+0xb8/0x180 blk_mq_queue_reinit_prepare+0x84/0xa0 cpuhp_invoke_callback+0x17c/0x600 cpuhp_up_callbacks+0x58/0x150 _cpu_up+0xf0/0x1c0 do_cpu_up+0x120/0x150 cpu_subsys_online+0x64/0xe0 device_online+0xb4/0x120 online_store+0xb4/0xc0 dev_attr_store+0x68/0xa0 sysfs_kf_write+0x80/0xb0 kernfs_fop_write+0x17c/0x250 __vfs_write+0x6c/0x1e0 vfs_write+0xd0/0x270 SyS_write+0x6c/0x110 system_call+0x38/0xe0 Examination of the queue showed a single reference (no PERCPU_COUNT_BIAS, and __PERCPU_REF_DEAD, __PERCPU_REF_ATOMIC set) and no requests. However, conditions at the time of the race are count of PERCPU_COUNT_BIAS + 0 and __PERCPU_REF_DEAD and __PERCPU_REF_ATOMIC set. The fix is to make the tryget routines use an actual boolean internally instead of the atomic long result truncated to a int. Fixes: e625305b3907 percpu-refcount: make percpu_ref based on longs instead of ints Link: https://bugzilla.kernel.org/show_bug.cgi?id=190751 Signed-off-by: Douglas Miller <dougmill@linux.vnet.ibm.com> Reviewed-by: Jens Axboe <axboe@fb.com> Signed-off-by: Tejun Heo <tj@kernel.org> Fixes: e625305b3907 ("percpu-refcount: make percpu_ref based on longs instead of ints") Cc: stable@vger.kernel.org # v3.18+
Diffstat (limited to 'sound/sparc/Makefile')