diff mbox series

[net] ip6mr: fix UAF issue in ip6mr_sk_done() when addrconf_init_net() failed

Message ID 20221017080331.16878-1-shaozhengchao@huawei.com (mailing list archive)
State Accepted
Commit 1ca695207ed2271ecbf8ee6c641970f621c157cc
Delegated to: Netdev Maintainers
Headers show
Series [net] ip6mr: fix UAF issue in ip6mr_sk_done() when addrconf_init_net() failed | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net
netdev/fixes_present success Fixes tag present in non-next series
netdev/subject_prefix success Link
netdev/cover_letter success Single patches do not need cover letters
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
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: 0 this patch: 0
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 11 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

shaozhengchao Oct. 17, 2022, 8:03 a.m. UTC
If the initialization fails in calling addrconf_init_net(), devconf_all is
the pointer that has been released. Then ip6mr_sk_done() is called to
release the net, accessing devconf->mc_forwarding directly causes invalid
pointer access.

The process is as follows:
setup_net()
	ops_init()
		addrconf_init_net()
		all = kmemdup(...)           ---> alloc "all"
		...
		net->ipv6.devconf_all = all;
		__addrconf_sysctl_register() ---> failed
		...
		kfree(all);                  ---> ipv6.devconf_all invalid
		...
	ops_exit_list()
		...
		ip6mr_sk_done()
			devconf = net->ipv6.devconf_all;
			//devconf is invalid pointer
			if (!devconf || !atomic_read(&devconf->mc_forwarding))

The following is the Call Trace information:
BUG: KASAN: use-after-free in ip6mr_sk_done+0x112/0x3a0
Read of size 4 at addr ffff888075508e88 by task ip/14554
Call Trace:
<TASK>
dump_stack_lvl+0x8e/0xd1
print_report+0x155/0x454
kasan_report+0xba/0x1f0
kasan_check_range+0x35/0x1b0
ip6mr_sk_done+0x112/0x3a0
rawv6_close+0x48/0x70
inet_release+0x109/0x230
inet6_release+0x4c/0x70
sock_release+0x87/0x1b0
igmp6_net_exit+0x6b/0x170
ops_exit_list+0xb0/0x170
setup_net+0x7ac/0xbd0
copy_net_ns+0x2e6/0x6b0
create_new_namespaces+0x382/0xa50
unshare_nsproxy_namespaces+0xa6/0x1c0
ksys_unshare+0x3a4/0x7e0
__x64_sys_unshare+0x2d/0x40
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x46/0xb0
RIP: 0033:0x7f7963322547

</TASK>
Allocated by task 14554:
kasan_save_stack+0x1e/0x40
kasan_set_track+0x21/0x30
__kasan_kmalloc+0xa1/0xb0
__kmalloc_node_track_caller+0x4a/0xb0
kmemdup+0x28/0x60
addrconf_init_net+0x1be/0x840
ops_init+0xa5/0x410
setup_net+0x5aa/0xbd0
copy_net_ns+0x2e6/0x6b0
create_new_namespaces+0x382/0xa50
unshare_nsproxy_namespaces+0xa6/0x1c0
ksys_unshare+0x3a4/0x7e0
__x64_sys_unshare+0x2d/0x40
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x46/0xb0

Freed by task 14554:
kasan_save_stack+0x1e/0x40
kasan_set_track+0x21/0x30
kasan_save_free_info+0x2a/0x40
____kasan_slab_free+0x155/0x1b0
slab_free_freelist_hook+0x11b/0x220
__kmem_cache_free+0xa4/0x360
addrconf_init_net+0x623/0x840
ops_init+0xa5/0x410
setup_net+0x5aa/0xbd0
copy_net_ns+0x2e6/0x6b0
create_new_namespaces+0x382/0xa50
unshare_nsproxy_namespaces+0xa6/0x1c0
ksys_unshare+0x3a4/0x7e0
__x64_sys_unshare+0x2d/0x40
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x46/0xb0

Fixes: 7d9b1b578d67 ("ip6mr: fix use-after-free in ip6mr_sk_done()")
Signed-off-by: Zhengchao Shao <shaozhengchao@huawei.com>
---
 net/ipv6/addrconf.c | 2 ++
 1 file changed, 2 insertions(+)

Comments

