diff mbox series

[net-next,v2,1/2] net/ipv6: insert a f6i to a GC list only if the f6i is in a fib6_table tree.

Message ID 20231208194523.312416-2-thinker.li@gmail.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series Fix dangling pointer at f6i->gc_link. | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1616 this patch: 1616
netdev/cc_maintainers success CCed 6 of 6 maintainers
netdev/build_clang success Errors and warnings before: 1147 this patch: 1147
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 1656 this patch: 1656
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 79 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Kui-Feng Lee Dec. 8, 2023, 7:45 p.m. UTC
From: Kui-Feng Lee <thinker.li@gmail.com>

Check f6i->fib6_node and hlist_unhashed(&f6i->gc_link) before inserting a
f6i (fib6_info) to tb6_gc_hlist.

The current implementation checks if f6i->fib6_table is not NULL to
determines if a f6i is on a tree, however it is not enough. When a f6i is
removed from a fib6_table, f6i->fib6_table is not reset. However, fib6_node
is always reset when a f6i is removed from a fib6_table and is set when a
f6i is added to a fib6_table. So, f6i->fib6_node is a reliable way to
determine if a f6i is on a tree.

The current implementation checks RTF_EXPIRES on f6i->fib6_flags to
determine if a f6i is on a GC list. It also consider if the f6i is on a
tree before making a conclusion. This is indirect and complicated. The new
solution is checking hlist_unhashed(&f6i->gc_link), a clear evidence.

Putting them together, these changes provide more reliable signals to
determines if a f6i should be added/or removed to a GC list.

Fixes: 3dec89b14d37 ("net/ipv6: Remove expired routes with a separated list of routes.")
Reported-by: syzbot+c15aa445274af8674f41@syzkaller.appspotmail.com
Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: dsahern@kernel.org
---
 include/net/ip6_fib.h | 46 ++++++++++++++++++++++++++++++++-----------
 net/ipv6/route.c      |  6 +++---
 2 files changed, 38 insertions(+), 14 deletions(-)

Comments

David Ahern Dec. 8, 2023, 10:43 p.m. UTC | #1
On 12/8/23 12:45 PM, thinker.li@gmail.com wrote:
> From: Kui-Feng Lee <thinker.li@gmail.com>
> 
> Check f6i->fib6_node and hlist_unhashed(&f6i->gc_link) before inserting a
> f6i (fib6_info) to tb6_gc_hlist.
> 
> The current implementation checks if f6i->fib6_table is not NULL to
> determines if a f6i is on a tree, however it is not enough. When a f6i is
> removed from a fib6_table, f6i->fib6_table is not reset. However, fib6_node
> is always reset when a f6i is removed from a fib6_table and is set when a
> f6i is added to a fib6_table. So, f6i->fib6_node is a reliable way to
> determine if a f6i is on a tree.

Which is an indication that the table is not the right check but neither
is the fib6_node. If expires is set on a route entry, add it to the
gc_list; if expires is reset on a route entry, remove it from the
gc_list. If the value of expires is changed while on the gc_list list,
just update the expires value.
Kui-Feng Lee Dec. 8, 2023, 11:19 p.m. UTC | #2
On 12/8/23 14:43, David Ahern wrote:
> On 12/8/23 12:45 PM, thinker.li@gmail.com wrote:
>> From: Kui-Feng Lee <thinker.li@gmail.com>
>>
>> Check f6i->fib6_node and hlist_unhashed(&f6i->gc_link) before inserting a
>> f6i (fib6_info) to tb6_gc_hlist.
>>
>> The current implementation checks if f6i->fib6_table is not NULL to
>> determines if a f6i is on a tree, however it is not enough. When a f6i is
>> removed from a fib6_table, f6i->fib6_table is not reset. However, fib6_node
>> is always reset when a f6i is removed from a fib6_table and is set when a
>> f6i is added to a fib6_table. So, f6i->fib6_node is a reliable way to
>> determine if a f6i is on a tree.
> 
> Which is an indication that the table is not the right check but neither
> is the fib6_node. If expires is set on a route entry, add it to the
> gc_list; if expires is reset on a route entry, remove it from the
> gc_list. If the value of expires is changed while on the gc_list list,
> just update the expires value.
> 

I don't quite follow you.
If an entry is not on a tree, why do we still add the entry to the gc 
list? (This is the reason to check f6i->fib6_node.)

