vmd's doing something close to shotgun parsing of the "local prefix" and "local inet6 prefix" settings in vm.conf(5). The parser intermixes ipv4 and ipv6 parsing even when we know which one is valid in the parsing context. This makes me sad.
Even worse, we're not validating the inputs at time of parsing and deferring to vm creation time. This makes me even sadder. The diff below: - splits parsing ipv4 and ipv6 into distinct functions - puts the validation into those functions (e.g prefix length, prefix has properly zero'd octets) - does *not* muck (yet) with the existing validation sprinkled in priv.c or config.c I thought about making pulling apart the prefix from prefix length parsing, but getaddrinfo(3) appears to not like parsing addresses like "192.168.0.0" vs "192.168.0.0/16". (I'm not a network person...maybe I'm being dumb here.) kn: this addresses some of your feedback on my previous diff from a few weeks ago. ok? diff refs/heads/master refs/heads/vmd-parse commit - 5d90c77abd2d7447f16f88ac9ea9e0485eac9f73 commit + 228fe48802ec6250e3487aa791daceba4626b03f blob - b538d40be1a1e600c1021d95e3fadd310079fa7a blob + f5a2507ff5742ea3a62b0112e14d17aa8cbdf99d --- usr.sbin/vmd/config.c +++ usr.sbin/vmd/config.c @@ -49,7 +49,7 @@ config_init_localprefix(struct vmd_config *cfg) { struct sockaddr_in6 *sin6; - if (host(VMD_DHCP_PREFIX, &cfg->cfg_localprefix) == -1) + if (parse_prefix4(VMD_DHCP_PREFIX, &cfg->cfg_localprefix, NULL) == -1) return (-1); /* IPv6 is disabled by default */ @@ -58,7 +58,7 @@ config_init_localprefix(struct vmd_config *cfg) /* Generate random IPv6 prefix only once */ if (cfg->cfg_flags & VMD_CFG_AUTOINET6) return (0); - if (host(VMD_ULA_PREFIX, &cfg->cfg_localprefix6) == -1) + if (parse_prefix6(VMD_ULA_PREFIX, &cfg->cfg_localprefix6, NULL) == -1) return (-1); /* Randomize the 56 bits "Global ID" and "Subnet ID" */ sin6 = ss2sin6(&cfg->cfg_localprefix6.ss); blob - 09468e3fe2c9f4f9193710c65667132f79a90df3 blob + 3d030c201db3e8167831846cb1c8f6e3facc40fc --- usr.sbin/vmd/parse.y +++ usr.sbin/vmd/parse.y @@ -190,31 +190,30 @@ main : LOCAL INET6 { } | LOCAL INET6 PREFIX STRING { struct address h; + const char *err; - if (host($4, &h) == -1 || - h.ss.ss_family != AF_INET6 || - h.prefixlen > 64 || h.prefixlen < 0) { - yyerror("invalid local inet6 prefix: %s", $4); - free($4); + if (parse_prefix6($4, &h, &err)) { + yyerror("invalid local inet6 prefix: %s", err); YYERROR; + } else { + env->vmd_cfg.cfg_flags |= VMD_CFG_INET6; + env->vmd_cfg.cfg_flags &= ~VMD_CFG_AUTOINET6; + memcpy(&env->vmd_cfg.cfg_localprefix6, &h, + sizeof(h)); } - - env->vmd_cfg.cfg_flags |= VMD_CFG_INET6; - env->vmd_cfg.cfg_flags &= ~VMD_CFG_AUTOINET6; - memcpy(&env->vmd_cfg.cfg_localprefix6, &h, sizeof(h)); + free($4); } | LOCAL PREFIX STRING { struct address h; + const char *err; - if (host($3, &h) == -1 || - h.ss.ss_family != AF_INET || - h.prefixlen > 32 || h.prefixlen < 0) { - yyerror("invalid local prefix: %s", $3); - free($3); + if (parse_prefix4($3, &h, &err)) { + yyerror("invalid local prefix: %s", err); YYERROR; - } - - memcpy(&env->vmd_cfg.cfg_localprefix, &h, sizeof(h)); + } else + memcpy(&env->vmd_cfg.cfg_localprefix, &h, + sizeof(h)); + free($3); } | SOCKET OWNER owner_id { env->vmd_ps.ps_csock.cs_uid = $3.uid; @@ -1404,42 +1403,119 @@ int return (0); } +/* + * Parse an ipv4 address and prefix for local interfaces and validate + * constraints for vmd networking. + */ int -host(const char *str, struct address *h) +parse_prefix4(const char *str, struct address *h, const char **errstr) { - struct addrinfo hints, *res; - int prefixlen; - char *s, *p; - const char *errstr; + struct addrinfo hints, *res = NULL; + in_addr_t addr; + int prefixlen, ret; + char *s = NULL, *p = NULL; - if ((s = strdup(str)) == NULL) { - log_warn("%s", __func__); - goto fail; + if ((s = strdup(str)) == NULL) + fatal("%s: strdup", __func__); + p = strrchr(s, '/'); + if (p == NULL) { + if (errstr) + *errstr = "missing netmask"; + return (1); } + *p++ = '\0'; - if ((p = strrchr(s, '/')) != NULL) { - *p++ = '\0'; - prefixlen = strtonum(p, 0, 128, &errstr); - if (errstr) { - log_warnx("prefixlen is %s: %s", errstr, p); - goto fail; + /* Parse our prefix. For ipv4, we accept 16 as maximum. */ + prefixlen = strtonum(p, 1, 16, errstr); + if (prefixlen == 0) { + free(s); + return (1); + } + + /* Attempt to construct an address from the user input. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_flags = AI_NUMERICHOST; + if ((ret = getaddrinfo(s, NULL, &hints, &res)) != 0) { + if (errstr != NULL) + *errstr = gai_strerror(ret); + free(s); + return (1); + } + free(s); + + memset(h, 0, sizeof(*h)); + memcpy(&h->ss, res->ai_addr, res->ai_addrlen); + h->prefixlen = prefixlen; + freeaddrinfo(res); + + /* Our prefix address should end with zeros. */ + addr = ss2sin(&h->ss)->sin_addr.s_addr; + if (addr & 0xFFFF0000) { + if (errstr != NULL) + *errstr = "last two octets must be zero"; + return (1); + } + + return (0); +} + +/* + * Parse an ipv6 address and prefix for local interfaces and validate + * constraints for vmd networking. + */ +int +parse_prefix6(const char *str, struct address *h, const char **errstr) +{ + struct addrinfo hints, *res = NULL; + struct in6_addr *addr6 = NULL; + int prefixlen, ret; + char *s = NULL, *p = NULL; + size_t i; + + if ((s = strdup(str)) == NULL) + fatal("%s: strdup", __func__); + p = strrchr(s, '/'); + if (p == NULL) { + if (errstr) + *errstr = "missing netmask"; + return (1); + } + *p++ = '\0'; + + /* Parse our prefix. For ipv6, we accept 64 as a maximum. */ + prefixlen = strtonum(p, 1, 64, errstr); + if (prefixlen == 0) { + free(s); + return (1); + } + + /* Attempt to construct an address from the user input. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_NUMERICHOST; + if ((ret = getaddrinfo(s, NULL, &hints, &res)) != 0) { + if (errstr != NULL) + *errstr = gai_strerror(ret); + free(s); + return (1); + } + free(s); + + memset(h, 0, sizeof(*h)); + memcpy(&h->ss, res->ai_addr, res->ai_addrlen); + h->prefixlen = prefixlen; + freeaddrinfo(res); + + /* Last 8 octets are reserved for vmd. */ + addr6 = &ss2sin6(&h->ss)->sin6_addr; + for (i = 8; i < 16; i++) { + if (addr6->s6_addr[i] != 0) { + if (errstr != NULL) + *errstr = "last eight octets must be zero"; + return (1); } - } else - prefixlen = 128; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_NUMERICHOST; - if (getaddrinfo(s, NULL, &hints, &res) == 0) { - memset(h, 0, sizeof(*h)); - memcpy(&h->ss, res->ai_addr, res->ai_addrlen); - h->prefixlen = prefixlen; - freeaddrinfo(res); - free(s); - return (0); } - fail: - free(s); - return (-1); + return (0); } blob - 9c25b0c92ade2e1a1d3a1f67548becfc3d4eca7b blob + 91a8e1117f4859026db8adaa01d951ce1d9b4c11 --- usr.sbin/vmd/vmd.h +++ usr.sbin/vmd/vmd.h @@ -518,7 +518,8 @@ int host(const char *, struct address *); /* parse.y */ int parse_config(const char *); int cmdline_symset(char *); -int host(const char *, struct address *); +int parse_prefix4(const char *, struct address *, const char **); +int parse_prefix6(const char *, struct address *, const char **); /* virtio.c */ int virtio_get_base(int, char *, size_t, int, const char *);