aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Chagin <dchagin@FreeBSD.org>2023-03-04 09:11:38 +0000
committerDmitry Chagin <dchagin@FreeBSD.org>2023-03-04 09:11:38 +0000
commitf9b0675b014d06995e5337b71129fc94bd1cedd0 (patch)
tree547a675a4f909247659934f7fa9635cb683141ea
parent9e7f03e9c688d495a78f51b5122a012fa52df57b (diff)
downloadsrc-f9b0675b014d06995e5337b71129fc94bd1cedd0.tar.gz
src-f9b0675b014d06995e5337b71129fc94bd1cedd0.zip
linux(4): Refactor socket ioctl path to avoid referencing an unstable interfaces
Split the linux_ioctl_socket() function on two counterparts, where the linux_ioctl_socket_ifreq() intended to use in a code path which requires the struct ifreq manipulation, i.e., translating in/out values of the struct, while the linux_ioctl_socket() function is left as is, it calls sys_ioctl() without touching in/out values. Due to structures ifreq, sockaddr difference between FreeBSD and Linux the linux_ioctl_socket_ifreq() calls kern_ioctl() directly, converting in and out values to FreeBSD and to Linux accordingly. Finally, modify the ifname_linux_to_bsd() to return error code, not an unstable reference to the interface. Reviewed by: melifaro Differential Revision: https://reviews.freebsd.org/D38794
-rw-r--r--sys/compat/linux/linux.c4
-rw-r--r--sys/compat/linux/linux_common.h3
-rw-r--r--sys/compat/linux/linux_ioctl.c310
3 files changed, 132 insertions, 185 deletions
diff --git a/sys/compat/linux/linux.c b/sys/compat/linux/linux.c
index f31a4b5e4f5c..2e6e52f7490c 100644
--- a/sys/compat/linux/linux.c
+++ b/sys/compat/linux/linux.c
@@ -411,7 +411,7 @@ ifname_linux_to_ifp(struct thread *td, const char *lxname)
return (arg.ifp);
}
-struct ifnet *
+int
ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname)
{
struct epoch_tracker et;
@@ -424,7 +424,7 @@ ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname)
strlcpy(bsdname, if_name(ifp), IFNAMSIZ);
NET_EPOCH_EXIT(et);
CURVNET_RESTORE();
- return (ifp);
+ return (ifp != NULL ? 0 : EINVAL);
}
unsigned short
diff --git a/sys/compat/linux/linux_common.h b/sys/compat/linux/linux_common.h
index 4b693ccaf868..3392f55672f3 100644
--- a/sys/compat/linux/linux_common.h
+++ b/sys/compat/linux/linux_common.h
@@ -34,9 +34,8 @@ int ifname_bsd_to_linux_ifp(struct ifnet *, char *, size_t);
int ifname_bsd_to_linux_idx(u_int, char *, size_t);
int ifname_bsd_to_linux_name(const char *, char *, size_t);
struct ifnet *ifname_linux_to_ifp(struct thread *, const char *);
+int ifname_linux_to_bsd(struct thread *, const char *, char *);
-struct ifnet *ifname_linux_to_bsd(struct thread *td,
- const char *lxname, char *bsdname);
unsigned short linux_ifflags(struct ifnet *);
int linux_ifhwaddr(struct ifnet *ifp, struct l_sockaddr *lsa);
diff --git a/sys/compat/linux/linux_ioctl.c b/sys/compat/linux/linux_ioctl.c
index 9f1fdd3a6671..4c95745e4307 100644
--- a/sys/compat/linux/linux_ioctl.c
+++ b/sys/compat/linux/linux_ioctl.c
@@ -49,6 +49,7 @@ __FBSDID("$FreeBSD$");
#include <sys/sbuf.h>
#include <sys/sockio.h>
#include <sys/soundcard.h>
+#include <sys/syscallsubr.h>
#include <sys/sysctl.h>
#include <sys/sysproto.h>
#include <sys/sx.h>
@@ -2187,22 +2188,17 @@ linux_ifconf(struct thread *td, struct ifconf *uifc)
full = 0;
cbs.max_len = maxphys - 1;
- CURVNET_SET(TD_TO_VNET(td));
/* handle the 'request buffer size' case */
if ((l_uintptr_t)ifc.ifc_buf == PTROUT(NULL)) {
ifc.ifc_len = 0;
NET_EPOCH_ENTER(et);
if_foreach(linux_ifconf_ifnet_cb, &ifc);
NET_EPOCH_EXIT(et);
- error = copyout(&ifc, uifc, sizeof(ifc));
- CURVNET_RESTORE();
- return (error);
+ return (copyout(&ifc, uifc, sizeof(ifc)));
}
- if (ifc.ifc_len <= 0) {
- CURVNET_RESTORE();
+ if (ifc.ifc_len <= 0)
return (EINVAL);
- }
again:
if (ifc.ifc_len <= cbs.max_len) {
@@ -2229,52 +2225,132 @@ again:
if (error == 0)
error = copyout(&ifc, uifc, sizeof(ifc));
sbuf_delete(sb);
- CURVNET_RESTORE();
return (error);
}
static int
-linux_gifflags(struct thread *td, struct ifnet *ifp, struct l_ifreq *ifr)
+linux_ioctl_socket_ifreq(struct thread *td, int fd, u_int cmd,
+ struct l_ifreq *uifr)
{
- unsigned short flags;
-
- flags = linux_ifflags(ifp);
-
- return (copyout(&flags, &ifr->ifr_flags, sizeof(flags)));
-}
+ struct l_ifreq lifr;
+ struct ifreq bifr;
+ size_t ifrusiz;
+ int error, temp_flags;
-static int
-linux_gifhwaddr(struct ifnet *ifp, struct l_ifreq *ifr)
-{
- struct l_sockaddr lsa;
-
- if (linux_ifhwaddr(ifp, &lsa) != 0)
- return (ENOENT);
+ switch (cmd) {
+ case LINUX_SIOCGIFINDEX:
+ cmd = SIOCGIFINDEX;
+ break;
+ case LINUX_SIOCGIFFLAGS:
+ cmd = SIOCGIFFLAGS;
+ break;
+ case LINUX_SIOCGIFADDR:
+ cmd = SIOCGIFADDR;
+ break;
+ case LINUX_SIOCSIFADDR:
+ cmd = SIOCSIFADDR;
+ break;
+ case LINUX_SIOCGIFDSTADDR:
+ cmd = SIOCGIFDSTADDR;
+ break;
+ case LINUX_SIOCGIFBRDADDR:
+ cmd = SIOCGIFBRDADDR;
+ break;
+ case LINUX_SIOCGIFNETMASK:
+ cmd = SIOCGIFNETMASK;
+ break;
+ case LINUX_SIOCSIFNETMASK:
+ cmd = SIOCSIFNETMASK;
+ break;
+ case LINUX_SIOCGIFMTU:
+ cmd = SIOCGIFMTU;
+ break;
+ case LINUX_SIOCSIFMTU:
+ cmd = SIOCSIFMTU;
+ break;
+ case LINUX_SIOCGIFHWADDR:
+ cmd = SIOCGHWADDR;
+ break;
+ /*
+ * XXX This is slightly bogus, but these ioctls are currently
+ * XXX only used by the aironet (if_an) network driver.
+ */
+ case LINUX_SIOCDEVPRIVATE:
+ cmd = SIOCGPRIVATE_0;
+ break;
+ case LINUX_SIOCDEVPRIVATE+1:
+ cmd = SIOCGPRIVATE_1;
+ break;
+ default:
+ return (ENOIOCTL);
+ }
- return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa)));
-}
+ error = copyin(uifr, &lifr, sizeof(lifr));
+ if (error != 0)
+ return (error);
+ bzero(&bifr, sizeof(bifr));
- /*
-* If we fault in bsd_to_linux_ifreq() then we will fault when we call
-* the native ioctl(). Thus, we don't really need to check the return
-* value of this function.
-*/
-static int
-bsd_to_linux_ifreq(struct ifreq *arg)
-{
- struct ifreq ifr;
- size_t ifr_len = sizeof(struct ifreq);
- int error;
+ /*
+ * The size of Linux enum ifr_ifru is bigger than
+ * the FreeBSD size due to the struct ifmap.
+ */
+ ifrusiz = (sizeof(lifr) > sizeof(bifr) ? sizeof(bifr) :
+ sizeof(lifr)) - offsetof(struct l_ifreq, ifr_ifru);
+ bcopy(&lifr.ifr_ifru, &bifr.ifr_ifru, ifrusiz);
- if ((error = copyin(arg, &ifr, ifr_len)))
+ error = ifname_linux_to_bsd(td, lifr.ifr_name, bifr.ifr_name);
+ if (error != 0)
return (error);
- *(u_short *)&ifr.ifr_addr = ifr.ifr_addr.sa_family;
+ /* Translate in values. */
+ switch (cmd) {
+ case SIOCGIFINDEX:
+ bifr.ifr_index = lifr.ifr_index;
+ break;
+ case SIOCSIFADDR:
+ case SIOCSIFNETMASK:
+ bifr.ifr_addr.sa_len = sizeof(struct sockaddr);
+ bifr.ifr_addr.sa_family =
+ linux_to_bsd_domain(lifr.ifr_addr.sa_family);
+ break;
+ default:
+ break;
+ }
- error = copyout(&ifr, arg, ifr_len);
+ error = kern_ioctl(td, fd, cmd, (caddr_t)&bifr);
+ if (error != 0)
+ return (error);
+ bzero(&lifr.ifr_ifru, sizeof(lifr.ifr_ifru));
+
+ /* Translate out values. */
+ switch (cmd) {
+ case SIOCGIFINDEX:
+ lifr.ifr_index = bifr.ifr_index;
+ break;
+ case SIOCGIFFLAGS:
+ temp_flags = bifr.ifr_flags | (bifr.ifr_flagshigh << 16);
+ lifr.ifr_flags = bsd_to_linux_ifflags(temp_flags);
+ break;
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCGIFNETMASK:
+ bcopy(&bifr.ifr_addr, &lifr.ifr_addr, sizeof(bifr.ifr_addr));
+ lifr.ifr_addr.sa_family =
+ bsd_to_linux_domain(bifr.ifr_addr.sa_family);
+ break;
+ case SIOCGHWADDR:
+ bcopy(&bifr.ifr_addr, &lifr.ifr_hwaddr, sizeof(bifr.ifr_addr));
+ lifr.ifr_hwaddr.sa_family = LINUX_ARPHRD_ETHER;
+ break;
+ default:
+ bcopy(&bifr.ifr_ifru, &lifr.ifr_ifru, ifrusiz);
+ break;
+ }
- return (error);
+ return (copyout(&lifr, uifr, sizeof(lifr)));
}
/*
@@ -2284,84 +2360,34 @@ bsd_to_linux_ifreq(struct ifreq *arg)
static int
linux_ioctl_socket(struct thread *td, struct linux_ioctl_args *args)
{
- char lifname[LINUX_IFNAMSIZ], ifname[IFNAMSIZ];
- struct ifnet *ifp;
struct file *fp;
int error, type;
- ifp = NULL;
- error = 0;
-
error = fget(td, args->fd, &cap_ioctl_rights, &fp);
if (error != 0)
return (error);
type = fp->f_type;
fdrop(fp, td);
+
+ CURVNET_SET(TD_TO_VNET(td));
+
if (type != DTYPE_SOCKET) {
/* not a socket - probably a tap / vmnet device */
switch (args->cmd) {
case LINUX_SIOCGIFADDR:
case LINUX_SIOCSIFADDR:
case LINUX_SIOCGIFFLAGS:
- return (linux_ioctl_special(td, args));
+ error = linux_ioctl_special(td, args);
+ break;
default:
- return (ENOIOCTL);
+ error = ENOIOCTL;
+ break;
}
+ CURVNET_RESTORE();
+ return (error);
}
- switch (args->cmd & 0xffff) {
- case LINUX_FIOGETOWN:
- case LINUX_FIOSETOWN:
- case LINUX_SIOCADDMULTI:
- case LINUX_SIOCATMARK:
- case LINUX_SIOCDELMULTI:
- case LINUX_SIOCGIFNAME:
- case LINUX_SIOCGIFCONF:
- case LINUX_SIOCGPGRP:
- case LINUX_SIOCSPGRP:
- case LINUX_SIOCGIFCOUNT:
- /* these ioctls don't take an interface name */
- break;
-
- case LINUX_SIOCGIFFLAGS:
- case LINUX_SIOCGIFADDR:
- case LINUX_SIOCSIFADDR:
- case LINUX_SIOCGIFDSTADDR:
- case LINUX_SIOCGIFBRDADDR:
- case LINUX_SIOCGIFNETMASK:
- case LINUX_SIOCSIFNETMASK:
- case LINUX_SIOCGIFMTU:
- case LINUX_SIOCSIFMTU:
- case LINUX_SIOCSIFNAME:
- case LINUX_SIOCGIFHWADDR:
- case LINUX_SIOCSIFHWADDR:
- case LINUX_SIOCDEVPRIVATE:
- case LINUX_SIOCDEVPRIVATE+1:
- case LINUX_SIOCGIFINDEX:
- /* copy in the interface name and translate it. */
- error = copyin((void *)args->arg, lifname, LINUX_IFNAMSIZ);
- if (error != 0)
- return (error);
- memset(ifname, 0, sizeof(ifname));
- ifp = ifname_linux_to_bsd(td, lifname, ifname);
- if (ifp == NULL)
- return (EINVAL);
- /*
- * We need to copy it back out in case we pass the
- * request on to our native ioctl(), which will expect
- * the ifreq to be in user space and have the correct
- * interface name.
- */
- error = copyout(ifname, (void *)args->arg, IFNAMSIZ);
- if (error != 0)
- return (error);
- break;
-
- default:
- return (ENOIOCTL);
- }
-
- switch (args->cmd & 0xffff) {
+ switch (args->cmd) {
case LINUX_FIOSETOWN:
args->cmd = FIOSETOWN;
error = sys_ioctl(td, (struct ioctl_args *)args);
@@ -2397,67 +2423,6 @@ linux_ioctl_socket(struct thread *td, struct linux_ioctl_args *args)
error = linux_ifconf(td, (struct ifconf *)args->arg);
break;
- case LINUX_SIOCGIFFLAGS:
- args->cmd = SIOCGIFFLAGS;
- error = linux_gifflags(td, ifp, (struct l_ifreq *)args->arg);
- break;
-
- case LINUX_SIOCGIFADDR:
- args->cmd = SIOCGIFADDR;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- bsd_to_linux_ifreq((struct ifreq *)args->arg);
- break;
-
- case LINUX_SIOCSIFADDR:
- /* XXX probably doesn't work, included for completeness */
- args->cmd = SIOCSIFADDR;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- break;
-
- case LINUX_SIOCGIFDSTADDR:
- args->cmd = SIOCGIFDSTADDR;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- bsd_to_linux_ifreq((struct ifreq *)args->arg);
- break;
-
- case LINUX_SIOCGIFBRDADDR:
- args->cmd = SIOCGIFBRDADDR;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- bsd_to_linux_ifreq((struct ifreq *)args->arg);
- break;
-
- case LINUX_SIOCGIFNETMASK:
- args->cmd = SIOCGIFNETMASK;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- bsd_to_linux_ifreq((struct ifreq *)args->arg);
- break;
-
- case LINUX_SIOCSIFNETMASK:
- error = ENOIOCTL;
- break;
-
- case LINUX_SIOCGIFMTU:
- args->cmd = SIOCGIFMTU;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- break;
-
- case LINUX_SIOCSIFMTU:
- args->cmd = SIOCSIFMTU;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- break;
-
- case LINUX_SIOCSIFNAME:
- error = ENOIOCTL;
- break;
-
- case LINUX_SIOCGIFHWADDR:
- error = linux_gifhwaddr(ifp, (struct l_ifreq *)args->arg);
- break;
-
- case LINUX_SIOCSIFHWADDR:
- error = ENOIOCTL;
- break;
-
case LINUX_SIOCADDMULTI:
args->cmd = SIOCADDMULTI;
error = sys_ioctl(td, (struct ioctl_args *)args);
@@ -2468,34 +2433,17 @@ linux_ioctl_socket(struct thread *td, struct linux_ioctl_args *args)
error = sys_ioctl(td, (struct ioctl_args *)args);
break;
- case LINUX_SIOCGIFINDEX:
- args->cmd = SIOCGIFINDEX;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- break;
-
case LINUX_SIOCGIFCOUNT:
error = 0;
break;
- /*
- * XXX This is slightly bogus, but these ioctls are currently
- * XXX only used by the aironet (if_an) network driver.
- */
- case LINUX_SIOCDEVPRIVATE:
- args->cmd = SIOCGPRIVATE_0;
- error = sys_ioctl(td, (struct ioctl_args *)args);
- break;
-
- case LINUX_SIOCDEVPRIVATE+1:
- args->cmd = SIOCGPRIVATE_1;
- error = sys_ioctl(td, (struct ioctl_args *)args);
+ default:
+ error = linux_ioctl_socket_ifreq(td, args->fd, args->cmd,
+ PTRIN(args->arg));
break;
}
- if (ifp != NULL)
- /* restore the original interface name */
- copyout(lifname, (void *)args->arg, LINUX_IFNAMSIZ);
-
+ CURVNET_RESTORE();
return (error);
}