Eric Dumazet Oct. 17, 2022, 3:58 p.m. UTC | #1
On Mon, Oct 17, 2022 at 12:55 AM Zhengchao Shao
<shaozhengchao@huawei.com> wrote:
>
> If the initialization fails in calling addrconf_init_net(), devconf_all is
> the pointer that has been released. Then ip6mr_sk_done() is called to
> release the net, accessing devconf->mc_forwarding directly causes invalid
> pointer access.
>
> The process is as follows:
> setup_net()
>         ops_init()
>                 addrconf_init_net()
>                 all = kmemdup(...)           ---> alloc "all"
>                 ...
>                 net->ipv6.devconf_all = all;
>                 __addrconf_sysctl_register() ---> failed
>                 ...
>                 kfree(all);                  ---> ipv6.devconf_all invalid
>                 ...
>         ops_exit_list()
>                 ...
>                 ip6mr_sk_done()
>                         devconf = net->ipv6.devconf_all;
>                         //devconf is invalid pointer
>                         if (!devconf || !atomic_read(&devconf->mc_forwarding))
>
>
> Fixes: 7d9b1b578d67 ("ip6mr: fix use-after-free in ip6mr_sk_done()")

Hmmm. I wonder if you are not masking a more serious bug.

When I wrote this patch ( 7d9b1b578d67) I was following the standard
rule of ops->exit() being called
only if the corresponding ops->init() function had not failed.

net/core/net_namespace.c:setup_net() even has some comments about this rule:

out_undo:
        /* Walk through the list backwards calling the exit functions
         * for the pernet modules whose init functions did not fail.
         */
        list_add(&net->exit_list, &net_exit_list);
        saved_ops = ops;
        list_for_each_entry_continue_reverse(ops, &pernet_list, list)
                ops_pre_exit_list(ops, &net_exit_list);

        synchronize_rcu();

        ops = saved_ops;
        list_for_each_entry_continue_reverse(ops, &pernet_list, list)
                ops_exit_list(ops, &net_exit_list);

        ops = saved_ops;
        list_for_each_entry_continue_reverse(ops, &pernet_list, list)
                ops_free_list(ops, &net_exit_list);

        rcu_barrier();
        goto out;



> Signed-off-by: Zhengchao Shao <shaozhengchao@huawei.com>
> ---
>  net/ipv6/addrconf.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
> index 417834b7169d..9c3f5202a97b 100644
> --- a/net/ipv6/addrconf.c
> +++ b/net/ipv6/addrconf.c
> @@ -7214,9 +7214,11 @@ static int __net_init addrconf_init_net(struct net *net)
>         __addrconf_sysctl_unregister(net, all, NETCONFA_IFINDEX_ALL);
>  err_reg_all:
>         kfree(dflt);
> +       net->ipv6.devconf_dflt = NULL;
>  #endif
>  err_alloc_dflt:
>         kfree(all);
> +       net->ipv6.devconf_all = NULL;
>  err_alloc_all:
>         kfree(net->ipv6.inet6_addr_lst);
>  err_alloc_addr:
> --
> 2.17.1
>
shaozhengchao Oct. 18, 2022, 1:48 a.m. UTC | #2
On 2022/10/17 23:58, Eric Dumazet wrote:
> On Mon, Oct 17, 2022 at 12:55 AM Zhengchao Shao
> <shaozhengchao@huawei.com> wrote:
>>
>> If the initialization fails in calling addrconf_init_net(), devconf_all is
>> the pointer that has been released. Then ip6mr_sk_done() is called to
>> release the net, accessing devconf->mc_forwarding directly causes invalid
>> pointer access.
>>
>> The process is as follows:
>> setup_net()
>>          ops_init()
>>                  addrconf_init_net()
>>                  all = kmemdup(...)           ---> alloc "all"
>>                  ...
>>                  net->ipv6.devconf_all = all;
>>                  __addrconf_sysctl_register() ---> failed
>>                  ...
>>                  kfree(all);                  ---> ipv6.devconf_all invalid
>>                  ...
>>          ops_exit_list()
>>                  ...
>>                  ip6mr_sk_done()
>>                          devconf = net->ipv6.devconf_all;
>>                          //devconf is invalid pointer
>>                          if (!devconf || !atomic_read(&devconf->mc_forwarding))
>>
>>
>> Fixes: 7d9b1b578d67 ("ip6mr: fix use-after-free in ip6mr_sk_done()")
> 
> Hmmm. I wonder if you are not masking a more serious bug.
> 
> When I wrote this patch ( 7d9b1b578d67) I was following the standard
> rule of ops->exit() being called
> only if the corresponding ops->init() function had not failed.
> 
> net/core/net_namespace.c:setup_net() even has some comments about this rule:
> 
> out_undo:
>          /* Walk through the list backwards calling the exit functions
>           * for the pernet modules whose init functions did not fail.
>           */
>          list_add(&net->exit_list, &net_exit_list);
>          saved_ops = ops;
>          list_for_each_entry_continue_reverse(ops, &pernet_list, list)
>                  ops_pre_exit_list(ops, &net_exit_list);
> 
>          synchronize_rcu();
> 
>          ops = saved_ops;
>          list_for_each_entry_continue_reverse(ops, &pernet_list, list)
>                  ops_exit_list(ops, &net_exit_list);
> 
>          ops = saved_ops;
>          list_for_each_entry_continue_reverse(ops, &pernet_list, list)
>                  ops_free_list(ops, &net_exit_list);
> 
>          rcu_barrier();
>          goto out;
> 
> 
Hi Eric:
	Thank you for your reply. I wonder if fixing commit
