lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20211016165337.18a1299e@hermes.local>
Date:   Sat, 16 Oct 2021 16:53:37 -0700
From:   Stephen Hemminger <stephen@...workplumber.org>
To:     Manivannan Sadhasivam <mani@...nel.org>
Cc:     netdev@...r.kernel.org
Subject: Fw: [Bug 214739] New: Information leakage from kernel to user space
 in /net/qrtr/qrtr.c



Begin forwarded message:

Date: Sat, 16 Oct 2021 20:16:08 +0000
From: bugzilla-daemon@...zilla.kernel.org
To: stephen@...workplumber.org
Subject: [Bug 214739] New: Information leakage from kernel to user space in /net/qrtr/qrtr.c


https://bugzilla.kernel.org/show_bug.cgi?id=214739

            Bug ID: 214739
           Summary: Information leakage from kernel to user space in
                    /net/qrtr/qrtr.c
           Product: Networking
           Version: 2.5
    Kernel Version: 5.15-rc5
          Hardware: All
                OS: Linux
              Tree: Mainline
            Status: NEW
          Severity: normal
          Priority: P1
         Component: Other
          Assignee: stephen@...workplumber.org
          Reporter: bao00065@....edu
        Regression: No

Hi Maintainers,
I recently reviewed the uninitialized value use bug in Linux kernel:

https://syzkaller.appspot.com/bug?id=739ce0bc6e4097668cbf94c862f3b643b364d589. 
and patch:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b42b3a2744b3e8f427de79896720c72823af91ad

The vulnerable function is:
int __sys_getsockname(int fd, struct sockaddr __user *usockaddr,
                      int __user *usockaddr_len)
{
        struct socket *sock;
        struct sockaddr_storage address; // allocation
        int err, fput_needed;

        sock = sockfd_lookup_light(fd, &err, &fput_needed);
        if (!sock)
                goto out;

        err = security_socket_getsockname(sock);
        if (err)
                goto out_put;

        err = sock->ops->getname(sock, (struct sockaddr *)&address, 0); //
initialization
        if (err < 0)
                goto out_put;
        /* "err" is actually length in this case */
        err = move_addr_to_user(&address, err, usockaddr, usockaddr_len); //use

out_put:
        fput_light(sock->file, fput_needed);
out:
        return err;
}

static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
                             void __user *uaddr, int __user *ulen)
{
        int err;
        int len;

        BUG_ON(klen > sizeof(struct sockaddr_storage));
        err = get_user(len, ulen);
        if (err)
                return err;
        if (len > klen)
                len = klen;
        if (len < 0)
                return -EINVAL;
        if (len) {
                if (audit_sockaddr(klen, kaddr))
                        return -ENOMEM;
                if (copy_to_user(uaddr, kaddr, len)) // use 
                        return -EFAULT;
        }
        /*
         *      "fromlen shall refer to the value before truncation.."
         *                      1003.1g
         */
        return __put_user(klen, ulen);
}


the variable address is allocated in __sys_getsockname, and then is initialized
in sock->ops->getname(sock, (struct sockaddr *)& address, 0); After that,
address is passed to move_addr_to_user() and finally it passed to
copy_to_user(), leading to uninitialized value use. 

Main reason for this bug: initialization in sock->ops->getname(sock, (struct
sockaddr *)&address, 0) is partially initialized.  It only initializes the
fields in the struct but not the holes between the fields. As a result, since
uaddr will be passed to copy_to_user(), and the holes inside the uaddr struct 
contain uninitialized data inherited from the kernel stack, it may cause
information leakage from kernel space to user space

Here is the initialization function:

static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
{
        struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
        struct sock *sk = sock->sk;
        struct isotp_sock *so = isotp_sk(sk);

        if (peer)
                return -EOPNOTSUPP;

        +memset(addr, 0, ISOTP_MIN_NAMELEN);//patch
        addr->can_family = AF_CAN;
        addr->can_ifindex = so->ifindex;
        addr->can_addr.tp.rx_id = so->rxid;
        addr->can_addr.tp.tx_id = so->txid;

        return ISOTP_MIN_NAMELEN;
}

The patch: memset() initializes the whole struct sockaddr_can, including the
holes within the struct sockaddr_can.

Sockaddr_can is declared here:
struct sockaddr_can {
        __kernel_sa_family_t can_family;
        int         can_ifindex;
        union {
                /* transport protocol class address information (e.g. ISOTP) */
                struct { canid_t rx_id, tx_id; } tp;

                /* J1939 address information */
                struct {
                        /* 8 byte name when using dynamic addressing */
                        __u64 name;

                        /* pgn:
                         * 8 bit: PS in PDU2 case, else 0
                         * 8 bit: PF
                         * 1 bit: DP
                         * 1 bit: reserved
                         */
                        __u32 pgn;

                        /* 1 byte address */
                        __u8 addr;
                } j1939;

                /* reserved for future CAN protocols address information */
        } can_addr;
};
There are a few holes inside the struct, but it doesn’t explicitly set to 0 in 
isotp_getname()

At the same time, I realized  sock->ops->getname(sock, (struct sockaddr
*)&address, 0) is an indirect call. Thus it may go to different functions at
the run time. If one of these functions doesn't initialize the holes within the
struct, it may cause an information leak from kernel space to userspace. 


My tools find similar cloned bugs
The same bug happens in /net/qtr/qrtr.c

static int qrtr_getname(struct socket *sock, struct sockaddr *saddr,
                        int peer)
{
        struct qrtr_sock *ipc = qrtr_sk(sock->sk);
        struct sockaddr_qrtr qaddr;
        struct sock *sk = sock->sk;

        lock_sock(sk);
        if (peer) {
                if (sk->sk_state != TCP_ESTABLISHED) {
                        release_sock(sk);
                        return -ENOTCONN;
                }

                qaddr = ipc->peer;
        } else {
                qaddr = ipc->us;
        }
        release_sock(sk);

        qaddr.sq_family = AF_QIPCRTR;

        memcpy(saddr, &qaddr, sizeof(qaddr));

        return sizeof(qaddr);
}



sockaddr_qrtr is declared here:

struct sockaddr_qrtr {
        __kernel_sa_family_t sq_family;
        __u32 sq_node;
        __u32 sq_port;
};

We can see there is a hole between sq_family and sq_node. Thus, we have to
explicitly set the hole to zero. Otherwise, the address will be passed to
copy_to_user and cause information leakage.

Suggested patch:
memset(maddr, 0, sizeof(sockaddr_qrtr));


Thank you for the review. I appreciate your time. 

Andrew Bao

-- 
You may reply to this email to add a comment.

You are receiving this mail because:
You are the assignee for the bug.

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