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

Configure Feed

Select the types of activity you want to include in your feed.

ipmr: Free mr_table after RCU grace period.

With CONFIG_IP_MROUTE_MULTIPLE_TABLES=n, ipmr_fib_lookup()
does not check if net->ipv4.mrt is NULL.

Since default_device_exit_batch() is called after ->exit_rtnl(),
a device could receive IGMP packets and access net->ipv4.mrt
during/after ipmr_rules_exit_rtnl().

If ipmr_rules_exit_rtnl() had already cleared it and freed the
memory, the access would trigger null-ptr-deref or use-after-free.

Let's fix it by using RCU helper and free mrt after RCU grace
period.

In addition, check_net(net) is added to mroute_clean_tables()
and ipmr_cache_unresolved() to synchronise via mfc_unres_lock.
This prevents ipmr_cache_unresolved() from putting skb into
c->_c.mfc_un.unres.unresolved after mroute_clean_tables()
purges it.

For the same reason, timer_shutdown_sync() is moved after
mroute_clean_tables().

Since rhltable_destroy() holds mutex internally, rcu_work is
used, and it is placed as the first member because rcu_head
must be placed within <4K offset. mr_table is alraedy 3864
bytes without rcu_work.

Note that IP6MR is not yet converted to ->exit_rtnl(), so this
change is not needed for now but will be.

Fixes: b22b01867406 ("ipmr: Convert ipmr_net_exit_batch() to ->exit_rtnl().")
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20260423053456.4097409-1-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Kuniyuki Iwashima and committed by
Jakub Kicinski
b3b6babf 5b0c911b