e0da5a480caf ("[NETNS]: Create ipv6 devconf-s for namespaces")
would be more appropriate?

Zhengchao Shao

> 
>> Signed-off-by: Zhengchao Shao <shaozhengchao@huawei.com>
>> ---
>>   net/ipv6/addrconf.c | 2 ++
>>   1 file changed, 2 insertions(+)
>>
>> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
>> index 417834b7169d..9c3f5202a97b 100644
>> --- a/net/ipv6/addrconf.c
>> +++ b/net/ipv6/addrconf.c
>> @@ -7214,9 +7214,11 @@ static int __net_init addrconf_init_net(struct net *net)
>>          __addrconf_sysctl_unregister(net, all, NETCONFA_IFINDEX_ALL);
>>   err_reg_all:
>>          kfree(dflt);
>> +       net->ipv6.devconf_dflt = NULL;
>>   #endif
>>   err_alloc_dflt:
>>          kfree(all);
>> +       net->ipv6.devconf_all = NULL;
>>   err_alloc_all:
>>          kfree(net->ipv6.inet6_addr_lst);
>>   err_alloc_addr:
>> --
>> 2.17.1
>>
Eric Dumazet Oct. 18, 2022, 2:33 a.m. UTC | #3
On Mon, Oct 17, 2022 at 6:48 PM shaozhengchao <shaozhengchao@huawei.com> wrote:
>
>
>
> On 2022/10/17 23:58, Eric Dumazet wrote:
> > On Mon, Oct 17, 2022 at 12:55 AM Zhengchao Shao
> > <shaozhengchao@huawei.com> wrote:
> >>
> >> If the initialization fails in calling addrconf_init_net(), devconf_all is
> >> the pointer that has been released. Then ip6mr_sk_done() is called to
> >> release the net, accessing devconf->mc_forwarding directly causes invalid
> >> pointer access.
> >>
> >> The process is as follows:
> >> setup_net()
> >>          ops_init()
> >>                  addrconf_init_net()
> >>                  all = kmemdup(...)           ---> alloc "all"
> >>                  ...
> >>                  net->ipv6.devconf_all = all;
> >>                  __addrconf_sysctl_register() ---> failed
> >>                  ...
> >>                  kfree(all);                  ---> ipv6.devconf_all invalid
> >>                  ...
> >>          ops_exit_list()
> >>                  ...
> >>                  ip6mr_sk_done()
> >>                          devconf = net->ipv6.devconf_all;
> >>                          //devconf is invalid pointer
> >>                          if (!devconf || !atomic_read(&devconf->mc_forwarding))
> >>
> >>
> >> Fixes: 7d9b1b578d67 ("ip6mr: fix use-after-free in ip6mr_sk_done()")
> >
> > Hmmm. I wonder if you are not masking a more serious bug.
> >
> > When I wrote this patch ( 7d9b1b578d67) I was following the standard
> > rule of ops->exit() being called
> > only if the corresponding ops->init() function had not failed.
> >
> > net/core/net_namespace.c:setup_net() even has some comments about this rule:
> >
> > out_undo:
> >          /* Walk through the list backwards calling the exit functions
> >           * for the pernet modules whose init functions did not fail.
> >           */
> >          list_add(&net->exit_list, &net_exit_list);
> >          saved_ops = ops;
> >          list_for_each_entry_continue_reverse(ops, &pernet_list, list)
> >                  ops_pre_exit_list(ops, &net_exit_list);
> >
> >          synchronize_rcu();
> >
> >          ops = saved_ops;
> >          list_for_each_entry_continue_reverse(ops, &pernet_list, list)
> >                  ops_exit_list(ops, &net_exit_list);
> >
> >          ops = saved_ops;
> >          list_for_each_entry_continue_reverse(ops, &pernet_list, list)
> >                  ops_free_list(ops, &net_exit_list);
> >
> >          rcu_barrier();
> >          goto out;
> >
> >
> Hi Eric:
>         Thank you for your reply. I wonder if fixing commit
> e0da5a480caf ("[NETNS]: Create ipv6 devconf-s for namespaces")
> would be more appropriate?

Do you have a repro ?

You could first use scripts/decode_stack.pl to get symbols from your
traces, to confirm the issue.

