mirror_iproute2/ip/iptuntap.c
Phil Sutter 625df645b7 Check user supplied interface name lengths
The original problem was that something like:

| strncpy(ifr.ifr_name, *argv, IFNAMSIZ);

might leave ifr.ifr_name unterminated if length of *argv exceeds
IFNAMSIZ. In order to fix this, I thought about replacing all those
cases with (equivalent) calls to snprintf() or even introducing
strlcpy(). But as Ulrich Drepper correctly pointed out when rejecting
the latter from being added to glibc, truncating a string without
notifying the user is not to be considered good practice. So let's
excercise what he suggested and reject empty, overlong or otherwise
invalid interface names right from the start - this way calls to
strncpy() like shown above become safe and the user has a chance to
reconsider what he was trying to do.

Note that this doesn't add calls to check_ifname() to all places where
user supplied interface name is parsed. In many cases, the interface
must exist already and is therefore looked up using ll_name_to_index(),
so if_nametoindex() will perform the necessary checks already.

Signed-off-by: Phil Sutter <phil@nwl.cc>
2017-10-02 08:01:21 -07:00

443 lines
8.9 KiB
C

/*
* iptunnel.c "ip tuntap"
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: David Woodhouse <David.Woodhouse@intel.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <glob.h>
#include "rt_names.h"
#include "utils.h"
#include "ip_common.h"
#define TUNDEV "/dev/net/tun"
static void usage(void) __attribute__((noreturn));
static void usage(void)
{
fprintf(stderr, "Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ]\n");
fprintf(stderr, " [ mode { tun | tap } ] [ user USER ] [ group GROUP ]\n");
fprintf(stderr, " [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ] [ name NAME ]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Where: USER := { STRING | NUMBER }\n");
fprintf(stderr, " GROUP := { STRING | NUMBER }\n");
exit(-1);
}
static int tap_add_ioctl(struct ifreq *ifr, uid_t uid, gid_t gid)
{
int fd;
int ret = -1;
#ifdef IFF_TUN_EXCL
ifr->ifr_flags |= IFF_TUN_EXCL;
#endif
fd = open(TUNDEV, O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
if (ioctl(fd, TUNSETIFF, ifr)) {
perror("ioctl(TUNSETIFF)");
goto out;
}
if (uid != -1 && ioctl(fd, TUNSETOWNER, uid)) {
perror("ioctl(TUNSETOWNER)");
goto out;
}
if (gid != -1 && ioctl(fd, TUNSETGROUP, gid)) {
perror("ioctl(TUNSETGROUP)");
goto out;
}
if (ioctl(fd, TUNSETPERSIST, 1)) {
perror("ioctl(TUNSETPERSIST)");
goto out;
}
ret = 0;
out:
close(fd);
return ret;
}
static int tap_del_ioctl(struct ifreq *ifr)
{
int fd = open(TUNDEV, O_RDWR);
int ret = -1;
if (fd < 0) {
perror("open");
return -1;
}
if (ioctl(fd, TUNSETIFF, ifr)) {
perror("ioctl(TUNSETIFF)");
goto out;
}
if (ioctl(fd, TUNSETPERSIST, 0)) {
perror("ioctl(TUNSETPERSIST)");
goto out;
}
ret = 0;
out:
close(fd);
return ret;
}
static int parse_args(int argc, char **argv,
struct ifreq *ifr, uid_t *uid, gid_t *gid)
{
int count = 0;
memset(ifr, 0, sizeof(*ifr));
ifr->ifr_flags |= IFF_NO_PI;
while (argc > 0) {
if (matches(*argv, "mode") == 0) {
NEXT_ARG();
if (matches(*argv, "tun") == 0) {
if (ifr->ifr_flags & IFF_TAP) {
fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
exit(-1);
}
ifr->ifr_flags |= IFF_TUN;
} else if (matches(*argv, "tap") == 0) {
if (ifr->ifr_flags & IFF_TUN) {
fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
exit(-1);
}
ifr->ifr_flags |= IFF_TAP;
} else {
fprintf(stderr, "Unknown tunnel mode \"%s\"\n", *argv);
exit(-1);
}
} else if (uid && matches(*argv, "user") == 0) {
char *end;
unsigned long user;
NEXT_ARG();
if (**argv && ((user = strtol(*argv, &end, 10)), !*end))
*uid = user;
else {
struct passwd *pw = getpwnam(*argv);
if (!pw) {
fprintf(stderr, "invalid user \"%s\"\n", *argv);
exit(-1);
}
*uid = pw->pw_uid;
}
} else if (gid && matches(*argv, "group") == 0) {
char *end;
unsigned long group;
NEXT_ARG();
if (**argv && ((group = strtol(*argv, &end, 10)), !*end))
*gid = group;
else {
struct group *gr = getgrnam(*argv);
if (!gr) {
fprintf(stderr, "invalid group \"%s\"\n", *argv);
exit(-1);
}
*gid = gr->gr_gid;
}
} else if (matches(*argv, "pi") == 0) {
ifr->ifr_flags &= ~IFF_NO_PI;
} else if (matches(*argv, "one_queue") == 0) {
ifr->ifr_flags |= IFF_ONE_QUEUE;
} else if (matches(*argv, "vnet_hdr") == 0) {
ifr->ifr_flags |= IFF_VNET_HDR;
} else if (matches(*argv, "multi_queue") == 0) {
ifr->ifr_flags |= IFF_MULTI_QUEUE;
} else if (matches(*argv, "dev") == 0) {
NEXT_ARG();
if (get_ifname(ifr->ifr_name, *argv))
invarg("\"dev\" not a valid ifname", *argv);
} else {
if (matches(*argv, "name") == 0) {
NEXT_ARG();
} else if (matches(*argv, "help") == 0)
usage();
if (ifr->ifr_name[0])
duparg2("name", *argv);
if (get_ifname(ifr->ifr_name, *argv))
invarg("\"name\" not a valid ifname", *argv);
}
count++;
argc--; argv++;
}
if (!(ifr->ifr_flags & TUN_TYPE_MASK)) {
fprintf(stderr, "You failed to specify a tunnel mode\n");
return -1;
}
return 0;
}
static int do_add(int argc, char **argv)
{
struct ifreq ifr;
uid_t uid = -1;
gid_t gid = -1;
if (parse_args(argc, argv, &ifr, &uid, &gid) < 0)
return -1;
return tap_add_ioctl(&ifr, uid, gid);
}
static int do_del(int argc, char **argv)
{
struct ifreq ifr;
if (parse_args(argc, argv, &ifr, NULL, NULL) < 0)
return -1;
return tap_del_ioctl(&ifr);
}
static int read_prop(char *dev, char *prop, long *value)
{
char fname[IFNAMSIZ+25], buf[80], *endp;
ssize_t len;
int fd;
long result;
sprintf(fname, "/sys/class/net/%s/%s", dev, prop);
fd = open(fname, O_RDONLY);
if (fd < 0) {
if (strcmp(prop, "tun_flags"))
fprintf(stderr, "open %s: %s\n", fname,
strerror(errno));
return -1;
}
len = read(fd, buf, sizeof(buf)-1);
close(fd);
if (len < 0) {
fprintf(stderr, "read %s: %s", fname, strerror(errno));
return -1;
}
buf[len] = 0;
result = strtol(buf, &endp, 0);
if (*endp != '\n') {
fprintf(stderr, "Failed to parse %s\n", fname);
return -1;
}
*value = result;
return 0;
}
static void print_flags(long flags)
{
if (flags & IFF_TUN)
printf(" tun");
if (flags & IFF_TAP)
printf(" tap");
if (!(flags & IFF_NO_PI))
printf(" pi");
if (flags & IFF_ONE_QUEUE)
printf(" one_queue");
if (flags & IFF_VNET_HDR)
printf(" vnet_hdr");
flags &= ~(IFF_TUN|IFF_TAP|IFF_NO_PI|IFF_ONE_QUEUE|IFF_VNET_HDR);
if (flags)
printf(" UNKNOWN_FLAGS:%lx", flags);
}
static char *pid_name(pid_t pid)
{
char *comm;
FILE *f;
int err;
err = asprintf(&comm, "/proc/%d/comm", pid);
if (err < 0)
return NULL;
f = fopen(comm, "r");
free(comm);
if (!f) {
perror("fopen");
return NULL;
}
if (fscanf(f, "%ms\n", &comm) != 1) {
perror("fscanf");
comm = NULL;
}
if (fclose(f))
perror("fclose");
return comm;
}
static void show_processes(const char *name)
{
glob_t globbuf = { };
char **fd_path;
int err;
err = glob("/proc/[0-9]*/fd/[0-9]*", GLOB_NOSORT,
NULL, &globbuf);
if (err)
return;
fd_path = globbuf.gl_pathv;
while (*fd_path) {
const char *dev_net_tun = "/dev/net/tun";
const size_t linkbuf_len = strlen(dev_net_tun) + 2;
char linkbuf[linkbuf_len], *fdinfo;
int pid, fd;
FILE *f;
if (sscanf(*fd_path, "/proc/%d/fd/%d", &pid, &fd) != 2)
goto next;
if (pid == getpid())
goto next;
err = readlink(*fd_path, linkbuf, linkbuf_len - 1);
if (err < 0) {
perror("readlink");
goto next;
}
linkbuf[err] = '\0';
if (strcmp(dev_net_tun, linkbuf))
goto next;
if (asprintf(&fdinfo, "/proc/%d/fdinfo/%d", pid, fd) < 0)
goto next;
f = fopen(fdinfo, "r");
free(fdinfo);
if (!f) {
perror("fopen");
goto next;
}
while (!feof(f)) {
char *key = NULL, *value = NULL;
err = fscanf(f, "%m[^:]: %ms\n", &key, &value);
if (err == EOF) {
if (ferror(f))
perror("fscanf");
break;
} else if (err == 2 &&
!strcmp("iff", key) &&
!strcmp(name, value)) {
char *pname = pid_name(pid);
printf(" %s(%d)", pname ? : "<NULL>", pid);
free(pname);
}
free(key);
free(value);
}
if (fclose(f))
perror("fclose");
next:
++fd_path;
}
globfree(&globbuf);
}
static int do_show(int argc, char **argv)
{
DIR *dir;
struct dirent *d;
long flags, owner = -1, group = -1;
dir = opendir("/sys/class/net");
if (!dir) {
perror("opendir");
return -1;
}
while ((d = readdir(dir))) {
if (d->d_name[0] == '.' &&
(d->d_name[1] == 0 || d->d_name[1] == '.'))
continue;
if (read_prop(d->d_name, "tun_flags", &flags))
continue;
read_prop(d->d_name, "owner", &owner);
read_prop(d->d_name, "group", &group);
printf("%s:", d->d_name);
print_flags(flags);
if (owner != -1)
printf(" user %ld", owner);
if (group != -1)
printf(" group %ld", group);
printf("\n");
if (show_details) {
printf("\tAttached to processes:");
show_processes(d->d_name);
printf("\n");
}
}
closedir(dir);
return 0;
}
int do_iptuntap(int argc, char **argv)
{
if (argc > 0) {
if (matches(*argv, "add") == 0)
return do_add(argc-1, argv+1);
if (matches(*argv, "delete") == 0)
return do_del(argc-1, argv+1);
if (matches(*argv, "show") == 0 ||
matches(*argv, "lst") == 0 ||
matches(*argv, "list") == 0)
return do_show(argc-1, argv+1);
if (matches(*argv, "help") == 0)
usage();
} else
return do_show(0, NULL);
fprintf(stderr, "Command \"%s\" is unknown, try \"ip tuntap help\".\n",
*argv);
exit(-1);
}