net/ipv6: Revert remove expired routes with a separated list of routes

This reverts commit 3dec89b14d.

The commit has some race conditions given how expires is managed on a
fib6_info in relation to gc start, adding the entry to the gc list and
setting the timer value leading to UAF. Revert the commit and try again
in a later release.

Fixes: 3dec89b14d ("net/ipv6: Remove expired routes with a separated list of routes")
Cc: Kui-Feng Lee <thinker.li@gmail.com>
Signed-off-by: David Ahern <dsahern@kernel.org>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://lore.kernel.org/r/20231219030243.25687-1-dsahern@kernel.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
David Ahern 2023-12-18 20:02:43 -07:00 committed by Paolo Abeni
parent b414020fed
commit dade3f6a1e
3 changed files with 22 additions and 103 deletions

View file

@ -179,9 +179,6 @@ struct fib6_info {
refcount_t fib6_ref;
unsigned long expires;
struct hlist_node gc_link;
struct dst_metrics *fib6_metrics;
#define fib6_pmtu fib6_metrics->metrics[RTAX_MTU-1]
@ -250,6 +247,19 @@ static inline bool fib6_requires_src(const struct fib6_info *rt)
return rt->fib6_src.plen > 0;
}
static inline void fib6_clean_expires(struct fib6_info *f6i)
{
f6i->fib6_flags &= ~RTF_EXPIRES;
f6i->expires = 0;
}
static inline void fib6_set_expires(struct fib6_info *f6i,
unsigned long expires)
{
f6i->expires = expires;
f6i->fib6_flags |= RTF_EXPIRES;
}
static inline bool fib6_check_expired(const struct fib6_info *f6i)
{
if (f6i->fib6_flags & RTF_EXPIRES)
@ -257,11 +267,6 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i)
return false;
}
static inline bool fib6_has_expires(const struct fib6_info *f6i)
{
return f6i->fib6_flags & RTF_EXPIRES;
}
/* Function to safely get fn->fn_sernum for passed in rt
* and store result in passed in cookie.
* Return true if we can get cookie safely
@ -383,7 +388,6 @@ struct fib6_table {
struct inet_peer_base tb6_peers;
unsigned int flags;
unsigned int fib_seq;
struct hlist_head tb6_gc_hlist; /* GC candidates */
#define RT6_TABLE_HAS_DFLT_ROUTER BIT(0)
};
@ -500,48 +504,6 @@ void fib6_gc_cleanup(void);
int fib6_init(void);
/* fib6_info must be locked by the caller, and fib6_info->fib6_table can be
* NULL.
*/
static inline void fib6_set_expires_locked(struct fib6_info *f6i,
unsigned long expires)
{
struct fib6_table *tb6;
tb6 = f6i->fib6_table;
f6i->expires = expires;
if (tb6 && !fib6_has_expires(f6i))
hlist_add_head(&f6i->gc_link, &tb6->tb6_gc_hlist);
f6i->fib6_flags |= RTF_EXPIRES;
}
/* fib6_info must be locked by the caller, and fib6_info->fib6_table can be
* NULL. If fib6_table is NULL, the fib6_info will no be inserted into the
* list of GC candidates until it is inserted into a table.
*/
static inline void fib6_set_expires(struct fib6_info *f6i,
unsigned long expires)
{
spin_lock_bh(&f6i->fib6_table->tb6_lock);
fib6_set_expires_locked(f6i, expires);
spin_unlock_bh(&f6i->fib6_table->tb6_lock);
}
static inline void fib6_clean_expires_locked(struct fib6_info *f6i)
{
if (fib6_has_expires(f6i))
hlist_del_init(&f6i->gc_link);
f6i->fib6_flags &= ~RTF_EXPIRES;
f6i->expires = 0;
}
static inline void fib6_clean_expires(struct fib6_info *f6i)
{
spin_lock_bh(&f6i->fib6_table->tb6_lock);
fib6_clean_expires_locked(f6i);
spin_unlock_bh(&f6i->fib6_table->tb6_lock);
}
struct ipv6_route_iter {
struct seq_net_private p;
struct fib6_walker w;

View file

@ -160,8 +160,6 @@ struct fib6_info *fib6_info_alloc(gfp_t gfp_flags, bool with_fib6_nh)
INIT_LIST_HEAD(&f6i->fib6_siblings);
refcount_set(&f6i->fib6_ref, 1);
INIT_HLIST_NODE(&f6i->gc_link);
return f6i;
}
@ -248,7 +246,6 @@ static struct fib6_table *fib6_alloc_table(struct net *net, u32 id)
net->ipv6.fib6_null_entry);
table->tb6_root.fn_flags = RTN_ROOT | RTN_TL_ROOT | RTN_RTINFO;
inet_peer_base_init(&table->tb6_peers);
INIT_HLIST_HEAD(&table->tb6_gc_hlist);
}
return table;
@ -1060,8 +1057,6 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
lockdep_is_held(&table->tb6_lock));
}
}
fib6_clean_expires_locked(rt);
}
/*
@ -1123,10 +1118,9 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
if (!(iter->fib6_flags & RTF_EXPIRES))
return -EEXIST;
if (!(rt->fib6_flags & RTF_EXPIRES))
fib6_clean_expires_locked(iter);
fib6_clean_expires(iter);
else
fib6_set_expires_locked(iter,
rt->expires);
fib6_set_expires(iter, rt->expires);
if (rt->fib6_pmtu)
fib6_metric_set(iter, RTAX_MTU,
@ -1485,10 +1479,6 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
if (rt->nh)
list_add(&rt->nh_list, &rt->nh->f6i_list);
__fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net));
if (fib6_has_expires(rt))
hlist_add_head(&rt->gc_link, &table->tb6_gc_hlist);
fib6_start_gc(info->nl_net, rt);
}
@ -2291,8 +2281,9 @@ static void fib6_flush_trees(struct net *net)
* Garbage collection
*/
static int fib6_age(struct fib6_info *rt, struct fib6_gc_args *gc_args)
static int fib6_age(struct fib6_info *rt, void *arg)
{
struct fib6_gc_args *gc_args = arg;
unsigned long now = jiffies;
/*
@ -2300,7 +2291,7 @@ static int fib6_age(struct fib6_info *rt, struct fib6_gc_args *gc_args)
* Routes are expired even if they are in use.
*/
if (fib6_has_expires(rt) && rt->expires) {
if (rt->fib6_flags & RTF_EXPIRES && rt->expires) {
if (time_after(now, rt->expires)) {
RT6_TRACE("expiring %p\n", rt);
return -1;
@ -2317,40 +2308,6 @@ static int fib6_age(struct fib6_info *rt, struct fib6_gc_args *gc_args)
return 0;
}
static void fib6_gc_table(struct net *net,
struct fib6_table *tb6,
struct fib6_gc_args *gc_args)
{
struct fib6_info *rt;
struct hlist_node *n;
struct nl_info info = {
.nl_net = net,
.skip_notify = false,
};
hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link)
if (fib6_age(rt, gc_args) == -1)
fib6_del(rt, &info);
}
static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args)
{
struct fib6_table *table;
struct hlist_head *head;
unsigned int h;
rcu_read_lock();
for (h = 0; h < FIB6_TABLE_HASHSZ; h++) {
head = &net->ipv6.fib_table_hash[h];
hlist_for_each_entry_rcu(table, head, tb6_hlist) {
spin_lock_bh(&table->tb6_lock);
fib6_gc_table(net, table, gc_args);
spin_unlock_bh(&table->tb6_lock);
}
}
rcu_read_unlock();
}
void fib6_run_gc(unsigned long expires, struct net *net, bool force)
{
struct fib6_gc_args gc_args;
@ -2366,7 +2323,7 @@ void fib6_run_gc(unsigned long expires, struct net *net, bool force)
net->ipv6.sysctl.ip6_rt_gc_interval;
gc_args.more = 0;
fib6_gc_all(net, &gc_args);
fib6_clean_all(net, fib6_age, &gc_args);
now = jiffies;
net->ipv6.ip6_rt_last_gc = now;

View file

@ -3763,10 +3763,10 @@ static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
rt->dst_nocount = true;
if (cfg->fc_flags & RTF_EXPIRES)
fib6_set_expires_locked(rt, jiffies +
clock_t_to_jiffies(cfg->fc_expires));
fib6_set_expires(rt, jiffies +
clock_t_to_jiffies(cfg->fc_expires));
else
fib6_clean_expires_locked(rt);
fib6_clean_expires(rt);
if (cfg->fc_protocol == RTPROT_UNSPEC)
cfg->fc_protocol = RTPROT_BOOT;