Freed by task 14554:
kasan_save_stack+0x1e/0x40
kasan_set_track+0x21/0x30
kasan_save_free_info+0x2a/0x40
____kasan_slab_free+0x155/0x1b0
slab_free_freelist_hook+0x11b/0x220
__kmem_cache_free+0xa4/0x360
addrconf_init_net+0x623/0x840
ops_init+0xa5/0x410
setup_net+0x5aa/0xbd0
copy_net_ns+0x2e6/0x6b0
create_new_namespaces+0x382/0xa50
unshare_nsproxy_namespaces+0xa6/0x1c0
ksys_unshare+0x3a4/0x7e0
__x64_sys_unshare+0x2d/0x40
do_syscall_64+0x35/0x80
entry_SYSCALL_64_after_hwframe+0x46/0xb0

>
> Zhengchao Shao
>
> >
> >> Signed-off-by: Zhengchao Shao <shaozhengchao@huawei.com>
> >> ---
> >>   net/ipv6/addrconf.c | 2 ++
> >>   1 file changed, 2 insertions(+)
> >>
> >> diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
> >> index 417834b7169d..9c3f5202a97b 100644
> >> --- a/net/ipv6/addrconf.c
> >> +++ b/net/ipv6/addrconf.c
> >> @@ -7214,9 +7214,11 @@ static int __net_init addrconf_init_net(struct net *net)
> >>          __addrconf_sysctl_unregister(net, all, NETCONFA_IFINDEX_ALL);
> >>   err_reg_all:
> >>          kfree(dflt);
> >> +       net->ipv6.devconf_dflt = NULL;
> >>   #endif
> >>   err_alloc_dflt:
> >>          kfree(all);
> >> +       net->ipv6.devconf_all = NULL;
> >>   err_alloc_all:
> >>          kfree(net->ipv6.inet6_addr_lst);
> >>   err_alloc_addr:
> >> --
> >> 2.17.1
> >>
Eric Dumazet Oct. 18, 2022, 3:35 a.m. UTC | #4
On Mon, Oct 17, 2022 at 7:33 PM Eric Dumazet <edumazet@google.com> wrote:
>
> On Mon, Oct 17, 2022 at 6:48 PM shaozhengchao <shaozhengchao@huawei.com> wrote:

> > Hi Eric:
> >         Thank you for your reply. I wonder if fixing commit
> > e0da5a480caf ("[NETNS]: Create ipv6 devconf-s for namespaces")
> > would be more appropriate?
>
> Do you have a repro ?
>
> You could first use scripts/decode_stack.pl to get symbols from your
> traces, to confirm the issue.
>

Hmm... I read again the stack traces, and while addrconf_exit_net() is
not called,
igmp6_net_exit() _is_ called, so I guess your patch is fine, thanks !

Reviewed-by: Eric Dumazet <edumazet@google.com>
patchwork-bot+netdevbpf@kernel.org Oct. 18, 2022, 9:20 a.m. UTC | #5
Hello:

This patch was applied to netdev/net.git (master)
by Paolo Abeni <pabeni@redhat.com>:

On Mon, 17 Oct 2022 16:03:31 +0800 you wrote:
> If the initialization fails in calling addrconf_init_net(), devconf_all is
> the pointer that has been released. Then ip6mr_sk_done() is called to
> release the net, accessing devconf->mc_forwarding directly causes invalid
> pointer access.
> 
> The process is as follows:
> setup_net()
> 	ops_init()
> 		addrconf_init_net()
> 		all = kmemdup(...)           ---> alloc "all"
> 		...
> 		net->ipv6.devconf_all = all;
> 		__addrconf_sysctl_register() ---> failed
> 		...
> 		kfree(all);                  ---> ipv6.devconf_all invalid
> 		...
> 	ops_exit_list()
> 		...
> 		ip6mr_sk_done()
> 			devconf = net->ipv6.devconf_all;
> 			//devconf is invalid pointer
> 			if (!devconf || !atomic_read(&devconf->mc_forwarding))
> 
> [...]

Here is the summary with links:
  - [net] ip6mr: fix UAF issue in ip6mr_sk_done() when addrconf_init_net() failed
    https://git.kernel.org/netdev/net/c/1ca695207ed2

You are awesome, thank you!
diff mbox series

Patch

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 417834b7169d..9c3f5202a97b 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -7214,9 +7214,11 @@  static int __net_init addrconf_init_net(struct net *net)
 	__addrconf_sysctl_unregister(net, all, NETCONFA_IFINDEX_ALL);
 err_reg_all:
 	kfree(dflt);
+	net->ipv6.devconf_dflt = NULL;
 #endif
 err_alloc_dflt:
 	kfree(all);
+	net->ipv6.devconf_all = NULL;
 err_alloc_all:
 	kfree(net->ipv6.inet6_addr_lst);
 err_alloc_addr: