Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: dsa: fix 'increment on 0' warning

Setting the refcount to 0 when allocating a tree to match the number of
switch devices it holds may cause an 'increment on 0; use-after-free',
if CONFIG_REFCOUNT_FULL is enabled.

To fix this, do not decrement the refcount of a newly allocated tree,
increment it when an already allocated tree is found, and decrement it
after the probing of a switch, as done with the previous behavior.

At the same time, make dsa_tree_get and dsa_tree_put accept a NULL
argument to simplify callers, and return the tree after incrementation,
as most kref users like of_node_get and of_node_put do.

Fixes: 8e5bf9759a06 ("net: dsa: simplify tree reference counting")
Signed-off-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Tested-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Vivien Didelot and committed by
David S. Miller
9e741045 afbea2cd

+15 -12
+15 -12
net/dsa/dsa2.c
··· 51 51 INIT_LIST_HEAD(&dst->list); 52 52 list_add_tail(&dsa_tree_list, &dst->list); 53 53 54 - /* Initialize the reference counter to the number of switches, not 1 */ 55 54 kref_init(&dst->refcount); 56 - refcount_set(&dst->refcount.refcount, 0); 57 55 58 56 return dst; 59 57 } ··· 62 64 kfree(dst); 63 65 } 64 66 67 + static struct dsa_switch_tree *dsa_tree_get(struct dsa_switch_tree *dst) 68 + { 69 + if (dst) 70 + kref_get(&dst->refcount); 71 + 72 + return dst; 73 + } 74 + 65 75 static struct dsa_switch_tree *dsa_tree_touch(int index) 66 76 { 67 77 struct dsa_switch_tree *dst; 68 78 69 79 dst = dsa_tree_find(index); 70 - if (!dst) 71 - dst = dsa_tree_alloc(index); 72 - 73 - return dst; 74 - } 75 - 76 - static void dsa_tree_get(struct dsa_switch_tree *dst) 77 - { 78 - kref_get(&dst->refcount); 80 + if (dst) 81 + return dsa_tree_get(dst); 82 + else 83 + return dsa_tree_alloc(index); 79 84 } 80 85 81 86 static void dsa_tree_release(struct kref *ref) ··· 92 91 93 92 static void dsa_tree_put(struct dsa_switch_tree *dst) 94 93 { 95 - kref_put(&dst->refcount, dsa_tree_release); 94 + if (dst) 95 + kref_put(&dst->refcount, dsa_tree_release); 96 96 } 97 97 98 98 static bool dsa_port_is_dsa(struct dsa_port *port) ··· 767 765 768 766 mutex_lock(&dsa2_mutex); 769 767 err = dsa_switch_probe(ds); 768 + dsa_tree_put(ds->dst); 770 769 mutex_unlock(&dsa2_mutex); 771 770 772 771 return err;