diff --git a/libnetutil/TCPHeader.cc b/libnetutil/TCPHeader.cc index c1946b345..009ebd93d 100644 --- a/libnetutil/TCPHeader.cc +++ b/libnetutil/TCPHeader.cc @@ -813,6 +813,27 @@ const u8 *TCPHeader::getOptions(size_t *optslen) const { return this->h.options; } /* End of getOptions() */ +struct tcpopt_atindex_ctx { + unsigned int index; + unsigned int found; + nping_tcp_opt_t result; + tcpopt_atindex_ctx() : index(0), found(0) { + memset(&result, 0, sizeof(result)); + } +}; + +static bool tcpopt_atindex(u8 op, u8 oplen, const u8 *data, void *ctx) +{ + tcpopt_atindex_ctx *args = static_cast(ctx); + if (args->index == args->found) { + args->result.type = op; + args->result.len = oplen; + args->result.value = data + 2; + return false; + } + args->found += 1; + return true; +} /* Returns the index-th option in the TCP header. On success it returns a * structure filled with option information. If there is no index-th option, @@ -824,61 +845,13 @@ const u8 *TCPHeader::getOptions(size_t *optslen) const { * would be set to zero and the "value" field should NOT be accessed, as it * will not contain reliable information. */ nping_tcp_opt_t TCPHeader::getOption(unsigned int index) const { - nping_tcp_opt_t *curr_opt=NULL; - u8 *curr_pnt=(u8 *)this->h.options; - int bytes_left=this->length - TCP_HEADER_LEN; - assert((this->length - TCP_HEADER_LEN) == this->tcpoptlen); - unsigned int optsfound=0; - nping_tcp_opt_t result; - memset(&result, 0, sizeof(nping_tcp_opt_t)); - - while(bytes_left>0){ - /* Use the opts structure as a template to access current option. It is - * OK to use it because we only access the first two elements. */ - curr_opt=(nping_tcp_opt_t *)curr_pnt; - - /* If we are right in the option that the caller wants, just return it */ - if(optsfound==index){ - result.type=curr_opt->type; - if(result.type==TCPOPT_EOL || result.type==TCPOPT_NOOP) - result.len=1; - else - result.len=curr_opt->len; - result.value=(u8 *)curr_pnt+2; - return result; - } - - /* Otherwise, we have to parse it, so we can skip it and access the next - * option */ - switch(curr_opt->type){ - - /* EOL or NOOP - +-+-+-+-+-+-+-+-+ - | X | - +-+-+-+-+-+-+-+-+ */ - case TCPOPT_EOL: - goto out; - - case TCPOPT_NOOP: - curr_pnt++; /* Skip one octet */ - bytes_left--; - break; - - /* TLV encoded option */ - default: - /* If we don't have as many octets as the option advertises, the - * option is bogus. Return failure. */ - if(bytes_leftlen) - return result; - curr_pnt+=curr_opt->len; - bytes_left-=curr_opt->len; - break; - } - optsfound++; + TCPOptions opts; + tcpopt_atindex_ctx ctx; + if (opts.fromTCPHeader(*this)) { + ctx.index = index; + opts.foreachOpt(tcpopt_atindex, &ctx); } - -out: - return result; + return ctx.result; } @@ -935,3 +908,71 @@ const char *TCPHeader::optcode2str(u8 optcode){ } /* End of optcode2str() */ +bool TCPOptions::fromTCPPacket(const u8 *tcppkt, int tcplen) +{ + tcpopts = NULL; + optslen = 0; + if (tcplen < TCP_HEADER_LEN) + return false; + + u8 data_offset = tcppkt[12] >> 4; + if (data_offset < 5) + return false; + + tcpopts = tcppkt + TCP_HEADER_LEN; + optslen = MIN(4 * data_offset, tcplen) - TCP_HEADER_LEN; + if (optslen == 0) + tcpopts = NULL; + return true; +} + +bool TCPOptions::fromBuffer(const u8 *tcpoptions, int optionslen) +{ + if (!tcpoptions || optionslen <= 0) + return false; + tcpopts = tcpoptions; + optslen = optionslen; + return true; +} + +bool TCPOptions::fromTCPHeader(const TCPHeader &T) +{ + size_t len = 0; + tcpopts = T.getOptions(&len); + if (len > INT_MAX) { + tcpopts = NULL; + return false; + } + optslen = len; + return tcpopts != NULL; +} + +bool TCPOptions::foreachOpt(tcpopt_callback cb, void *ctx) const +{ + const u8 *p = tcpopts; + int len = optslen; + while (len > 0) { + int op = p[0]; + int oplen = 1; + switch (op) { + case 0: /* TCPOPT_EOL */ + case 1: /* TCPOPT_NOP */ + break; + default: /* TLV option */ + if (len < 2) + return false; + oplen = p[1]; + if (oplen < 2) + return false; /* No infinite loops, please */ + if (oplen > len) + return false; /* Not enough space */ + break; + } + if (!cb(op, oplen, p, ctx)) + return true; + len -= oplen; + p += oplen; + } + return len == 0; +} + diff --git a/libnetutil/TCPHeader.h b/libnetutil/TCPHeader.h index 54002dd71..1890482d4 100644 --- a/libnetutil/TCPHeader.h +++ b/libnetutil/TCPHeader.h @@ -123,7 +123,7 @@ struct nping_tcp_opt { u8 type; /* Option type code. */ u8 len; /* Option length. */ - u8 *value; /* Option value */ + const u8 *value; /* Option value */ }__attribute__((__packed__)); typedef struct nping_tcp_opt nping_tcp_opt_t; @@ -257,4 +257,26 @@ class TCPHeader : public TransportLayerElement { }; /* End of class TCPHeader */ +/* This callback will be called for each option in the buffer. + * Return true to continue processing options. + * Return false to stop processing options. */ +typedef bool (*tcpopt_callback)(u8 op, u8 oplen, const u8 *data, void *ctx); + +class TCPOptions { +public: + /* Note: this class parses in-place and does not make a copy of the data. The + * data pointed to by tcppkt must remain allocated as long as the TCPOptions + * object is in use. */ + TCPOptions() : tcpopts(NULL), optslen(0) {} + bool fromTCPPacket(const u8 *tcppkt, int tcplen); + bool fromBuffer(const u8 *tcpoptions, int optionslen); + bool fromTCPHeader(const TCPHeader &T); + + /* Returns true if no errors were encountered, even if the callback returns false. */ + bool foreachOpt(tcpopt_callback cb, void *ctx) const; +private: + const u8 *tcpopts; + int optslen; +}; + #endif /* __TCPHEADER_H__ */ diff --git a/libnetutil/netutil.cc b/libnetutil/netutil.cc index bd41d6413..424eaef99 100644 --- a/libnetutil/netutil.cc +++ b/libnetutil/netutil.cc @@ -120,6 +120,7 @@ typedef unsigned __int8 u_int8_t; #endif #include "netutil.h" +#include "TCPHeader.h" #if HAVE_NET_IF_H #ifndef NET_IF_H /* This guarding is needed for at least some versions of OpenBSD */ @@ -1392,6 +1393,42 @@ const char *proto2ascii_uppercase(u8 proto) { return proto2ascii_case(proto, 1); } +struct tcpopt_info_ctx { + char *p; + char *end; + bool valid; + tcpopt_info_ctx() : p(NULL), end(NULL), valid(true) {} + bool check_length(size_t len) const { + return (end - p) >= len; + } + void put_str(const char *str) { + if (p >= end) + return; + int r = Snprintf(p, end - p, "%s", str); + if (r < 0) + p = end; + else { + p += r; + if (p > end) + p = end; + } + } + void fmt_num(const char *fmt, unsigned int n) { + if (p >= end) + return; + int r = Snprintf(p, end - p, fmt, n); + if (r < 0) + p = end; + else { + p += r; + if (p > end) + p = end; + } + } +}; + +static bool tcpopt_info(u8 opcode, u8 len, const u8 *data, void *ctx); + /* Get an ASCII information about a tcp option which is pointed by optp, with a length of len. The result is stored in the result buffer. The result may look like " 0); + memset(result, 0, bufsize); - p = result; - *p = '\0'; - q = optp; - ch = '<'; + TCPOptions opts; + if (bufsize < 2 || !opts.fromBuffer(optp, len)) + return; + tcpopt_info_ctx ctx; + ctx.p = result; + ctx.end = result + bufsize - 1; + ctx.put_str("<"); - while (len > 0 && bufsize > 2) { - Snprintf(p, bufsize, "%c", ch); - bufsize--; - p++; - opcode = *q++; - if (!opcode) { /* End of List */ - - Snprintf(p, bufsize, "eol"); - bufsize -= strlen(p); - p += strlen(p); - - len--; - - } else if (opcode == 1) { /* No Op */ - Snprintf(p, bufsize, "nop"); - bufsize -= strlen(p); - p += strlen(p); - - len--; - } else if (opcode == 2) { /* MSS */ - if (len < 4) - break; /* MSS has 4 bytes */ - - q++; - memcpy(&tmpshort, q, 2); - - Snprintf(p, bufsize, "mss %hu", (unsigned short) ntohs(tmpshort)); - bufsize -= strlen(p); - p += strlen(p); - - q += 2; - len -= 4; - } else if (opcode == 3) { /* Window Scale */ - if (len < 3) - break; /* Window Scale option has 3 bytes */ - - q++; - - Snprintf(p, bufsize, "wscale %u", *q); - bufsize -= strlen(p); - p += strlen(p); - - q++; - len -= 3; - } else if (opcode == 4) { /* SACK permitted */ - if (len < 2) - break; /* SACK permitted option has 2 bytes */ - - Snprintf(p, bufsize, "sackOK"); - bufsize -= strlen(p); - p += strlen(p); - - q++; - len -= 2; - } else if (opcode == 5) { /* SACK */ - unsigned sackoptlen = *q; - if ((unsigned) len < sackoptlen) - break; - - /* This would break parsing, so it's best to just give up */ - if (sackoptlen < 2) - break; - - q++; - - if ((sackoptlen - 2) == 0 || ((sackoptlen - 2) % 8 != 0)) { - Snprintf(p, bufsize, "malformed sack"); - bufsize -= strlen(p); - p += strlen(p); - } else { - Snprintf(p, bufsize, "sack %d ", (sackoptlen - 2) / 8); - bufsize -= strlen(p); - p += strlen(p); - for (i = 0; i < sackoptlen - 2; i += 8) { - memcpy(&tmpword1, q + i, 4); - memcpy(&tmpword2, q + i + 4, 4); - Snprintf(p, bufsize, "{%u:%u}", tmpword1, tmpword2); - bufsize -= strlen(p); - p += strlen(p); - } - } - - q += sackoptlen - 2; - len -= sackoptlen; - } else if (opcode == 8) { /* Timestamp */ - if (len < 10) - break; /* Timestamp option has 10 bytes */ - - q++; - memcpy(&tmpword1, q, 4); - memcpy(&tmpword2, q + 4, 4); - - Snprintf(p, bufsize, "timestamp %lu %lu", (unsigned long) ntohl(tmpword1), - (unsigned long) ntohl(tmpword2)); - bufsize -= strlen(p); - p += strlen(p); - - q += 8; - len -= 10; - } - - ch = ','; - } - - if (len > 0) { - *result = '\0'; + if (!opts.foreachOpt(tcpopt_info, &ctx) || !ctx.valid) { + Snprintf(result, bufsize, ""); return; } - Snprintf(p, bufsize, ">"); + char *q = ctx.p - 1; + if (*q != ',') + q++; + *q++ = '>'; + *q++ = '\0'; + return; } +static bool tcpopt_info(u8 opcode, u8 len, const u8 *data, void *ctx) +{ + tcpopt_info_ctx *args = static_cast(ctx); + if (!args->check_length(4)) + return false; + const u8 *q = data + 2; + + switch (opcode) { + case 0: /* End of List */ + args->put_str("eol,"); + break; + case 1: /* No Op */ + args->put_str("nop,"); + break; + case 2: /* MSS */ + if (len < 4) { + args->valid = false; + break; /* MSS has 4 bytes */ + } + args->fmt_num("mss %u,", (q[0] << 8) + q[1]); + case 3: /* Window Scale */ + if (len < 3) { + args->valid = false; + break; /* Window Scale option has 3 bytes */ + } + args->fmt_num("wscale %u,", q[0]); + break; + case 4: /* SACK permitted */ + if (len < 2) { + args->valid = false; + break; /* SACK permitted option has 2 bytes */ + } + args->put_str("sackOK,"); + break; + case 5: /* SACK */ + if (len < 2) { + args->valid = false; + break; + } + if ((len - 2) == 0 || ((len - 2) % 8 != 0)) { + args->put_str("malformed sack,"); + } else { + args->fmt_num("sack %u ", (len - 2) / 8); + for (int i = 0; i < len - 2; i += 8) { + args->fmt_num("{%u:", (q[i] << 24) + (q[i+1] << 16) + (q[i+2] << 8) + q[i+3]); + args->fmt_num("%u}", (q[i+4] << 24) + (q[i+5] << 16) + (q[i+6] << 8) + q[i+7]); + } + args->put_str(","); + } + break; + case 8: /* Timestamp */ + if (len < 10) { + args->valid = false; + break; /* Timestamp option has 10 bytes */ + } + args->fmt_num("timestamp %u ", (q[0] << 24) + (q[1] << 16) + (q[2] << 8) + q[3]); + args->fmt_num("%u,", (q[4] << 24) + (q[5] << 16) + (q[6] << 8) + q[7]); + break; + default: + break; + } + return args->valid; +} /* A trivial function used with qsort to sort the routes by netmask and metric */ diff --git a/osscan2.cc b/osscan2.cc index 864e1605c..a5ef75da2 100644 --- a/osscan2.cc +++ b/osscan2.cc @@ -2684,7 +2684,7 @@ bool HostOsScan::processTOpsResp(HostOsScanStats *hss, const u8 *tcp, int tcplen if (hss->FP_TOps || hss->TOps_AVs[replyNo]) return false; - int opsParseResult = get_tcpopt_string(tcp, tcplen, this->tcpMss, ops_buf, sizeof(ops_buf)); + int opsParseResult = get_tcpopt_string(tcp, tcplen, ops_buf, sizeof(ops_buf)); if (opsParseResult <= 0) { if (opsParseResult < 0 && o.debugging) @@ -2740,7 +2740,7 @@ bool HostOsScan::processTEcnResp(HostOsScanStats *hss, const struct ip *ip, cons test.setAVal("W", hss->target->FPR->cp_hex(ntohs(tcp->th_win))); /* Now for the TCP options ... */ - int opsParseResult = get_tcpopt_string(tcppkt, tcplen, this->tcpMss, ops_buf, sizeof(ops_buf)); + int opsParseResult = get_tcpopt_string(tcppkt, tcplen, ops_buf, sizeof(ops_buf)); if (opsParseResult <= 0) { if (opsParseResult < 0 && o.debugging) @@ -2880,7 +2880,7 @@ bool HostOsScan::processT1_7Resp(HostOsScanStats *hss, const struct ip *ip, cons char ops_buf[256]; /* Now for the TCP options ... */ - int opsParseResult = get_tcpopt_string(tcppkt, tcplen, this->tcpMss, ops_buf, sizeof(ops_buf)); + int opsParseResult = get_tcpopt_string(tcppkt, tcplen, ops_buf, sizeof(ops_buf)); if (opsParseResult <= 0) { if (opsParseResult < 0 && o.debugging) error("Option parse error for T%d response from %s.", replyNo, hss->target->targetipstr()); @@ -3136,86 +3136,103 @@ bool HostOsScan::processTIcmpResp(HostOsScanStats *hss, const struct ip *ip, con return true; } - -int HostOsScan::get_tcpopt_string(const u8 *tcp, int tcplen, int mss, char *result, int maxlen) const { +struct tcpopt_string_ctx { char *p; - const u8 *q; - u16 tmpshort; - u32 tmpword; - int length; - int opcode; + char *end; + bool valid; + tcpopt_string_ctx() : p(NULL), end(NULL), valid(true) {} + bool check_length(size_t len) const { + return (end - p) >= len; + } + void put(char c) { + assert(end > p); + *p++ = c; + } + void put_hex(unsigned int u) { + int w = sprintf(p, "%X", u); + p += w; + } +}; - p = result; - struct tcp_hdr hdr; - memcpy(&hdr, tcp, sizeof(hdr)); - length = MIN(hdr.th_off * 4, tcplen) - sizeof(struct tcp_hdr); - q = tcp + sizeof(struct tcp_hdr); +static bool tcpopt_tostring(u8 op, u8 oplen, const u8 *data, void *ctx) +{ + tcpopt_string_ctx *args = static_cast(ctx); + + if (!args->check_length(1)) + return false; + + const u8 *q = data + 2; + + switch (op) { + case 0: /* End of List */ + args->put('L'); + break; + case 1: /* No Op */ + args->put('N'); + break; + case 2: /* MSS */ + if (oplen < 4) { + args->valid = false; + break; /* MSS has 4 bytes */ + } + args->put('M'); + if (!args->check_length(4)) + return false; + args->put_hex((q[0] << 8) + q[1]); + break; + case 3:/* Window Scale */ + if (oplen < 3) { + args->valid = false; + break; /* Window Scale option has 3 bytes */ + } + args->put('W'); + if (!args->check_length(2)) + return false; + args->put_hex(q[0]); + break; + case 4:/* SACK permitted */ + if (oplen < 2) { + args->valid = false; + break; /* SACK permitted option has 2 bytes */ + } + args->put('S'); + break; + case 8: /* Timestamp */ + if (oplen < 10) { + args->valid = false; + break; /* Timestamp option has 10 bytes */ + } + args->put('T'); + if (!args->check_length(2)) + return false; + args->put((q[0] || q[1] || q[2] || q[3]) ? '1' : '0'); + args->put((q[4] || q[5] || q[6] || q[7]) ? '1' : '0'); + break; + default: + break; + } + return args->valid; +} + +int HostOsScan::get_tcpopt_string(const u8 *tcp, int tcplen, char *result, int maxlen) const { + assert(tcp); + assert(result); + assert(maxlen > 0); + memset(result, 0, maxlen); /* * Example parsed result: M5B4ST11NW2 * MSS, Sack Permitted, Timestamp with both value not zero, Nop, WScale with value 2 */ - /* Be aware of the max increment value for p in parsing, - * now is 5 = strlen("Mxxxx") <-> MSS Option - */ - while (length > 0 && (p - result) < (maxlen - 5)) { - opcode = *q++; - if (!opcode) { /* End of List */ - *p++ = 'L'; - length--; - } else if (opcode == 1) { /* No Op */ - *p++ = 'N'; - length--; - } else if (opcode == 2) { /* MSS */ - if (length < 4) - break; /* MSS has 4 bytes */ - *p++ = 'M'; - q++; - memcpy(&tmpshort, q, 2); - /* if (ntohs(tmpshort) == mss) */ - /* *p++ = 'E'; */ - sprintf(p, "%hX", ntohs(tmpshort)); - p += strlen(p); /* max movement of p is 4 (0xFFFF) */ - q += 2; - length -= 4; - } else if (opcode == 3) { /* Window Scale */ - if (length < 3) - break; /* Window Scale option has 3 bytes */ - *p++ = 'W'; - q++; - snprintf(p, length, "%hhX", *((u8*)q)); - p += strlen(p); /* max movement of p is 2 (max WScale value is 0xFF) */ - q++; - length -= 3; - } else if (opcode == 4) { /* SACK permitted */ - if (length < 2) - break; /* SACK permitted option has 2 bytes */ - *p++ = 'S'; - q++; - length -= 2; - } else if (opcode == 8) { /* Timestamp */ - if (length < 10) - break; /* Timestamp option has 10 bytes */ - *p++ = 'T'; - q++; - memcpy(&tmpword, q, 4); - if (tmpword) - *p++ = '1'; - else - *p++ = '0'; - q += 4; - memcpy(&tmpword, q, 4); - if (tmpword) - *p++ = '1'; - else - *p++ = '0'; - q += 4; - length -= 10; - } - } + TCPOptions opts; + if (!opts.fromTCPPacket(tcp, tcplen)) + return -1; + tcpopt_string_ctx ctx; + ctx.p = result; + ctx.end = result + maxlen - 1; - if (length > 0) { + if (!opts.foreachOpt(tcpopt_tostring, &ctx) || !ctx.valid) { /* We could reach here for one of the two reasons: * 1. At least one option is not correct. (Eg. Should have 4 bytes but only has 3 bytes left). * 2. The option string is too long. @@ -3223,9 +3240,7 @@ int HostOsScan::get_tcpopt_string(const u8 *tcp, int tcplen, int mss, char *resu *result = '\0'; return -1; } - - *p = '\0'; - return p - result; + return ctx.p - result; } diff --git a/osscan2.h b/osscan2.h index 341a07625..bfb19bb35 100644 --- a/osscan2.h +++ b/osscan2.h @@ -429,7 +429,7 @@ private: void makeTOpsFP(HostOsScanStats *hss); void makeTWinFP(HostOsScanStats *hss); - int get_tcpopt_string(const u8 *tcp, int tcplen, int mss, char *result, int maxlen) const; + int get_tcpopt_string(const u8 *tcp, int tcplen, char *result, int maxlen) const; int rawsd; /* Raw socket descriptor */ netutil_eth_t *ethsd; /* Ethernet handle */ diff --git a/tcpip.cc b/tcpip.cc index 1fcdb1a3b..74159bed2 100644 --- a/tcpip.cc +++ b/tcpip.cc @@ -73,6 +73,7 @@ #include "utils.h" #include "nmap_error.h" #include "libnetutil/netutil.h" +#include "libnetutil/TCPHeader.h" #include "struct_ip.h" @@ -1625,6 +1626,33 @@ int recvtime(int sd, char *buf, int len, int seconds, int *timedout) { return -1; } +struct getTS_args { + u32 *timestamp; + u32 *echots; + int found; + getTS_args() : timestamp(NULL), echots(NULL), found(0) {} +}; + +static bool tcpopt_ts_cb(u8 op, u8 oplen, const u8 *data, void *ctx) +{ + if (op == 8 /* TCPOPT_TIMESTAMP */ && oplen == 10) { + getTS_args *args = static_cast(ctx); + const u8 *p = data + 2; + /* Legitimate ts option */ + if (args->timestamp) { + *args->timestamp = (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; + } + if (args->echots) { + *args->echots = (p[4] << 24) + (p[5] << 16) + (p[6] << 8) + p[7]; + } + // done! + args->found = 1; + return false; + } + // Keep looking + return true; +} + /* Examines the given tcp packet and obtains the TCP timestamp option information if available. Note that the CALLER must ensure that "tcp" contains a valid header (in particular the th_off must be the @@ -1636,52 +1664,19 @@ int recvtime(int sd, char *buf, int len, int seconds, int *timedout) { correct way to check for errors is to look at the return value since a zero ts or echots could possibly be valid. */ int gettcpopt_ts(const u8 *tcppkt, int tcplen, u32 *timestamp, u32 *echots) { - - const u8 *p; - int len = 0; - int op; - int oplen; - struct tcp_hdr tcp; - assert(tcplen >= sizeof(tcp)); - memcpy(&tcp, tcppkt, sizeof(tcp)); - - /* first we find where the tcp options start ... */ - p = tcppkt + 20; - len = MIN(4 * tcp.th_off, tcplen) - 20; - while (len > 0 && *p != 0 /* TCPOPT_EOL */ ) { - op = *p++; - if (op == 0 /* TCPOPT_EOL */ ) - break; - if (op == 1 /* TCPOPT_NOP */ ) { - len--; - continue; - } - oplen = *p++; - if (oplen < 2) - break; /* No infinite loops, please */ - if (oplen > len) - break; /* Not enough space */ - if (op == 8 /* TCPOPT_TIMESTAMP */ && oplen == 10) { - /* Legitimate ts option */ - if (timestamp) { - memcpy((char *) timestamp, p, 4); - *timestamp = ntohl(*timestamp); - } - p += 4; - if (echots) { - memcpy((char *) echots, p, 4); - *echots = ntohl(*echots); - } - return 1; - } - len -= oplen; - p += oplen - 2; - } - - /* Didn't find anything */ if (timestamp) *timestamp = 0; if (echots) *echots = 0; - return 0; + + TCPOptions opts; + if (!opts.fromTCPPacket(tcppkt, tcplen)) + return 0; + + getTS_args args; + args.timestamp = timestamp; + args.echots = echots; + + opts.foreachOpt(tcpopt_ts_cb, &args); + return args.found; }