The changes in this patch rely on two indications, 1. if a f6i is on a 
tree, 2. if a f6i is on a gc list (described in the 3rd paragraph of
the comment log.)
An entry is added to a gc list only if it has expires and is not on the 
list yet. An entry is removed from a gc list only if it is on a gc list.
And, just like what you said earlier, it just updates the expires value
while an entry is already on a gc list.
Kui-Feng Lee Dec. 8, 2023, 11:38 p.m. UTC | #3
On 12/8/23 15:19, Kui-Feng Lee wrote:
> 
> 
> On 12/8/23 14:43, David Ahern wrote:
>> On 12/8/23 12:45 PM, thinker.li@gmail.com wrote:
>>> From: Kui-Feng Lee <thinker.li@gmail.com>
>>>
>>> Check f6i->fib6_node and hlist_unhashed(&f6i->gc_link) before 
>>> inserting a
>>> f6i (fib6_info) to tb6_gc_hlist.
>>>
>>> The current implementation checks if f6i->fib6_table is not NULL to
>>> determines if a f6i is on a tree, however it is not enough. When a 
>>> f6i is
>>> removed from a fib6_table, f6i->fib6_table is not reset. However, 
>>> fib6_node
>>> is always reset when a f6i is removed from a fib6_table and is set 
>>> when a
>>> f6i is added to a fib6_table. So, f6i->fib6_node is a reliable way to
>>> determine if a f6i is on a tree.
>>
>> Which is an indication that the table is not the right check but neither
>> is the fib6_node. If expires is set on a route entry, add it to the
>> gc_list; if expires is reset on a route entry, remove it from the
>> gc_list. If the value of expires is changed while on the gc_list list,
>> just update the expires value.
>>
> 
> I don't quite follow you.
> If an entry is not on a tree, why do we still add the entry to the gc 
> list? (This is the reason to check f6i->fib6_node.)
> 
> The changes in this patch rely on two indications, 1. if a f6i is on a 
> tree, 2. if a f6i is on a gc list (described in the 3rd paragraph of
> the comment log.)
> An entry is added to a gc list only if it has expires and is not on the 
> list yet. An entry is removed from a gc list only if it is on a gc list.
> And, just like what you said earlier, it just updates the expires value
> while an entry is already on a gc list.

Add more context here.

Use rt6_route_rcv() as an example. It looks up or adds a route first.
Then it changes expires of the route at the very end of the function.
There is gap between looking up and the modification. The f6i found here
can be removed from the tree in-between. If the f6i here is added to the
gc list even it has been removed from the tree, fib6_info_release() at 
the end of rt6_route_rcv() might free the f6i while the f6i is still in
the gc list.

This is why it checks if a f6i is on the tree with the protection of
tb6_lock.
diff mbox series

Patch

diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 1ba9f4ddf2f6..1213722c394f 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -500,21 +500,47 @@  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)
+static inline void fib6_add_gc_list(struct fib6_info *f6i)
 {
 	struct fib6_table *tb6;
 
 	tb6 = f6i->fib6_table;
-	f6i->expires = expires;
-	if (tb6 && !fib6_has_expires(f6i))
+	if (tb6 &&
+	    rcu_dereference_protected(f6i->fib6_node,
+				      lockdep_is_held(&tb6->tb6_lock)) &&
+	    hlist_unhashed(&f6i->gc_link))
 		hlist_add_head(&f6i->gc_link, &tb6->tb6_gc_hlist);
+}
+
+static inline void fib6_del_gc_list(struct fib6_info *f6i)
+{
+	if (!hlist_unhashed(&f6i->gc_link))
+		hlist_del_init(&f6i->gc_link);
+}
+
+static inline void __fib6_set_expires(struct fib6_info *f6i,
+				      unsigned long expires)
+{
+	f6i->expires = expires;
 	f6i->fib6_flags |= RTF_EXPIRES;
 }
 
+static inline void __fib6_clean_expires(struct fib6_info *f6i)
+{
+	f6i->fib6_flags &= ~RTF_EXPIRES;
+	f6i->expires = 0;
+}
+
+/* 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)
+{
+	__fib6_set_expires(f6i, expires);
+	fib6_add_gc_list(f6i);
+}
+
 /* 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.
@@ -529,10 +555,8 @@  static inline void fib6_set_expires(struct fib6_info *f6i,
 
 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;
+	fib6_del_gc_list(f6i);
+	__fib6_clean_expires(f6i);
 }
 
 static inline void fib6_clean_expires(struct fib6_info *f6i)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index b132feae3393..dcaeb88d73aa 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -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;