mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-24 10:51:57 +07:00
33902b4a42
In nsim_fib_init(), if register_fib_notifier failed, nsim_fib_net_ops
should be unregistered before return.
In nsim_fib_exit(), unregister_fib_notifier should be called before
nsim_fib_net_ops be unregistered, otherwise may cause use-after-free:
BUG: KASAN: use-after-free in nsim_fib_event_nb+0x342/0x570 [netdevsim]
Read of size 8 at addr ffff8881daaf4388 by task kworker/0:3/3499
CPU: 0 PID: 3499 Comm: kworker/0:3 Not tainted 5.3.0-rc7+ #30
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
Workqueue: ipv6_addrconf addrconf_dad_work [ipv6]
Call Trace:
__dump_stack lib/dump_stack.c:77 [inline]
dump_stack+0xa9/0x10e lib/dump_stack.c:113
print_address_description+0x65/0x380 mm/kasan/report.c:351
__kasan_report+0x149/0x18d mm/kasan/report.c:482
kasan_report+0xe/0x20 mm/kasan/common.c:618
nsim_fib_event_nb+0x342/0x570 [netdevsim]
notifier_call_chain+0x52/0xf0 kernel/notifier.c:95
__atomic_notifier_call_chain+0x78/0x140 kernel/notifier.c:185
call_fib_notifiers+0x30/0x60 net/core/fib_notifier.c:30
call_fib6_entry_notifiers+0xc1/0x100 [ipv6]
fib6_add+0x92e/0x1b10 [ipv6]
__ip6_ins_rt+0x40/0x60 [ipv6]
ip6_ins_rt+0x84/0xb0 [ipv6]
__ipv6_ifa_notify+0x4b6/0x550 [ipv6]
ipv6_ifa_notify+0xa5/0x180 [ipv6]
addrconf_dad_completed+0xca/0x640 [ipv6]
addrconf_dad_work+0x296/0x960 [ipv6]
process_one_work+0x5c0/0xc00 kernel/workqueue.c:2269
worker_thread+0x5c/0x670 kernel/workqueue.c:2415
kthread+0x1d7/0x200 kernel/kthread.c:255
ret_from_fork+0x3a/0x50 arch/x86/entry/entry_64.S:352
Allocated by task 3388:
save_stack+0x19/0x80 mm/kasan/common.c:69
set_track mm/kasan/common.c:77 [inline]
__kasan_kmalloc.constprop.3+0xa0/0xd0 mm/kasan/common.c:493
kmalloc include/linux/slab.h:557 [inline]
kzalloc include/linux/slab.h:748 [inline]
ops_init+0xa9/0x220 net/core/net_namespace.c:127
__register_pernet_operations net/core/net_namespace.c:1135 [inline]
register_pernet_operations+0x1d4/0x420 net/core/net_namespace.c:1212
register_pernet_subsys+0x24/0x40 net/core/net_namespace.c:1253
nsim_fib_init+0x12/0x70 [netdevsim]
veth_get_link_ksettings+0x2b/0x50 [veth]
do_one_initcall+0xd4/0x454 init/main.c:939
do_init_module+0xe0/0x330 kernel/module.c:3490
load_module+0x3c2f/0x4620 kernel/module.c:3841
__do_sys_finit_module+0x163/0x190 kernel/module.c:3931
do_syscall_64+0x72/0x2e0 arch/x86/entry/common.c:296
entry_SYSCALL_64_after_hwframe+0x49/0xbe
Freed by task 3534:
save_stack+0x19/0x80 mm/kasan/common.c:69
set_track mm/kasan/common.c:77 [inline]
__kasan_slab_free+0x130/0x180 mm/kasan/common.c:455
slab_free_hook mm/slub.c:1423 [inline]
slab_free_freelist_hook mm/slub.c:1474 [inline]
slab_free mm/slub.c:3016 [inline]
kfree+0xe9/0x2d0 mm/slub.c:3957
ops_free net/core/net_namespace.c:151 [inline]
ops_free_list.part.7+0x156/0x220 net/core/net_namespace.c:184
ops_free_list net/core/net_namespace.c:182 [inline]
__unregister_pernet_operations net/core/net_namespace.c:1165 [inline]
unregister_pernet_operations+0x221/0x2a0 net/core/net_namespace.c:1224
unregister_pernet_subsys+0x1d/0x30 net/core/net_namespace.c:1271
nsim_fib_exit+0x11/0x20 [netdevsim]
nsim_module_exit+0x16/0x21 [netdevsim]
__do_sys_delete_module kernel/module.c:1015 [inline]
__se_sys_delete_module kernel/module.c:958 [inline]
__x64_sys_delete_module+0x244/0x330 kernel/module.c:958
do_syscall_64+0x72/0x2e0 arch/x86/entry/common.c:296
entry_SYSCALL_64_after_hwframe+0x49/0xbe
Reported-by: Hulk Robot <hulkci@huawei.com>
Fixes: 59c84b9fcf
("netdevsim: Restore per-network namespace accounting for fib entries")
Signed-off-by: YueHaibing <yuehaibing@huawei.com>
Acked-by: Jakub Kicinski <jakub.kicinski@netronome.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
306 lines
7.1 KiB
C
306 lines
7.1 KiB
C
/*
|
|
* Copyright (c) 2018 Cumulus Networks. All rights reserved.
|
|
* Copyright (c) 2018 David Ahern <dsa@cumulusnetworks.com>
|
|
*
|
|
* This software is licensed under the GNU General License Version 2,
|
|
* June 1991 as shown in the file COPYING in the top-level directory of this
|
|
* source tree.
|
|
*
|
|
* THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
|
|
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
|
* OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
|
|
* THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
*/
|
|
|
|
#include <net/fib_notifier.h>
|
|
#include <net/ip_fib.h>
|
|
#include <net/ip6_fib.h>
|
|
#include <net/fib_rules.h>
|
|
#include <net/net_namespace.h>
|
|
|
|
#include "netdevsim.h"
|
|
|
|
struct nsim_fib_entry {
|
|
u64 max;
|
|
u64 num;
|
|
};
|
|
|
|
struct nsim_per_fib_data {
|
|
struct nsim_fib_entry fib;
|
|
struct nsim_fib_entry rules;
|
|
};
|
|
|
|
struct nsim_fib_data {
|
|
struct notifier_block fib_nb;
|
|
struct nsim_per_fib_data ipv4;
|
|
struct nsim_per_fib_data ipv6;
|
|
};
|
|
|
|
u64 nsim_fib_get_val(struct nsim_fib_data *fib_data,
|
|
enum nsim_resource_id res_id, bool max)
|
|
{
|
|
struct nsim_fib_entry *entry;
|
|
|
|
switch (res_id) {
|
|
case NSIM_RESOURCE_IPV4_FIB:
|
|
entry = &fib_data->ipv4.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV4_FIB_RULES:
|
|
entry = &fib_data->ipv4.rules;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB:
|
|
entry = &fib_data->ipv6.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB_RULES:
|
|
entry = &fib_data->ipv6.rules;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return max ? entry->max : entry->num;
|
|
}
|
|
|
|
static void nsim_fib_set_max(struct nsim_fib_data *fib_data,
|
|
enum nsim_resource_id res_id, u64 val)
|
|
{
|
|
struct nsim_fib_entry *entry;
|
|
|
|
switch (res_id) {
|
|
case NSIM_RESOURCE_IPV4_FIB:
|
|
entry = &fib_data->ipv4.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV4_FIB_RULES:
|
|
entry = &fib_data->ipv4.rules;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB:
|
|
entry = &fib_data->ipv6.fib;
|
|
break;
|
|
case NSIM_RESOURCE_IPV6_FIB_RULES:
|
|
entry = &fib_data->ipv6.rules;
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
entry->max = val;
|
|
}
|
|
|
|
static int nsim_fib_rule_account(struct nsim_fib_entry *entry, bool add,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
int err = 0;
|
|
|
|
if (add) {
|
|
if (entry->num < entry->max) {
|
|
entry->num++;
|
|
} else {
|
|
err = -ENOSPC;
|
|
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib rule entries");
|
|
}
|
|
} else {
|
|
entry->num--;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_rule_event(struct nsim_fib_data *data,
|
|
struct fib_notifier_info *info, bool add)
|
|
{
|
|
struct netlink_ext_ack *extack = info->extack;
|
|
int err = 0;
|
|
|
|
switch (info->family) {
|
|
case AF_INET:
|
|
err = nsim_fib_rule_account(&data->ipv4.rules, add, extack);
|
|
break;
|
|
case AF_INET6:
|
|
err = nsim_fib_rule_account(&data->ipv6.rules, add, extack);
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_account(struct nsim_fib_entry *entry, bool add,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
int err = 0;
|
|
|
|
if (add) {
|
|
if (entry->num < entry->max) {
|
|
entry->num++;
|
|
} else {
|
|
err = -ENOSPC;
|
|
NL_SET_ERR_MSG_MOD(extack, "Exceeded number of supported fib entries");
|
|
}
|
|
} else {
|
|
entry->num--;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_event(struct nsim_fib_data *data,
|
|
struct fib_notifier_info *info, bool add)
|
|
{
|
|
struct netlink_ext_ack *extack = info->extack;
|
|
int err = 0;
|
|
|
|
switch (info->family) {
|
|
case AF_INET:
|
|
err = nsim_fib_account(&data->ipv4.fib, add, extack);
|
|
break;
|
|
case AF_INET6:
|
|
err = nsim_fib_account(&data->ipv6.fib, add, extack);
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int nsim_fib_event_nb(struct notifier_block *nb, unsigned long event,
|
|
void *ptr)
|
|
{
|
|
struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
|
|
fib_nb);
|
|
struct fib_notifier_info *info = ptr;
|
|
int err = 0;
|
|
|
|
switch (event) {
|
|
case FIB_EVENT_RULE_ADD: /* fall through */
|
|
case FIB_EVENT_RULE_DEL:
|
|
err = nsim_fib_rule_event(data, info,
|
|
event == FIB_EVENT_RULE_ADD);
|
|
break;
|
|
|
|
case FIB_EVENT_ENTRY_ADD: /* fall through */
|
|
case FIB_EVENT_ENTRY_DEL:
|
|
err = nsim_fib_event(data, info,
|
|
event == FIB_EVENT_ENTRY_ADD);
|
|
break;
|
|
}
|
|
|
|
return notifier_from_errno(err);
|
|
}
|
|
|
|
/* inconsistent dump, trying again */
|
|
static void nsim_fib_dump_inconsistent(struct notifier_block *nb)
|
|
{
|
|
struct nsim_fib_data *data = container_of(nb, struct nsim_fib_data,
|
|
fib_nb);
|
|
|
|
data->ipv4.fib.num = 0ULL;
|
|
data->ipv4.rules.num = 0ULL;
|
|
data->ipv6.fib.num = 0ULL;
|
|
data->ipv6.rules.num = 0ULL;
|
|
}
|
|
|
|
static u64 nsim_fib_ipv4_resource_occ_get(void *priv)
|
|
{
|
|
struct nsim_fib_data *data = priv;
|
|
|
|
return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB, false);
|
|
}
|
|
|
|
static u64 nsim_fib_ipv4_rules_res_occ_get(void *priv)
|
|
{
|
|
struct nsim_fib_data *data = priv;
|
|
|
|
return nsim_fib_get_val(data, NSIM_RESOURCE_IPV4_FIB_RULES, false);
|
|
}
|
|
|
|
static u64 nsim_fib_ipv6_resource_occ_get(void *priv)
|
|
{
|
|
struct nsim_fib_data *data = priv;
|
|
|
|
return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB, false);
|
|
}
|
|
|
|
static u64 nsim_fib_ipv6_rules_res_occ_get(void *priv)
|
|
{
|
|
struct nsim_fib_data *data = priv;
|
|
|
|
return nsim_fib_get_val(data, NSIM_RESOURCE_IPV6_FIB_RULES, false);
|
|
}
|
|
|
|
static void nsim_fib_set_max_all(struct nsim_fib_data *data,
|
|
struct devlink *devlink)
|
|
{
|
|
enum nsim_resource_id res_ids[] = {
|
|
NSIM_RESOURCE_IPV4_FIB, NSIM_RESOURCE_IPV4_FIB_RULES,
|
|
NSIM_RESOURCE_IPV6_FIB, NSIM_RESOURCE_IPV6_FIB_RULES
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(res_ids); i++) {
|
|
int err;
|
|
u64 val;
|
|
|
|
err = devlink_resource_size_get(devlink, res_ids[i], &val);
|
|
if (err)
|
|
val = (u64) -1;
|
|
nsim_fib_set_max(data, res_ids[i], val);
|
|
}
|
|
}
|
|
|
|
struct nsim_fib_data *nsim_fib_create(struct devlink *devlink,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct nsim_fib_data *data;
|
|
int err;
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
nsim_fib_set_max_all(data, devlink);
|
|
|
|
data->fib_nb.notifier_call = nsim_fib_event_nb;
|
|
err = register_fib_notifier(devlink_net(devlink), &data->fib_nb,
|
|
nsim_fib_dump_inconsistent, extack);
|
|
if (err) {
|
|
pr_err("Failed to register fib notifier\n");
|
|
goto err_out;
|
|
}
|
|
|
|
devlink_resource_occ_get_register(devlink,
|
|
NSIM_RESOURCE_IPV4_FIB,
|
|
nsim_fib_ipv4_resource_occ_get,
|
|
data);
|
|
devlink_resource_occ_get_register(devlink,
|
|
NSIM_RESOURCE_IPV4_FIB_RULES,
|
|
nsim_fib_ipv4_rules_res_occ_get,
|
|
data);
|
|
devlink_resource_occ_get_register(devlink,
|
|
NSIM_RESOURCE_IPV6_FIB,
|
|
nsim_fib_ipv6_resource_occ_get,
|
|
data);
|
|
devlink_resource_occ_get_register(devlink,
|
|
NSIM_RESOURCE_IPV6_FIB_RULES,
|
|
nsim_fib_ipv6_rules_res_occ_get,
|
|
data);
|
|
return data;
|
|
|
|
err_out:
|
|
kfree(data);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
void nsim_fib_destroy(struct devlink *devlink, struct nsim_fib_data *data)
|
|
{
|
|
devlink_resource_occ_get_unregister(devlink,
|
|
NSIM_RESOURCE_IPV6_FIB_RULES);
|
|
devlink_resource_occ_get_unregister(devlink,
|
|
NSIM_RESOURCE_IPV6_FIB);
|
|
devlink_resource_occ_get_unregister(devlink,
|
|
NSIM_RESOURCE_IPV4_FIB_RULES);
|
|
devlink_resource_occ_get_unregister(devlink,
|
|
NSIM_RESOURCE_IPV4_FIB);
|
|
unregister_fib_notifier(devlink_net(devlink), &data->fib_nb);
|
|
kfree(data);
|
|
}
|