Index: sys/netinet6/ip6_output.c =================================================================== --- sys/netinet6/ip6_output.c +++ sys/netinet6/ip6_output.c @@ -149,6 +149,8 @@ u_long *, int *, u_int); static int ip6_getpmtu_ctl(u_int, const struct in6_addr *, u_long *); static int copypktopts(struct ip6_pktopts *, struct ip6_pktopts *, int); +static int get_pktopt_var(struct ip6_pktopts **, struct inpcb *, size_t, size_t, bool, void **, int *); +static int get_pktopt_var_len(struct ip6_pktopts *, size_t, size_t, bool); /* @@ -2301,34 +2303,69 @@ return (ip6_setpktopt(optname, buf, len, opt, cred, 1, 0, uproto)); } -#define GET_PKTOPT_VAR(field, lenexpr) do { \ - if (pktopt && pktopt->field) { \ - INP_RUNLOCK(in6p); \ - optdata = malloc(sopt->sopt_valsize, M_TEMP, M_WAITOK); \ - malloc_optdata = true; \ - INP_RLOCK(in6p); \ - if (in6p->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) { \ - INP_RUNLOCK(in6p); \ - free(optdata, M_TEMP); \ - return (ECONNRESET); \ - } \ - pktopt = in6p->in6p_outputopts; \ - if (pktopt && pktopt->field) { \ - optdatalen = min(lenexpr, sopt->sopt_valsize); \ - bcopy(&pktopt->field, optdata, optdatalen); \ - } else { \ - free(optdata, M_TEMP); \ - optdata = NULL; \ - malloc_optdata = false; \ - } \ - } \ -} while(0) +static int +get_pktopt_var_len(struct ip6_pktopts *pktopt, size_t field_offset, size_t len_offset, bool ext_hdr) { + void *field_ptr; + void *len_ptr; + if (!pktopt) + return 0; + field_ptr = (char *)pktopt + field_offset; + if (!field_ptr) + return 0; + len_ptr = (char *)field_ptr + len_offset; + if (!len_ptr) + return 0; + if (ext_hdr) + return (*(__typeof__(&((struct ip6_ext *)0)->ip6e_len))len_ptr + 1) << 3; + return *(__typeof__(&((struct sockaddr *)0)->sa_len))len_ptr; +} + +static int +get_pktopt_var(struct ip6_pktopts **pktopt, struct inpcb *in6p, size_t field_offset, size_t len_offset, bool ext_hdr, void **optdata, int *optdatalen) { + int prev_optdatalen = 0; + *optdatalen = get_pktopt_var_len(*pktopt, field_offset, len_offset, ext_hdr); + while (*optdatalen) { + INP_RUNLOCK(in6p); + *optdata = malloc(*optdatalen, M_TEMP, M_WAITOK); + INP_RLOCK(in6p); + if (in6p->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) { + INP_RUNLOCK(in6p); + free(*optdata, M_TEMP); + return (ECONNRESET); + } + *pktopt = in6p->in6p_outputopts; + prev_optdatalen = *optdatalen; + *optdatalen = get_pktopt_var_len(*pktopt, field_offset, len_offset, ext_hdr); + if (*optdatalen != prev_optdatalen) { + free(*optdata, M_TEMP); + *optdata = NULL; + } else { + bcopy((char *)*pktopt + field_offset, *optdata, *optdatalen); + return 0; + } + } + return ENOATTR; +} -#define GET_PKTOPT_EXT_HDR(field) GET_PKTOPT_VAR(field, \ - (((struct ip6_ext *)pktopt->field)->ip6e_len + 1) << 3) +#define GET_PKTOPT_EXT_HDR(field) do { \ + error = get_pktopt_var(&pktopt, in6p, \ + offsetof(struct ip6_pktopts, field), \ + offsetof(struct ip6_ext, ip6e_len), \ + true, &optdata, &optdatalen); \ + if (error != 0) \ + return error; \ + malloc_optdata = true; \ +} while(0) -#define GET_PKTOPT_SOCKADDR(field) GET_PKTOPT_VAR(field, \ - pktopt->field->sa_len) +#define GET_PKTOPT_SOCKADDR(field) do { \ + error = get_pktopt_var(&pktopt, in6p, \ + offsetof(struct ip6_pktopts, field), \ + offsetof(struct sockaddr, sa_len), \ + true, &optdata, &optdatalen); \ + if (error != 0) \ + return error; \ + malloc_optdata = true; \ +} while(0) static int ip6_getpcbopt(struct inpcb *in6p, int optname, struct sockopt *sopt)