diff options
author | Dmitry Chagin <dchagin@FreeBSD.org> | 2019-05-13 17:48:16 +0000 |
---|---|---|
committer | Dmitry Chagin <dchagin@FreeBSD.org> | 2019-05-13 17:48:16 +0000 |
commit | d5368bf3dff2b4627e2d69cce5c5dd98428fdaba (patch) | |
tree | 529d60cbcb45b6d4c3380aa588054b8bf953dc96 /sys/compat/linux/linux.c | |
parent | 54a3a1142189f60edfbbdfb0e1f4085e2d30c115 (diff) | |
download | src-d5368bf3dff2b4627e2d69cce5c5dd98428fdaba.tar.gz src-d5368bf3dff2b4627e2d69cce5c5dd98428fdaba.zip |
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
Notes
Notes:
svn path=/head/; revision=347533
Diffstat (limited to 'sys/compat/linux/linux.c')
-rw-r--r-- | sys/compat/linux/linux.c | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/sys/compat/linux/linux.c b/sys/compat/linux/linux.c index 33a2a1098401..0c725ad5dc9c 100644 --- a/sys/compat/linux/linux.c +++ b/sys/compat/linux/linux.c @@ -27,21 +27,29 @@ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); +#include <opt_inet6.h> + #include <sys/param.h> #include <sys/systm.h> #include <sys/ctype.h> #include <sys/jail.h> #include <sys/lock.h> +#include <sys/malloc.h> #include <sys/signalvar.h> #include <sys/socket.h> +#include <sys/socketvar.h> #include <net/if.h> #include <net/if_var.h> #include <net/if_dl.h> #include <net/if_types.h> +#include <sys/un.h> +#include <netinet/in.h> + #include <compat/linux/linux.h> #include <compat/linux/linux_common.h> +#include <compat/linux/linux_util.h> CTASSERT(LINUX_IFNAMSIZ == IFNAMSIZ); @@ -323,3 +331,193 @@ linux_ifhwaddr(struct ifnet *ifp, struct l_sockaddr *lsa) return (ENOENT); } + +int +linux_to_bsd_domain(int domain) +{ + + switch (domain) { + case LINUX_AF_UNSPEC: + return (AF_UNSPEC); + case LINUX_AF_UNIX: + return (AF_LOCAL); + case LINUX_AF_INET: + return (AF_INET); + case LINUX_AF_INET6: + return (AF_INET6); + case LINUX_AF_AX25: + return (AF_CCITT); + case LINUX_AF_IPX: + return (AF_IPX); + case LINUX_AF_APPLETALK: + return (AF_APPLETALK); + } + return (-1); +} + +int +bsd_to_linux_domain(int domain) +{ + + switch (domain) { + case AF_UNSPEC: + return (LINUX_AF_UNSPEC); + case AF_LOCAL: + return (LINUX_AF_UNIX); + case AF_INET: + return (LINUX_AF_INET); + case AF_INET6: + return (LINUX_AF_INET6); + case AF_CCITT: + return (LINUX_AF_AX25); + case AF_IPX: + return (LINUX_AF_IPX); + case AF_APPLETALK: + return (LINUX_AF_APPLETALK); + } + return (-1); +} + +/* + * Based on the fact that: + * 1. Native and Linux storage of struct sockaddr + * and struct sockaddr_in6 are equal. + * 2. On Linux sa_family is the first member of all struct sockaddr. + */ +int +bsd_to_linux_sockaddr(const struct sockaddr *sa, struct l_sockaddr **lsa, + socklen_t len) +{ + struct l_sockaddr *kosa; + int error, bdom; + + *lsa = NULL; + if (len < 2 || len > UCHAR_MAX) + return (EINVAL); + + kosa = malloc(len, M_SONAME, M_WAITOK); + bcopy(sa, kosa, len); + + bdom = bsd_to_linux_domain(sa->sa_family); + if (bdom == -1) { + error = EAFNOSUPPORT; + goto out; + } + + kosa->sa_family = bdom; + *lsa = kosa; + return (0); + +out: + free(kosa, M_SONAME); + return (error); +} + +int +linux_to_bsd_sockaddr(const struct l_sockaddr *osa, struct sockaddr **sap, + socklen_t *len) +{ + struct sockaddr *sa; + struct l_sockaddr *kosa; +#ifdef INET6 + struct sockaddr_in6 *sin6; + bool oldv6size; +#endif + char *name; + int salen, bdom, error, hdrlen, namelen; + + if (*len < 2 || *len > UCHAR_MAX) + return (EINVAL); + + salen = *len; + +#ifdef INET6 + oldv6size = false; + /* + * Check for old (pre-RFC2553) sockaddr_in6. We may accept it + * if it's a v4-mapped address, so reserve the proper space + * for it. + */ + if (salen == sizeof(struct sockaddr_in6) - sizeof(uint32_t)) { + salen += sizeof(uint32_t); + oldv6size = true; + } +#endif + + kosa = malloc(salen, M_SONAME, M_WAITOK); + + if ((error = copyin(osa, kosa, *len))) + goto out; + + bdom = linux_to_bsd_domain(kosa->sa_family); + if (bdom == -1) { + error = EAFNOSUPPORT; + goto out; + } + +#ifdef INET6 + /* + * Older Linux IPv6 code uses obsolete RFC2133 struct sockaddr_in6, + * which lacks the scope id compared with RFC2553 one. If we detect + * the situation, reject the address and write a message to system log. + * + * Still accept addresses for which the scope id is not used. + */ + if (oldv6size) { + if (bdom == AF_INET6) { + sin6 = (struct sockaddr_in6 *)kosa; + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || + (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && + !IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && + !IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr) && + !IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && + !IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))) { + sin6->sin6_scope_id = 0; + } else { + linux_msg(curthread, + "obsolete pre-RFC2553 sockaddr_in6 rejected\n"); + error = EINVAL; + goto out; + } + } else + salen -= sizeof(uint32_t); + } +#endif + if (bdom == AF_INET) { + if (salen < sizeof(struct sockaddr_in)) { + error = EINVAL; + goto out; + } + salen = sizeof(struct sockaddr_in); + } + + if (bdom == AF_LOCAL && salen > sizeof(struct sockaddr_un)) { + hdrlen = offsetof(struct sockaddr_un, sun_path); + name = ((struct sockaddr_un *)kosa)->sun_path; + if (*name == '\0') { + /* + * Linux abstract namespace starts with a NULL byte. + * XXX We do not support abstract namespace yet. + */ + namelen = strnlen(name + 1, salen - hdrlen - 1) + 1; + } else + namelen = strnlen(name, salen - hdrlen); + salen = hdrlen + namelen; + if (salen > sizeof(struct sockaddr_un)) { + error = ENAMETOOLONG; + goto out; + } + } + + sa = (struct sockaddr *)kosa; + sa->sa_family = bdom; + sa->sa_len = salen; + + *sap = sa; + *len = salen; + return (0); + +out: + free(kosa, M_SONAME); + return (error); +} |