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
This commit is contained in:
parent
9e7f03e9c6
commit
f9b0675b01
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 @@ linux_ifconf(struct thread *td, struct ifconf *uifc)
|
||||
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;
|
||||
struct l_ifreq lifr;
|
||||
struct ifreq bifr;
|
||||
size_t ifrusiz;
|
||||
int error, temp_flags;
|
||||
|
||||
flags = linux_ifflags(ifp);
|
||||
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(&flags, &ifr->ifr_flags, sizeof(flags)));
|
||||
}
|
||||
error = copyin(uifr, &lifr, sizeof(lifr));
|
||||
if (error != 0)
|
||||
return (error);
|
||||
bzero(&bifr, sizeof(bifr));
|
||||
|
||||
static int
|
||||
linux_gifhwaddr(struct ifnet *ifp, struct l_ifreq *ifr)
|
||||
{
|
||||
struct l_sockaddr lsa;
|
||||
/*
|
||||
* 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 (linux_ifhwaddr(ifp, &lsa) != 0)
|
||||
return (ENOENT);
|
||||
|
||||
return (copyout(&lsa, &ifr->ifr_hwaddr, sizeof(lsa)));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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));
|
||||
|
||||
return (error);
|
||||
/* 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 (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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user