28259bac7f
Syzbot reported the suspecious RCU usage in nexthop_fib6_nh() when
called from ipv6_route_seq_show(). The reason is ipv6_route_seq_start()
calls rcu_read_lock_bh(), while nexthop_fib6_nh() calls
rcu_dereference_rtnl().
The fix proposed is to add a variant of nexthop_fib6_nh() to use
rcu_dereference_bh_rtnl() for ipv6_route_seq_show().
The reported trace is as follows:
./include/net/nexthop.h:416 suspicious rcu_dereference_check() usage!
other info that might help us debug this:
rcu_scheduler_active = 2, debug_locks = 1
2 locks held by syz-executor.0/17895:
at: seq_read+0x71/0x12a0 fs/seq_file.c:169
at: seq_file_net include/linux/seq_file_net.h:19 [inline]
at: ipv6_route_seq_start+0xaf/0x300 net/ipv6/ip6_fib.c:2616
stack backtrace:
CPU: 1 PID: 17895 Comm: syz-executor.0 Not tainted 4.15.0-syzkaller #0
Call Trace:
[<ffffffff849edf9e>] __dump_stack lib/dump_stack.c:17 [inline]
[<ffffffff849edf9e>] dump_stack+0xd8/0x147 lib/dump_stack.c:53
[<ffffffff8480b7fa>] lockdep_rcu_suspicious+0x153/0x15d kernel/locking/lockdep.c:5745
[<ffffffff8459ada6>] nexthop_fib6_nh include/net/nexthop.h:416 [inline]
[<ffffffff8459ada6>] ipv6_route_native_seq_show net/ipv6/ip6_fib.c:2488 [inline]
[<ffffffff8459ada6>] ipv6_route_seq_show+0x436/0x7a0 net/ipv6/ip6_fib.c:2673
[<ffffffff81c556df>] seq_read+0xccf/0x12a0 fs/seq_file.c:276
[<ffffffff81dbc62c>] proc_reg_read+0x10c/0x1d0 fs/proc/inode.c:231
[<ffffffff81bc28ae>] do_loop_readv_writev fs/read_write.c:714 [inline]
[<ffffffff81bc28ae>] do_loop_readv_writev fs/read_write.c:701 [inline]
[<ffffffff81bc28ae>] do_iter_read+0x49e/0x660 fs/read_write.c:935
[<ffffffff81bc81ab>] vfs_readv+0xfb/0x170 fs/read_write.c:997
[<ffffffff81c88847>] kernel_readv fs/splice.c:361 [inline]
[<ffffffff81c88847>] default_file_splice_read+0x487/0x9c0 fs/splice.c:416
[<ffffffff81c86189>] do_splice_to+0x129/0x190 fs/splice.c:879
[<ffffffff81c86f66>] splice_direct_to_actor+0x256/0x890 fs/splice.c:951
[<ffffffff81c8777d>] do_splice_direct+0x1dd/0x2b0 fs/splice.c:1060
[<ffffffff81bc4747>] do_sendfile+0x597/0xce0 fs/read_write.c:1459
[<ffffffff81bca205>] SYSC_sendfile64 fs/read_write.c:1520 [inline]
[<ffffffff81bca205>] SyS_sendfile64+0x155/0x170 fs/read_write.c:1506
[<ffffffff81015fcf>] do_syscall_64+0x1ff/0x310 arch/x86/entry/common.c:305
[<ffffffff84a00076>] entry_SYSCALL_64_after_hwframe+0x42/0xb7
Fixes: f88d8ea67f
("ipv6: Plumb support for nexthop object in a fib6_info")
Reported-by: syzbot <syzkaller@googlegroups.com>
Signed-off-by: Wei Wang <weiwan@google.com>
Cc: David Ahern <dsahern@kernel.org>
Cc: Ido Schimmel <idosch@idosch.org>
Cc: Petr Machata <petrm@nvidia.com>
Cc: Eric Dumazet <edumazet@google.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
514 lines
10 KiB
C
514 lines
10 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Generic nexthop implementation
|
|
*
|
|
* Copyright (c) 2017-19 Cumulus Networks
|
|
* Copyright (c) 2017-19 David Ahern <dsa@cumulusnetworks.com>
|
|
*/
|
|
|
|
#ifndef __LINUX_NEXTHOP_H
|
|
#define __LINUX_NEXTHOP_H
|
|
|
|
#include <linux/netdevice.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/route.h>
|
|
#include <linux/types.h>
|
|
#include <net/ip_fib.h>
|
|
#include <net/ip6_fib.h>
|
|
#include <net/netlink.h>
|
|
|
|
#define NEXTHOP_VALID_USER_FLAGS RTNH_F_ONLINK
|
|
|
|
struct nexthop;
|
|
|
|
struct nh_config {
|
|
u32 nh_id;
|
|
|
|
u8 nh_family;
|
|
u8 nh_protocol;
|
|
u8 nh_blackhole;
|
|
u8 nh_fdb;
|
|
u32 nh_flags;
|
|
|
|
int nh_ifindex;
|
|
struct net_device *dev;
|
|
|
|
union {
|
|
__be32 ipv4;
|
|
struct in6_addr ipv6;
|
|
} gw;
|
|
|
|
struct nlattr *nh_grp;
|
|
u16 nh_grp_type;
|
|
|
|
struct nlattr *nh_encap;
|
|
u16 nh_encap_type;
|
|
|
|
u32 nlflags;
|
|
struct nl_info nlinfo;
|
|
};
|
|
|
|
struct nh_info {
|
|
struct hlist_node dev_hash; /* entry on netns devhash */
|
|
struct nexthop *nh_parent;
|
|
|
|
u8 family;
|
|
bool reject_nh;
|
|
bool fdb_nh;
|
|
|
|
union {
|
|
struct fib_nh_common fib_nhc;
|
|
struct fib_nh fib_nh;
|
|
struct fib6_nh fib6_nh;
|
|
};
|
|
};
|
|
|
|
struct nh_grp_entry {
|
|
struct nexthop *nh;
|
|
u8 weight;
|
|
|
|
union {
|
|
struct {
|
|
atomic_t upper_bound;
|
|
} mpath;
|
|
};
|
|
|
|
struct list_head nh_list;
|
|
struct nexthop *nh_parent; /* nexthop of group with this entry */
|
|
};
|
|
|
|
struct nh_group {
|
|
struct nh_group *spare; /* spare group for removals */
|
|
u16 num_nh;
|
|
bool mpath;
|
|
bool fdb_nh;
|
|
bool has_v4;
|
|
struct nh_grp_entry nh_entries[];
|
|
};
|
|
|
|
struct nexthop {
|
|
struct rb_node rb_node; /* entry on netns rbtree */
|
|
struct list_head fi_list; /* v4 entries using nh */
|
|
struct list_head f6i_list; /* v6 entries using nh */
|
|
struct list_head fdb_list; /* fdb entries using this nh */
|
|
struct list_head grp_list; /* nh group entries using this nh */
|
|
struct net *net;
|
|
|
|
u32 id;
|
|
|
|
u8 protocol; /* app managing this nh */
|
|
u8 nh_flags;
|
|
bool is_group;
|
|
|
|
refcount_t refcnt;
|
|
struct rcu_head rcu;
|
|
|
|
union {
|
|
struct nh_info __rcu *nh_info;
|
|
struct nh_group __rcu *nh_grp;
|
|
};
|
|
};
|
|
|
|
enum nexthop_event_type {
|
|
NEXTHOP_EVENT_DEL,
|
|
NEXTHOP_EVENT_REPLACE,
|
|
};
|
|
|
|
enum nh_notifier_info_type {
|
|
NH_NOTIFIER_INFO_TYPE_SINGLE,
|
|
NH_NOTIFIER_INFO_TYPE_GRP,
|
|
};
|
|
|
|
struct nh_notifier_single_info {
|
|
struct net_device *dev;
|
|
u8 gw_family;
|
|
union {
|
|
__be32 ipv4;
|
|
struct in6_addr ipv6;
|
|
};
|
|
u8 is_reject:1,
|
|
is_fdb:1,
|
|
has_encap:1;
|
|
};
|
|
|
|
struct nh_notifier_grp_entry_info {
|
|
u8 weight;
|
|
u32 id;
|
|
struct nh_notifier_single_info nh;
|
|
};
|
|
|
|
struct nh_notifier_grp_info {
|
|
u16 num_nh;
|
|
bool is_fdb;
|
|
struct nh_notifier_grp_entry_info nh_entries[];
|
|
};
|
|
|
|
struct nh_notifier_info {
|
|
struct net *net;
|
|
struct netlink_ext_ack *extack;
|
|
u32 id;
|
|
enum nh_notifier_info_type type;
|
|
union {
|
|
struct nh_notifier_single_info *nh;
|
|
struct nh_notifier_grp_info *nh_grp;
|
|
};
|
|
};
|
|
|
|
int register_nexthop_notifier(struct net *net, struct notifier_block *nb,
|
|
struct netlink_ext_ack *extack);
|
|
int unregister_nexthop_notifier(struct net *net, struct notifier_block *nb);
|
|
void nexthop_set_hw_flags(struct net *net, u32 id, bool offload, bool trap);
|
|
|
|
/* caller is holding rcu or rtnl; no reference taken to nexthop */
|
|
struct nexthop *nexthop_find_by_id(struct net *net, u32 id);
|
|
void nexthop_free_rcu(struct rcu_head *head);
|
|
|
|
static inline bool nexthop_get(struct nexthop *nh)
|
|
{
|
|
return refcount_inc_not_zero(&nh->refcnt);
|
|
}
|
|
|
|
static inline void nexthop_put(struct nexthop *nh)
|
|
{
|
|
if (refcount_dec_and_test(&nh->refcnt))
|
|
call_rcu(&nh->rcu, nexthop_free_rcu);
|
|
}
|
|
|
|
static inline bool nexthop_cmp(const struct nexthop *nh1,
|
|
const struct nexthop *nh2)
|
|
{
|
|
return nh1 == nh2;
|
|
}
|
|
|
|
static inline bool nexthop_is_fdb(const struct nexthop *nh)
|
|
{
|
|
if (nh->is_group) {
|
|
const struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
return nh_grp->fdb_nh;
|
|
} else {
|
|
const struct nh_info *nhi;
|
|
|
|
nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
return nhi->fdb_nh;
|
|
}
|
|
}
|
|
|
|
static inline bool nexthop_has_v4(const struct nexthop *nh)
|
|
{
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
return nh_grp->has_v4;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline bool nexthop_is_multipath(const struct nexthop *nh)
|
|
{
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
return nh_grp->mpath;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct nexthop *nexthop_select_path(struct nexthop *nh, int hash);
|
|
|
|
static inline unsigned int nexthop_num_path(const struct nexthop *nh)
|
|
{
|
|
unsigned int rc = 1;
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
if (nh_grp->mpath)
|
|
rc = nh_grp->num_nh;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static inline
|
|
struct nexthop *nexthop_mpath_select(const struct nh_group *nhg, int nhsel)
|
|
{
|
|
/* for_nexthops macros in fib_semantics.c grabs a pointer to
|
|
* the nexthop before checking nhsel
|
|
*/
|
|
if (nhsel >= nhg->num_nh)
|
|
return NULL;
|
|
|
|
return nhg->nh_entries[nhsel].nh;
|
|
}
|
|
|
|
static inline
|
|
int nexthop_mpath_fill_node(struct sk_buff *skb, struct nexthop *nh,
|
|
u8 rt_family)
|
|
{
|
|
struct nh_group *nhg = rtnl_dereference(nh->nh_grp);
|
|
int i;
|
|
|
|
for (i = 0; i < nhg->num_nh; i++) {
|
|
struct nexthop *nhe = nhg->nh_entries[i].nh;
|
|
struct nh_info *nhi = rcu_dereference_rtnl(nhe->nh_info);
|
|
struct fib_nh_common *nhc = &nhi->fib_nhc;
|
|
int weight = nhg->nh_entries[i].weight;
|
|
|
|
if (fib_add_nexthop(skb, nhc, weight, rt_family) < 0)
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* called with rcu lock */
|
|
static inline bool nexthop_is_blackhole(const struct nexthop *nh)
|
|
{
|
|
const struct nh_info *nhi;
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
if (nh_grp->num_nh > 1)
|
|
return false;
|
|
|
|
nh = nh_grp->nh_entries[0].nh;
|
|
}
|
|
|
|
nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
return nhi->reject_nh;
|
|
}
|
|
|
|
static inline void nexthop_path_fib_result(struct fib_result *res, int hash)
|
|
{
|
|
struct nh_info *nhi;
|
|
struct nexthop *nh;
|
|
|
|
nh = nexthop_select_path(res->fi->nh, hash);
|
|
nhi = rcu_dereference(nh->nh_info);
|
|
res->nhc = &nhi->fib_nhc;
|
|
}
|
|
|
|
/* called with rcu read lock or rtnl held */
|
|
static inline
|
|
struct fib_nh_common *nexthop_fib_nhc(struct nexthop *nh, int nhsel)
|
|
{
|
|
struct nh_info *nhi;
|
|
|
|
BUILD_BUG_ON(offsetof(struct fib_nh, nh_common) != 0);
|
|
BUILD_BUG_ON(offsetof(struct fib6_nh, nh_common) != 0);
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
if (nh_grp->mpath) {
|
|
nh = nexthop_mpath_select(nh_grp, nhsel);
|
|
if (!nh)
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
return &nhi->fib_nhc;
|
|
}
|
|
|
|
/* called from fib_table_lookup with rcu_lock */
|
|
static inline
|
|
struct fib_nh_common *nexthop_get_nhc_lookup(const struct nexthop *nh,
|
|
int fib_flags,
|
|
const struct flowi4 *flp,
|
|
int *nhsel)
|
|
{
|
|
struct nh_info *nhi;
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nhg = rcu_dereference(nh->nh_grp);
|
|
int i;
|
|
|
|
for (i = 0; i < nhg->num_nh; i++) {
|
|
struct nexthop *nhe = nhg->nh_entries[i].nh;
|
|
|
|
nhi = rcu_dereference(nhe->nh_info);
|
|
if (fib_lookup_good_nhc(&nhi->fib_nhc, fib_flags, flp)) {
|
|
*nhsel = i;
|
|
return &nhi->fib_nhc;
|
|
}
|
|
}
|
|
} else {
|
|
nhi = rcu_dereference(nh->nh_info);
|
|
if (fib_lookup_good_nhc(&nhi->fib_nhc, fib_flags, flp)) {
|
|
*nhsel = 0;
|
|
return &nhi->fib_nhc;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool nexthop_uses_dev(const struct nexthop *nh,
|
|
const struct net_device *dev)
|
|
{
|
|
struct nh_info *nhi;
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nhg = rcu_dereference(nh->nh_grp);
|
|
int i;
|
|
|
|
for (i = 0; i < nhg->num_nh; i++) {
|
|
struct nexthop *nhe = nhg->nh_entries[i].nh;
|
|
|
|
nhi = rcu_dereference(nhe->nh_info);
|
|
if (nhc_l3mdev_matches_dev(&nhi->fib_nhc, dev))
|
|
return true;
|
|
}
|
|
} else {
|
|
nhi = rcu_dereference(nh->nh_info);
|
|
if (nhc_l3mdev_matches_dev(&nhi->fib_nhc, dev))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline unsigned int fib_info_num_path(const struct fib_info *fi)
|
|
{
|
|
if (unlikely(fi->nh))
|
|
return nexthop_num_path(fi->nh);
|
|
|
|
return fi->fib_nhs;
|
|
}
|
|
|
|
int fib_check_nexthop(struct nexthop *nh, u8 scope,
|
|
struct netlink_ext_ack *extack);
|
|
|
|
static inline struct fib_nh_common *fib_info_nhc(struct fib_info *fi, int nhsel)
|
|
{
|
|
if (unlikely(fi->nh))
|
|
return nexthop_fib_nhc(fi->nh, nhsel);
|
|
|
|
return &fi->fib_nh[nhsel].nh_common;
|
|
}
|
|
|
|
/* only used when fib_nh is built into fib_info */
|
|
static inline struct fib_nh *fib_info_nh(struct fib_info *fi, int nhsel)
|
|
{
|
|
WARN_ON(fi->nh);
|
|
|
|
return &fi->fib_nh[nhsel];
|
|
}
|
|
|
|
/*
|
|
* IPv6 variants
|
|
*/
|
|
int fib6_check_nexthop(struct nexthop *nh, struct fib6_config *cfg,
|
|
struct netlink_ext_ack *extack);
|
|
|
|
/* Caller should either hold rcu_read_lock(), or RTNL. */
|
|
static inline struct fib6_nh *nexthop_fib6_nh(struct nexthop *nh)
|
|
{
|
|
struct nh_info *nhi;
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_rtnl(nh->nh_grp);
|
|
nh = nexthop_mpath_select(nh_grp, 0);
|
|
if (!nh)
|
|
return NULL;
|
|
}
|
|
|
|
nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
if (nhi->family == AF_INET6)
|
|
return &nhi->fib6_nh;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Variant of nexthop_fib6_nh().
|
|
* Caller should either hold rcu_read_lock_bh(), or RTNL.
|
|
*/
|
|
static inline struct fib6_nh *nexthop_fib6_nh_bh(struct nexthop *nh)
|
|
{
|
|
struct nh_info *nhi;
|
|
|
|
if (nh->is_group) {
|
|
struct nh_group *nh_grp;
|
|
|
|
nh_grp = rcu_dereference_bh_rtnl(nh->nh_grp);
|
|
nh = nexthop_mpath_select(nh_grp, 0);
|
|
if (!nh)
|
|
return NULL;
|
|
}
|
|
|
|
nhi = rcu_dereference_bh_rtnl(nh->nh_info);
|
|
if (nhi->family == AF_INET6)
|
|
return &nhi->fib6_nh;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline struct net_device *fib6_info_nh_dev(struct fib6_info *f6i)
|
|
{
|
|
struct fib6_nh *fib6_nh;
|
|
|
|
fib6_nh = f6i->nh ? nexthop_fib6_nh(f6i->nh) : f6i->fib6_nh;
|
|
return fib6_nh->fib_nh_dev;
|
|
}
|
|
|
|
static inline void nexthop_path_fib6_result(struct fib6_result *res, int hash)
|
|
{
|
|
struct nexthop *nh = res->f6i->nh;
|
|
struct nh_info *nhi;
|
|
|
|
nh = nexthop_select_path(nh, hash);
|
|
|
|
nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
if (nhi->reject_nh) {
|
|
res->fib6_type = RTN_BLACKHOLE;
|
|
res->fib6_flags |= RTF_REJECT;
|
|
res->nh = nexthop_fib6_nh(nh);
|
|
} else {
|
|
res->nh = &nhi->fib6_nh;
|
|
}
|
|
}
|
|
|
|
int nexthop_for_each_fib6_nh(struct nexthop *nh,
|
|
int (*cb)(struct fib6_nh *nh, void *arg),
|
|
void *arg);
|
|
|
|
static inline int nexthop_get_family(struct nexthop *nh)
|
|
{
|
|
struct nh_info *nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
|
|
return nhi->family;
|
|
}
|
|
|
|
static inline
|
|
struct fib_nh_common *nexthop_fdb_nhc(struct nexthop *nh)
|
|
{
|
|
struct nh_info *nhi = rcu_dereference_rtnl(nh->nh_info);
|
|
|
|
return &nhi->fib_nhc;
|
|
}
|
|
|
|
static inline struct fib_nh_common *nexthop_path_fdb_result(struct nexthop *nh,
|
|
int hash)
|
|
{
|
|
struct nh_info *nhi;
|
|
struct nexthop *nhp;
|
|
|
|
nhp = nexthop_select_path(nh, hash);
|
|
if (unlikely(!nhp))
|
|
return NULL;
|
|
nhi = rcu_dereference(nhp->nh_info);
|
|
return &nhi->fib_nhc;
|
|
}
|
|
#endif
|