+77 -50
+3
include/linux/mroute_base.h
··· 226 226 227 227 /** 228 228 * struct mr_table - a multicast routing table 229 + * @work: used for table destruction 229 230 * @list: entry within a list of multicast routing tables 230 231 * @net: net where this table belongs 231 232 * @ops: protocol specific operations ··· 244 243 * @mroute_reg_vif_num: PIM-device vif index 245 244 */ 246 245 struct mr_table { 246 + struct rcu_work work; 247 247 struct list_head list; 248 248 possible_net_t net; 249 249 struct mr_table_ops ops; ··· 276 274 unsigned short flags, 277 275 unsigned short get_iflink_mask); 278 276 277 + void mr_table_free(struct mr_table *mrt); 279 278 struct mr_table * 280 279 mr_table_alloc(struct net *net, u32 id, 281 280 struct mr_table_ops *ops,
+58 -50
net/ipv4/ipmr.c
··· 151 151 return NULL; 152 152 } 153 153 154 - static struct mr_table *ipmr_get_table(struct net *net, u32 id) 155 - { 156 - struct mr_table *mrt; 157 - 158 - rcu_read_lock(); 159 - mrt = __ipmr_get_table(net, id); 160 - rcu_read_unlock(); 161 - return mrt; 162 - } 163 - 164 154 static int ipmr_fib_lookup(struct net *net, struct flowi4 *flp4, 165 155 struct mr_table **mrt) 166 156 { ··· 283 293 struct mr_table *mrt, *next; 284 294 285 295 list_for_each_entry_safe(mrt, next, &net->ipv4.mr_tables, list) { 286 - list_del(&mrt->list); 296 + list_del_rcu(&mrt->list); 287 297 ipmr_free_table(mrt, dev_kill_list); 288 298 } 289 299 } ··· 305 315 } 306 316 EXPORT_SYMBOL(ipmr_rule_default); 307 317 #else 308 - #define ipmr_for_each_table(mrt, net) \ 309 - for (mrt = net->ipv4.mrt; mrt; mrt = NULL) 310 - 311 318 static struct mr_table *ipmr_mr_table_iter(struct net *net, 312 319 struct mr_table *mrt) 313 320 { 314 321 if (!mrt) 315 - return net->ipv4.mrt; 322 + return rcu_dereference(net->ipv4.mrt); 316 323 return NULL; 317 324 } 318 325 319 - static struct mr_table *ipmr_get_table(struct net *net, u32 id) 326 + static struct mr_table *__ipmr_get_table(struct net *net, u32 id) 320 327 { 321 - return net->ipv4.mrt; 328 + return rcu_dereference_check(net->ipv4.mrt, 329 + lockdep_rtnl_is_held() || 330 + !rcu_access_pointer(net->ipv4.mrt)); 322 331 } 323 332 324 - #define __ipmr_get_table ipmr_get_table 333 + #define ipmr_for_each_table(mrt, net) \ 334 + for (mrt = __ipmr_get_table(net, 0); mrt; mrt = NULL) 325 335 326 336 static int ipmr_fib_lookup(struct net *net, struct flowi4 *flp4, 327 337 struct mr_table **mrt) 328 338 { 329 - *mrt = net->ipv4.mrt; 339 + *mrt = rcu_dereference(net->ipv4.mrt); 340 + if (!*mrt) 341 + return -EAGAIN; 330 342 return 0; 331 343 } 332 344 ··· 339 347 mrt = ipmr_new_table(net, RT_TABLE_DEFAULT); 340 348 if (IS_ERR(mrt)) 341 349 return PTR_ERR(mrt); 342 - net->ipv4.mrt = mrt; 350 + 351 + rcu_assign_pointer(net->ipv4.mrt, mrt); 343 352 return 0; 344 353 } 345 354 ··· 351 358 static void __net_exit ipmr_rules_exit_rtnl(struct net *net, 352 359 struct list_head *dev_kill_list) 353 360 { 354 - ipmr_free_table(net->ipv4.mrt, dev_kill_list); 361 + struct mr_table *mrt = rcu_dereference_protected(net->ipv4.mrt, 1); 355 362 356 - net->ipv4.mrt = NULL; 363 + RCU_INIT_POINTER(net->ipv4.mrt, NULL); 364 + ipmr_free_table(mrt, dev_kill_list); 357 365 } 358 366 359 367 static int ipmr_rules_dump(struct net *net, struct notifier_block *nb, ··· 374 380 } 375 381 EXPORT_SYMBOL(ipmr_rule_default); 376 382 #endif 383 + 384 + static struct mr_table *ipmr_get_table(struct net *net, u32 id) 385 + { 386 + struct mr_table *mrt; 387 + 388 + rcu_read_lock(); 389 + mrt = __ipmr_get_table(net, id); 390 + rcu_read_unlock(); 391 + 392 + return mrt; 393 + } 377 394 378 395 static inline int ipmr_hash_cmp(struct rhashtable_compare_arg *arg, 379 396 const void *ptr) ··· 446 441 447 442 WARN_ON_ONCE(!mr_can_free_table(net)); 448 443 449 - timer_shutdown_sync(&mrt->ipmr_expire_timer); 450 444 mroute_clean_tables(mrt, MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC | 451 445 MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC, 452 446 &ipmr_dev_kill_list); 453 - rhltable_destroy(&mrt->mfc_hash); 454 - kfree(mrt); 447 + timer_shutdown_sync(&mrt->ipmr_expire_timer); 448 + mr_table_free(mrt); 455 449 456 450 WARN_ON_ONCE(!net_initialized(net) && !list_empty(&ipmr_dev_kill_list)); 457 451 list_splice(&ipmr_dev_kill_list, dev_kill_list); ··· 1139 1135 static int ipmr_cache_unresolved(struct mr_table *mrt, vifi_t vifi, 1140 1136 struct sk_buff *skb, struct net_device *dev) 1141 1137 { 1138 + struct net *net = read_pnet(&mrt->net); 1142 1139 const struct iphdr *iph = ip_hdr(skb); 1143 - struct mfc_cache *c; 1140 + struct mfc_cache *c = NULL; 1144 1141 bool found = false; 1145 1142 int err; 1146 1143 1147 1144 spin_lock_bh(&mfc_unres_lock); 1145 + 1146 + if (!check_net(net)) { 1147 + err = -EINVAL; 1148 + goto err; 1149 + } 1150 + 1148 1151 list_for_each_entry(c, &mrt->mfc_unres_queue, _c.list) { 1149 1152 if (c->mfc_mcastgrp == iph->daddr && 1150 1153 c->mfc_origin == iph->saddr) { ··· 1164 1153 /* Create a new entry if allowable */ 1165 1154 c = ipmr_cache_alloc_unres(); 1166 1155 if (!c) { 1167 - spin_unlock_bh(&mfc_unres_lock); 1168 - 1169 - kfree_skb(skb); 1170 - return -ENOBUFS; 1156 + err = -ENOBUFS; 1157 + goto err; 1171 1158 } 1172 1159 1173 1160 /* Fill in the new cache entry */ ··· 1175 1166 1176 1167 /* Reflect first query at mrouted. */ 1177 1168 err = ipmr_cache_report(mrt, skb, vifi, IGMPMSG_NOCACHE); 1178 - 1179 - if (err < 0) { 1180 - /* If the report failed throw the cache entry 1181 - out - Brad Parker 1182 - */ 1183 - spin_unlock_bh(&mfc_unres_lock); 1184 - 1185 - ipmr_cache_free(c); 1186 - kfree_skb(skb); 1187 - return err; 1188 - } 1169 + if (err < 0) 1170 + goto err; 1189 1171 1190 1172 atomic_inc(&mrt->cache_resolve_queue_len); 1191 1173 list_add(&c->_c.list, &mrt->mfc_unres_queue); ··· 1189 1189 1190 1190 /* See if we can append the packet */ 1191 1191 if (c->_c.mfc_un.unres.unresolved.qlen > 3) { 1192 - kfree_skb(skb); 1192 + c = NULL; 1193 1193 err = -ENOBUFS; 1194 - } else { 1195 - if (dev) { 1196 - skb->dev = dev; 1197 - skb->skb_iif = dev->ifindex; 1198 - } 1199 - skb_queue_tail(&c->_c.mfc_un.unres.unresolved, skb); 1200 - err = 0; 1194 + goto err; 1201 1195 } 1202 1196 1197 + if (dev) { 1198 + skb->dev = dev; 1199 + skb->skb_iif = dev->ifindex; 1200 + } 1201 + 1202 + skb_queue_tail(&c->_c.mfc_un.unres.unresolved, skb); 1203 + 1203 1204 spin_unlock_bh(&mfc_unres_lock); 1205 + return 0; 1206 + 1207 + err: 1208 + spin_unlock_bh(&mfc_unres_lock); 1209 + if (c) 1210 + ipmr_cache_free(c); 1211 + kfree_skb(skb); 1204 1212 return err; 1205 1213 } 1206 1214 ··· 1354 1346 } 1355 1347 1356 1348 if (flags & MRT_FLUSH_MFC) { 1357 - if (atomic_read(&mrt->cache_resolve_queue_len) != 0) { 1349 + if (atomic_read(&mrt->cache_resolve_queue_len) != 0 || !check_net(net)) { 1358 1350 spin_lock_bh(&mfc_unres_lock); 1359 1351 list_for_each_entry_safe(c, tmp, &mrt->mfc_unres_queue, list) { 1360 1352 list_del(&c->list);
+16
net/ipv4/ipmr_base.c
··· 28 28 v->link = dev->ifindex; 29 29 } 30 30 31 + static void __mr_free_table(struct work_struct *work) 32 + { 33 + struct mr_table *mrt = container_of(to_rcu_work(work), 34 + struct mr_table, work); 35 + 36 + rhltable_destroy(&mrt->mfc_hash); 37 + kfree(mrt); 38 + } 39 + 40 + void mr_table_free(struct mr_table *mrt) 41 + { 42 + queue_rcu_work(system_unbound_wq, &mrt->work); 43 + } 44 + 31 45 struct mr_table * 32 46 mr_table_alloc(struct net *net, u32 id, 33 47 struct mr_table_ops *ops, ··· 64 50 kfree(mrt); 65 51 return ERR_PTR(err); 66 52 } 53 + 54 + INIT_RCU_WORK(&mrt->work, __mr_free_table); 67 55 INIT_LIST_HEAD(&mrt->mfc_cache_list); 68 56 INIT_LIST_HEAD(&mrt->mfc_unres_queue); 69 57