Thread (7 messages) 7 messages, 2 authors, 2022-06-23

Re: [PATCH net 2/2] net: rose: fix null-ptr-deref caused by rose_kill_by_neigh

From: <hidden>
Date: 2022-06-23 12:16:58
Also in: linux-hams, lkml

Hello,

On Thu, 23 Jun 2022 11:30:04 +0200 Paolo Abeni wrote:
quoted
When the link layer connection is broken, the rose->neighbour is
set to null. But rose->neighbour could be used by rose_connection()
and rose_release() later, because there is no synchronization among
them. As a result, the null-ptr-deref bugs will happen.

One of the null-ptr-deref bugs is shown below:

    (thread 1)                  |        (thread 2)
                                |  rose_connect
rose_kill_by_neigh              |    lock_sock(sk)
  spin_lock_bh(&rose_list_lock) |    if (!rose->neighbour)
  rose->neighbour = NULL;//(1)  |
                                |    rose->neighbour->use++;//(2)

The rose->neighbour is set to null in position (1) and dereferenced
in position (2).

The KASAN report triggered by POC is shown below:

KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f]
...
RIP: 0010:rose_connect+0x6c2/0xf30
RSP: 0018:ffff88800ab47d60 EFLAGS: 00000206
RAX: 0000000000000005 RBX: 000000000000002a RCX: 0000000000000000
RDX: ffff88800ab38000 RSI: ffff88800ab47e48 RDI: ffff88800ab38309
RBP: dffffc0000000000 R08: 0000000000000000 R09: ffffed1001567062
R10: dfffe91001567063 R11: 1ffff11001567061 R12: 1ffff11000d17cd0
R13: ffff8880068be680 R14: 0000000000000002 R15: 1ffff11000d17cd0
...
Call Trace:
  <TASK>
  ? __local_bh_enable_ip+0x54/0x80
  ? selinux_netlbl_socket_connect+0x26/0x30
  ? rose_bind+0x5b0/0x5b0
  __sys_connect+0x216/0x280
  __x64_sys_connect+0x71/0x80
  do_syscall_64+0x43/0x90
  entry_SYSCALL_64_after_hwframe+0x46/0xb0

This patch adds lock_sock() in rose_kill_by_neigh() in order to
synchronize with rose_connect() and rose_release().

Meanwhile, this patch adds sock_hold() protected by rose_list_lock
that could synchronize with rose_remove_socket() in order to mitigate
UAF bug caused by lock_sock() we add.

What's more, there is no need using rose_neigh_list_lock to protect
rose_kill_by_neigh(). Because we have already used rose_neigh_list_lock
to protect the state change of rose_neigh in rose_link_failed(), which
is well synchronized.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Duoming Zhou <redacted>
---
 net/rose/af_rose.c    | 5 +++++
 net/rose/rose_route.c | 2 ++
 2 files changed, 7 insertions(+)
diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c
index bf2d986a6bc..dece637e274 100644
--- a/net/rose/af_rose.c
+++ b/net/rose/af_rose.c
@@ -169,9 +169,14 @@ void rose_kill_by_neigh(struct rose_neigh *neigh)
 		struct rose_sock *rose = rose_sk(s);
 
 		if (rose->neighbour == neigh) {
+			sock_hold(s);
 			rose_disconnect(s, ENETUNREACH, ROSE_OUT_OF_ORDER, 0);
 			rose->neighbour->use--;
+			spin_unlock_bh(&rose_list_lock);
You can't release the lock protecting the list traversal, then re-
acquire it and keep traversing using the same iterator. The list could
be modified in-between.
I think release the lock and then reacquire it is ok. Because we have held the
refcount of sock and called rose_disconnect() to change the state of sock with
the protection of rose_list_lock which could synchronize with rose_destroy_socket().

If the sock is removed from the list by rose_destroy_socket(), there is
no rose->neighbour equals to neigh and the rose_kill_by_neigh() will return.

If there is a rose->neighbour equals to neigh, we held the refcount of sock
and called the rose_disconnect() to change the state of it with the protection
of rose_list_lock. Even if the sock could be removed from the rose_list by
rose_destroy_socket() during the time of unlocking, but the sock will not be 
deallocated because we have held the refcount of sock. When we reacquire the 
rose_list_lock, we only do sock_put() in order to deallocate the sock.
@@ -169,9 +169,15 @@ void rose_kill_by_neigh(struct rose_neigh *neigh)
                struct rose_sock *rose = rose_sk(s);
 
                if (rose->neighbour == neigh) {
+                       sock_hold(s);
                        rose_disconnect(s, ENETUNREACH, ROSE_OUT_OF_ORDER, 0);
                        rose->neighbour->use--;
+                       spin_unlock_bh(&rose_list_lock);
+                       lock_sock(s);
                        rose->neighbour = NULL;
+                       release_sock(s);
+                       spin_lock_bh(&rose_list_lock);
+                       sock_put(s);
                }
        }
        spin_unlock_bh(&rose_list_lock);
Instead you could build a local list containing the relevant sockets
(under the rose_list_lock protection), additionally acquiring a
reference to each of them

Then traverse such list outside the rose_list_lock, acquire the socket
lock on each of them, do the neigh clearing and release the reference.
If we build a local list contain the relevant sockets and only acquire a reference
to each of them with the protection of rose_list_lock, the socket could be removed
by rose_destroy_socket() after we release the rose_list_lock. Then if we traverse 
such list outside the rose_list_lock we could not find the socket, as a result,
the neigh clearing and the refcount releasing operations will not be executed.

Best regards,
Duoming Zhou
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help