Fix a tcpdump segfault when attempting to print an invalid DECnet packet. DECnet packet printing code could cause a segfault on an impossibly large packet from a specifically crafted packet.
The segfault would occur in tcpdump.c:default_print() called by print-decnet.c:decnet_print(). Patch built using changes from upstream repo; Git commit: f61639179282f8e796966b21e45f79db317d7bdb https://github.com/the-tcpdump-group/tcpdump/commit/f61639179282f8e796 966b21e45f79db317d7bdb Changes adapted to older version of tcpdump in tree. Patch adds various length/size checks including TCHECK* throughout. Brings print-decnet.c length validation in-line with other print-*.c files. Additionally adds return values to internal print_* functions to indicate failure of new checks. Does not include the header no-copy optimization that was in the upstream patch. Feedback is greatly appreciated. Let me know if there are any questions, or if troublesome pcaps and backtrack are wanted. Tested with various pcaps. Behavior now matches newer upstream version on Linux. Index: print-decnet.c =================================================================== RCS file: /cvs/src/usr.sbin/tcpdump/print-decnet.c,v retrieving revision 1.14 diff -u -p -u -p -r1.14 print-decnet.c --- print-decnet.c 21 Aug 2015 02:07:32 -0000 1.14 +++ print-decnet.c 11 Oct 2015 10:40:22 -0000 @@ -44,13 +44,13 @@ struct rtentry; #include "addrtoname.h" /* Forwards */ -static void print_decnet_ctlmsg(const union routehdr *, u_int); +static int print_decnet_ctlmsg(const union routehdr *, u_int, u_int); static void print_t_info(int); -static void print_l1_routes(const char *, u_int); -static void print_l2_routes(const char *, u_int); +static int print_l1_routes(const char *, u_int); +static int print_l2_routes(const char *, u_int); static void print_i_info(int); -static void print_elist(const char *, u_int); -static void print_nsp(const u_char *, u_int); +static int print_elist(const char *, u_int); +static int print_nsp(const u_char *, u_int); static void print_reason(int); #ifdef PRINT_NSPDATA static void pdata(u_char *, int); @@ -76,12 +76,23 @@ decnet_print(register const u_char *ap, return; } + TCHECK2(*ap, sizeof(short)); pktlen = EXTRACT_LE_16BITS(ap); + if (pktlen < sizeof(struct shorthdr)) { + (void)printf("[|decnet]"); + return; + } + if (pktlen > length) { + (void)printf("[|decnet]"); + return; + } + length = pktlen; rhlen = min(length, caplen); rhlen = min(rhlen, sizeof(*rhp)); memcpy((char *)rhp, (char *)&(ap[sizeof(short)]), rhlen); + TCHECK(rhp->rh_short.sh_flags); mflags = EXTRACT_LE_8BITS(rhp->rh_short.sh_flags); if (mflags & RMF_PAD) { @@ -89,6 +100,11 @@ decnet_print(register const u_char *ap, u_int padlen = mflags & RMF_PADMASK; if (vflag) (void) printf("[pad:%d] ", padlen); + if (length < padlen + 2) { + (void)printf("[|decnet]"); + return; + } + TCHECK2(ap[sizeof(short)], padlen); ap += padlen; length -= padlen; caplen -= padlen; @@ -100,38 +116,43 @@ decnet_print(register const u_char *ap, if (mflags & RMF_FVER) { (void) printf("future-version-decnet"); - default_print(ap, length); + default_print(ap, min(length, caplen)); return; } /* is it a control message? */ if (mflags & RMF_CTLMSG) { - print_decnet_ctlmsg(rhp, min(length, caplen)); + if(!print_decnet_ctlmsg(rhp, length, caplen)) + goto trunc; return; } switch (mflags & RMF_MASK) { case RMF_LONG: + if (length < sizeof(struct longhdr)) { + (void)printf("[|decnet]"); + return; + } + TCHECK(rhp->rh_long); dst = EXTRACT_LE_16BITS(rhp->rh_long.lg_dst.dne_remote.dne_nodeaddr); src = EXTRACT_LE_16BITS(rhp->rh_long.lg_src.dne_remote.dne_nodeaddr); hops = EXTRACT_LE_8BITS(rhp->rh_long.lg_visits); nspp = &(ap[sizeof(short) + sizeof(struct longhdr)]); - nsplen = min((length - sizeof(struct longhdr)), - (caplen - sizeof(struct longhdr))); + nsplen = length - sizeof(struct longhdr); break; case RMF_SHORT: + TCHECK(rhp->rh_short); dst = EXTRACT_LE_16BITS(rhp->rh_short.sh_dst); src = EXTRACT_LE_16BITS(rhp->rh_short.sh_src); hops = (EXTRACT_LE_8BITS(rhp->rh_short.sh_visits) & VIS_MASK)+1; nspp = &(ap[sizeof(short) + sizeof(struct shorthdr)]); - nsplen = min((length - sizeof(struct shorthdr)), - (caplen - sizeof(struct shorthdr))); + nsplen = length - sizeof(struct shorthdr); break; default: (void) printf("unknown message flags under mask"); - default_print((u_char *)ap, length); + default_print((u_char *)ap, min(length, caplen)); return; } @@ -147,11 +168,18 @@ decnet_print(register const u_char *ap, (void)printf("%d hops ", hops); } - print_nsp(nspp, nsplen); + if (!print_nsp(nspp, nsplen)) + goto trunc; + return; + +trunc: + (void)printf("[|decnet]"); + return; } -static void -print_decnet_ctlmsg(register const union routehdr *rhp, u_int length) +static int +print_decnet_ctlmsg(register const union routehdr *rhp, u_int length, + u_int caplen) { int mflags = EXTRACT_LE_8BITS(rhp->rh_short.sh_flags); register union controlmsg *cmp = (union controlmsg *)rhp; @@ -159,10 +187,14 @@ print_decnet_ctlmsg(register const union etheraddr srcea, rtea; int priority; char *rhpx = (char *)rhp; + int ret; switch (mflags & RMF_CTLMASK) { case RMF_INIT: (void)printf("init "); + if (length < sizeof(struct initmsg)) + goto trunc; + TCHECK(cmp->cm_init); src = EXTRACT_LE_16BITS(cmp->cm_init.in_src); info = EXTRACT_LE_8BITS(cmp->cm_init.in_info); blksize = EXTRACT_LE_16BITS(cmp->cm_init.in_blksize); @@ -175,35 +207,54 @@ print_decnet_ctlmsg(register const union "src %sblksize %d vers %d eco %d ueco %d hello %d", dnaddr_string(src), blksize, vers, eco, ueco, hello); + + ret = 1; break; case RMF_VER: (void)printf("verification "); + if (length < sizeof(struct verifmsg)) + goto trunc; + TCHECK(cmp->cm_ver); src = EXTRACT_LE_16BITS(cmp->cm_ver.ve_src); other = EXTRACT_LE_8BITS(cmp->cm_ver.ve_fcnval); (void)printf("src %s fcnval %o", dnaddr_string(src), other); + ret = 1; break; case RMF_TEST: (void)printf("test "); + if (length < sizeof(struct testmsg)) + goto trunc; + TCHECK(cmp->cm_test); src = EXTRACT_LE_16BITS(cmp->cm_test.te_src); other = EXTRACT_LE_8BITS(cmp->cm_test.te_data); (void)printf("src %s data %o", dnaddr_string(src), other); + ret = 1; break; case RMF_L1ROUT: (void)printf("lev-1-routing "); + if (length < sizeof(struct l1rout)) + goto trunc; + TCHECK(cmp->cm_l1rou); src = EXTRACT_LE_16BITS(cmp->cm_l1rou.r1_src); (void)printf("src %s ", dnaddr_string(src)); - print_l1_routes(&(rhpx[sizeof(struct l1rout)]), + ret = print_l1_routes(&(rhpx[sizeof(struct l1rout)]), length - sizeof(struct l1rout)); break; case RMF_L2ROUT: (void)printf("lev-2-routing "); + if (length < sizeof(struct l2rout)) + goto trunc; + TCHECK(cmp->cm_l2rout); src = EXTRACT_LE_16BITS(cmp->cm_l2rout.r2_src); (void)printf("src %s ", dnaddr_string(src)); - print_l2_routes(&(rhpx[sizeof(struct l2rout)]), + ret = print_l2_routes(&(rhpx[sizeof(struct l2rout)]), length - sizeof(struct l2rout)); break; case RMF_RHELLO: (void)printf("router-hello "); + if (length < sizeof(struct rhellomsg)) + goto trunc; + TCHECK(cmp->cm_rhello); vers = EXTRACT_LE_8BITS(cmp->cm_rhello.rh_vers); eco = EXTRACT_LE_8BITS(cmp->cm_rhello.rh_eco); ueco = EXTRACT_LE_8BITS(cmp->cm_rhello.rh_ueco); @@ -219,11 +270,14 @@ print_decnet_ctlmsg(register const union "vers %d eco %d ueco %d src %s blksize %d pri %d hello %d", vers, eco, ueco, dnaddr_string(src), blksize, priority, hello); - print_elist(&(rhpx[sizeof(struct rhellomsg)]), + ret = print_elist(&(rhpx[sizeof(struct rhellomsg)]), length - sizeof(struct rhellomsg)); break; case RMF_EHELLO: (void)printf("endnode-hello "); + if (length < sizeof(struct ehellomsg)) + goto trunc; + TCHECK(cmp->cm_ehello); vers = EXTRACT_LE_8BITS(cmp->cm_ehello.eh_vers); eco = EXTRACT_LE_8BITS(cmp->cm_ehello.eh_eco); ueco = EXTRACT_LE_8BITS(cmp->cm_ehello.eh_ueco); @@ -243,13 +297,19 @@ print_decnet_ctlmsg(register const union "vers %d eco %d ueco %d src %s blksize %d rtr %s hello %d data %o", vers, eco, ueco, dnaddr_string(src), blksize, dnaddr_string(dst), hello, other); + ret = 1; break; default: (void)printf("unknown control message"); - default_print((u_char *)rhp, length); + default_print((u_char *)rhp, min(length, caplen)); + ret = 1; break; } + return (ret); + +trunc: + return (0); } static void @@ -268,7 +328,7 @@ print_t_info(int info) (void)printf("blo "); } -static void +static int print_l1_routes(const char *rp, u_int len) { int count; @@ -277,9 +337,10 @@ print_l1_routes(const char *rp, u_int le /* The last short is a checksum */ while (len > (3 * sizeof(short))) { + TCHECK2(*rp, 3 * sizeof(short)); count = EXTRACT_LE_16BITS(rp); if (count > 1024) - return; /* seems to be bogus from here on */ + return (1); /* seems to be bogus from here on */ rp += sizeof(short); len -= sizeof(short); id = EXTRACT_LE_16BITS(rp); @@ -291,9 +352,13 @@ print_l1_routes(const char *rp, u_int le (void)printf("{ids %d-%d cost %d hops %d} ", id, id + count, RI_COST(info), RI_HOPS(info)); } + return (1); + +trunc: + return (0); } -static void +static int print_l2_routes(const char *rp, u_int len) { int count; @@ -302,9 +367,10 @@ print_l2_routes(const char *rp, u_int le /* The last short is a checksum */ while (len > (3 * sizeof(short))) { + TCHECK2(*rp, 3 * sizeof(short)); count = EXTRACT_LE_16BITS(rp); if (count > 1024) - return; /* seems to be bogus from here on */ + return (1); /* seems to be bogus from here on */ rp += sizeof(short); len -= sizeof(short); area = EXTRACT_LE_16BITS(rp); @@ -316,6 +382,10 @@ print_l2_routes(const char *rp, u_int le (void)printf("{areas %d-%d cost %d hops %d} ", area, area + count, RI_COST(info), RI_HOPS(info)); } + return (1); + +trunc: + return (0); } static void @@ -336,18 +406,22 @@ print_i_info(int info) (void)printf("blo "); } -static void +static int print_elist(const char *elp, u_int len) { /* Not enough examples available for me to debug this */ + return (1); } -static void +static int print_nsp(const u_char *nspp, u_int nsplen) { const struct nsphdr *nsphp = (struct nsphdr *)nspp; int dst, src, flags; + if (nsplen < sizeof(struct nsphdr)) + goto trunc; + TCHECK(*nsphp); flags = EXTRACT_LE_8BITS(nsphp->nh_flags); dst = EXTRACT_LE_16BITS(nsphp->nh_dst); src = EXTRACT_LE_16BITS(nsphp->nh_src); @@ -368,27 +442,39 @@ print_nsp(const u_char *nspp, u_int nspl #endif u_int data_off = sizeof(struct minseghdr); + if (nsplen < data_off) + goto trunc; + TCHECK(shp->sh_seq[0]); ack = EXTRACT_LE_16BITS(shp->sh_seq[0]); if (ack & SGQ_ACK) { /* acknum field */ if ((ack & SGQ_NAK) == SGQ_NAK) (void)printf("nak %d ", ack & SGQ_MASK); else (void)printf("ack %d ", ack & SGQ_MASK); - ack = EXTRACT_LE_16BITS(shp->sh_seq[1]); - data_off += sizeof(short); + data_off += sizeof(short); + if (nsplen < data_off) + goto trunc; + TCHECK(shp->sh_seq[1]); + ack = EXTRACT_LE_16BITS(shp->sh_seq[1]); if (ack & SGQ_OACK) { /* ackoth field */ if ((ack & SGQ_ONAK) == SGQ_ONAK) (void)printf("onak %d ", ack & SGQ_MASK); else (void)printf("oack %d ", ack & SGQ_MASK); - ack = EXTRACT_LE_16BITS(shp->sh_seq[2]); data_off += sizeof(short); + if (nsplen < data_off) + goto trunc; + TCHECK(shp->sh_seq[2]); + ack = EXTRACT_LE_16BITS(shp->sh_seq[2]); } } (void)printf("seg %d ", ack & SGQ_MASK); #ifdef PRINT_NSPDATA - dp = &(nspp[data_off]); - pdata(dp, 10); + if (nsplen > data_off) { + dp = &(nspp[data_off]); + TCHECK2(*dp, nsplen - data_off); + pdata(dp, nsplen - data_off); + } #endif } break; @@ -402,27 +488,39 @@ print_nsp(const u_char *nspp, u_int nspl #endif u_int data_off = sizeof(struct minseghdr); + if (nsplen < data_off) + goto trunc; + TCHECK(shp->sh_seq[0]); ack = EXTRACT_LE_16BITS(shp->sh_seq[0]); if (ack & SGQ_ACK) { /* acknum field */ if ((ack & SGQ_NAK) == SGQ_NAK) (void)printf("nak %d ", ack & SGQ_MASK); else (void)printf("ack %d ", ack & SGQ_MASK); - ack = EXTRACT_LE_16BITS(shp->sh_seq[1]); - data_off += sizeof(short); + data_off += sizeof(short); + if (nsplen < data_off) + goto trunc; + TCHECK(shp->sh_seq[1]); + ack = EXTRACT_LE_16BITS(shp->sh_seq[1]); if (ack & SGQ_OACK) { /* ackdat field */ if ((ack & SGQ_ONAK) == SGQ_ONAK) (void)printf("nakdat %d ", ack & SGQ_MASK); else (void)printf("ackdat %d ", ack & SGQ_MASK); - ack = EXTRACT_LE_16BITS(shp->sh_seq[2]); data_off += sizeof(short); + if (nsplen < data_off) + goto trunc; + TCHECK(shp->sh_seq[2]); + ack = EXTRACT_LE_16BITS(shp->sh_seq[2]); } } (void)printf("seg %d ", ack & SGQ_MASK); #ifdef PRINT_NSPDATA - dp = &(nspp[data_off]); - pdata(dp, 10); + if (nsplen > data_off) { + dp = &(nspp[data_off]); + TCHECK2(*dp, nsplen - data_off); + pdata(dp, nsplen - data_off); + } #endif } break; @@ -435,22 +533,28 @@ print_nsp(const u_char *nspp, u_int nspl int ack; int lsflags, fcval; + if (nsplen < sizeof(struct seghdr) + sizeof(struct lsmsg)) + goto trunc; + TCHECK(shp->sh_seq[0]); ack = EXTRACT_LE_16BITS(shp->sh_seq[0]); if (ack & SGQ_ACK) { /* acknum field */ if ((ack & SGQ_NAK) == SGQ_NAK) (void)printf("nak %d ", ack & SGQ_MASK); else (void)printf("ack %d ", ack & SGQ_MASK); + TCHECK(shp->sh_seq[1]); ack = EXTRACT_LE_16BITS(shp->sh_seq[1]); if (ack & SGQ_OACK) { /* ackdat field */ if ((ack & SGQ_ONAK) == SGQ_ONAK) (void)printf("nakdat %d ", ack & SGQ_MASK); else (void)printf("ackdat %d ", ack & SGQ_MASK); + TCHECK(shp->sh_seq[2]); ack = EXTRACT_LE_16BITS(shp->sh_seq[2]); } } (void)printf("seg %d ", ack & SGQ_MASK); + TCHECK(*lsmp); lsflags = EXTRACT_LE_8BITS(lsmp->ls_lsflags); fcval = EXTRACT_LE_8BITS(lsmp->ls_fcval); switch (lsflags & LSI_MASK) { @@ -492,6 +596,9 @@ print_nsp(const u_char *nspp, u_int nspl struct ackmsg *amp = (struct ackmsg *)nspp; int ack; + if (nsplen < sizeof(struct ackmsg)) + goto trunc; + TCHECK(*amp); ack = EXTRACT_LE_16BITS(amp->ak_acknum[0]); if (ack & SGQ_ACK) { /* acknum field */ if ((ack & SGQ_NAK) == SGQ_NAK) @@ -514,13 +621,17 @@ print_nsp(const u_char *nspp, u_int nspl struct ackmsg *amp = (struct ackmsg *)nspp; int ack; + if (nsplen < sizeof(struct ackmsg)) + goto trunc; + TCHECK(*amp); ack = EXTRACT_LE_16BITS(amp->ak_acknum[0]); if (ack & SGQ_ACK) { /* acknum field */ if ((ack & SGQ_NAK) == SGQ_NAK) (void)printf("nak %d ", ack & SGQ_MASK); else (void)printf("ack %d ", ack & SGQ_MASK); - ack = EXTRACT_LE_16BITS(amp->ak_acknum[1]); + TCHECK(amp->ak_acknum[1]); + ack = EXTRACT_LE_16BITS(amp->ak_acknum[1]); if (ack & SGQ_OACK) { /* ackdat field */ if ((ack & SGQ_ONAK) == SGQ_ONAK) (void)printf("nakdat %d ", ack & SGQ_MASK); @@ -554,6 +665,9 @@ print_nsp(const u_char *nspp, u_int nspl u_char *dp; #endif + if (nsplen < sizeof(struct cimsg)) + goto trunc; + TCHECK(*cimp); services = EXTRACT_LE_8BITS(cimp->ci_services); info = EXTRACT_LE_8BITS(cimp->ci_info); segsize = EXTRACT_LE_16BITS(cimp->ci_segsize); @@ -564,7 +678,7 @@ print_nsp(const u_char *nspp, u_int nspl case COS_SEGMENT: (void)printf("seg "); break; - case COS_MESSAGE: + case COS_MESSAGE: (void)printf("msg "); break; case COS_CRYPTSER: @@ -573,7 +687,7 @@ print_nsp(const u_char *nspp, u_int nspl } switch (info & COI_MASK) { case COI_32: - (void)printf("ver 3.2 "); + (void)printf("ver 3.2 "); break; case COI_31: (void)printf("ver 3.1 "); @@ -587,8 +701,11 @@ print_nsp(const u_char *nspp, u_int nspl } (void)printf("segsize %d ", segsize); #ifdef PRINT_NSPDATA - dp = &(nspp[sizeof(struct cimsg)]); - pdata(dp, nsplen - sizeof(struct cimsg)); + if (nsplen > sizeof(struct cimsg)) { + dp = &(nspp[sizeof(struct cimsg)]); + TCHECK2(*dp, nsplen - sizeof(struct cimsg)); + pdata(dp, nsplen - sizeof(struct cimsg)); + } #endif } break; @@ -602,6 +719,9 @@ print_nsp(const u_char *nspp, u_int nspl u_char *dp; #endif + if (nsplen < sizeof(struct ccmsg)) + goto trunc; + TCHECK(*ccmp); services = EXTRACT_LE_8BITS(ccmp->cc_services); info = EXTRACT_LE_8BITS(ccmp->cc_info); segsize = EXTRACT_LE_16BITS(ccmp->cc_segsize); @@ -638,8 +758,10 @@ print_nsp(const u_char *nspp, u_int nspl if (optlen) { (void)printf("optlen %d ", optlen); #ifdef PRINT_NSPDATA - optlen = min(optlen, nsplen - sizeof(struct ccmsg)); + if (optlen > nsplen - sizeof(struct ccmsg)) + goto trunc; dp = &(nspp[sizeof(struct ccmsg)]); + TCHECK2(*dp, optlen); pdata(dp, optlen); #endif } @@ -655,6 +777,9 @@ print_nsp(const u_char *nspp, u_int nspl u_char *dp; #endif + if (nsplen < sizeof(struct dimsg)) + goto trunc; + TCHECK(*dimp); reason = EXTRACT_LE_16BITS(dimp->di_reason); optlen = EXTRACT_LE_8BITS(dimp->di_optlen); @@ -662,8 +787,10 @@ print_nsp(const u_char *nspp, u_int nspl if (optlen) { (void)printf("optlen %d ", optlen); #ifdef PRINT_NSPDATA - optlen = min(optlen, nsplen - sizeof(struct dimsg)); + if (optlen > nsplen - sizeof(struct dimsg)) + goto trunc; dp = &(nspp[sizeof(struct dimsg)]); + TCHECK2(*dp, optlen); pdata(dp, optlen); #endif } @@ -675,6 +802,7 @@ print_nsp(const u_char *nspp, u_int nspl struct dcmsg *dcmp = (struct dcmsg *)nspp; int reason; + TCHECK(*dcmp); reason = EXTRACT_LE_16BITS(dcmp->dc_reason); print_reason(reason); @@ -689,6 +817,10 @@ print_nsp(const u_char *nspp, u_int nspl (void)printf("reserved-type? %x %d > %d", flags, src, dst); break; } + return (1); + +trunc: + return (0); } static struct tok reason2str[] = {
