Bug 232920 - linuxulator: linux_to_bsd_sockaddr and bsd_to_linux_sockaddr are unsafe
Summary: linuxulator: linux_to_bsd_sockaddr and bsd_to_linux_sockaddr are unsafe
Status: Closed FIXED
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: CURRENT
Hardware: Any Any
: --- Affects Only Me
Assignee: Dmitry Chagin
URL:
Keywords:
Depends on:
Blocks: 247219
  Show dependency treegraph
 
Reported: 2018-11-02 22:49 UTC by Brooks Davis
Modified: 2020-09-01 00:42 UTC (History)
6 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Brooks Davis freebsd_committer freebsd_triage 2018-11-02 22:49:18 UTC
The bsd_to_linux_sockaddr() and linux_to_bsd_sockaddr() functions alter the userspace sockaddr to convert the format between linux and BSD versions.  If the sockaddr is shared between concurrent syscalls or the pages are unwritable these functions will fail.

Code should either be altered to perform the transformation purely in the kernel or at a minimum, copyout_map should be used to allocated a new location for the transformed sockaddr.
Comment 1 Vic Chen 2019-02-26 04:59:44 UTC
Another related issue is:

The function 'linux_getsockname' in 'linux_socket.c' calls 'bsd_to_linux_sockaddr', and it calls 'bsd_to_linux_domain' to convert 'sa_family' from BSD domain to Linux domain.
 
 But after calling  'bsd_to_linux_sockaddr', 'linux_sa_put' is called, and it calls 'bsd_to_linux_domain' to convert 'sa_family' from BSD domain to Linux domain again.
But the 'sa_family' has already been converted.
Since the value of AF_INTE6 and LINUX_AF_INET6 is different, and converting twice will cause issue. 

Test Case below:
get_sock_name_case.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <netinet/in.h>

int main()
{
    int sock;
    int len = sizeof(struct sockaddr_in6);
    struct sockaddr_in6 add;

    if ((sock=socket(AF_INET6, SOCK_STREAM, 0)) < 0)
    {
        perror("sock");
        return 1;
    }

    memset(&add, 0, len);
    add.sin6_family = AF_INET6;
    add.sin6_port = htons(10000);
    printf("bind address type:%d-port:%d\n", add.sin6_family, ntohs(add.sin6_port));

    if (bind(sock, (struct sockaddr *)&add, len)<0)
    {
        perror("bind");
        return 1;
    }

    memset(&add, 0, len);
    getsockname(sock, (struct sockaddr *)&add, &len);
    printf("getsockname address type:%d-port:%d\n", add.sin6_family, ntohs(add.sin6_port));

    return 0;
}

result:
case in linux
user@~/:uname -a
Linux CentOS 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
user@~/:cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

bind address type:10-port:10000
getsockname address type:10-port:10000

case in freebsd
[user@host ~]# uname -a
FreeBSD host 12.0-RELEASE FreeBSD 12.0-RELEASE #94 308a36af9(master)-dirty: Mon Feb 25 16:33:41 CST 2019     KERNCONF  amd64
[user@host ~]# ./case
bind address type:10-port:10000
getsockname address type:3-port:10000
Comment 2 commit-hook freebsd_committer freebsd_triage 2019-05-13 17:49:02 UTC
A commit references this bug:

Author: dchagin
Date: Mon May 13 17:48:17 UTC 2019
New revision: 347533
URL: https://svnweb.freebsd.org/changeset/base/347533

Log:
  Our bsd_to_linux_sockaddr() and linux_to_bsd_sockaddr() functions
  alter the userspace sockaddr to convert the format between linux and BSD versions.
  That's the minimum 3 of copyin/copyout operations for one syscall.

  Also some syscall uses linux_sa_put() and linux_getsockaddr() when load
  sockaddr to userspace or from userspace accordingly.

  To avoid this chaos, especially converting sockaddr in the userspace,
  rewrite these 4 functions to convert sockaddr only in kernel and leave
  only 2 of this functions.

  Also in order to reduce duplication between MD parts of the Linuxulator put
  struct sockaddr conversion functions that are MI out into linux_common module.

  PR:		232920
  MFC after:	2 weeks
  Differential Revision:	https://reviews.freebsd.org/D20157

Changes:
  head/sys/compat/linux/linux.c
  head/sys/compat/linux/linux.h
  head/sys/compat/linux/linux_common.h
  head/sys/compat/linux/linux_socket.c
  head/sys/compat/linux/linux_socket.h
  head/sys/modules/linux_common/Makefile
Comment 3 Mark Johnston freebsd_committer freebsd_triage 2020-01-16 19:20:17 UTC
Should this PR be kept open?  I note that there was no MFC.
Comment 4 Dmitry Chagin freebsd_committer freebsd_triage 2020-01-21 15:35:55 UTC
(In reply to Mark Johnston from comment #3)
will mfc it soon
Comment 5 commit-hook freebsd_committer freebsd_triage 2020-08-24 15:46:06 UTC
A commit references this bug:

Author: trasz
Date: Mon Aug 24 15:45:43 UTC 2020
New revision: 364693
URL: https://svnweb.freebsd.org/changeset/base/364693

Log:
  MFC r347533 by dchagin:

  Our bsd_to_linux_sockaddr() and linux_to_bsd_sockaddr() functions
  alter the userspace sockaddr to convert the format between linux and BSD versions.
  That's the minimum 3 of copyin/copyout operations for one syscall.

  Also some syscall uses linux_sa_put() and linux_getsockaddr() when load
  sockaddr to userspace or from userspace accordingly.

  To avoid this chaos, especially converting sockaddr in the userspace,
  rewrite these 4 functions to convert sockaddr only in kernel and leave
  only 2 of this functions.

  Also in order to reduce duplication between MD parts of the Linuxulator put
  struct sockaddr conversion functions that are MI out into linux_common module.

  PR:		232920

Changes:
_U  stable/12/
  stable/12/sys/compat/linux/linux.c
  stable/12/sys/compat/linux/linux.h
  stable/12/sys/compat/linux/linux_common.h
  stable/12/sys/compat/linux/linux_socket.c
  stable/12/sys/compat/linux/linux_socket.h
  stable/12/sys/modules/linux_common/Makefile