diff options
-rw-r--r-- | epan/dissectors/packet-tcp.c | 30 | ||||
-rw-r--r-- | epan/dissectors/packet-udp.c | 55 | ||||
-rw-r--r-- | epan/in_cksum.c | 31 | ||||
-rw-r--r-- | epan/in_cksum.h | 2 | ||||
-rw-r--r-- | packaging/debian/libwireshark0.symbols | 1 | ||||
-rw-r--r-- | test/captures/http-ooo-fuzzed.pcapng | bin | 0 -> 1584 bytes | |||
-rw-r--r-- | test/suite_clopts.py | 25 |
7 files changed, 110 insertions, 34 deletions
diff --git a/epan/dissectors/packet-tcp.c b/epan/dissectors/packet-tcp.c index 0a876853a8..18e00f7b80 100644 --- a/epan/dissectors/packet-tcp.c +++ b/epan/dissectors/packet-tcp.c @@ -472,6 +472,7 @@ static expert_field ei_tcp_connection_rst = EI_INIT; static expert_field ei_tcp_connection_fin_active = EI_INIT; static expert_field ei_tcp_connection_fin_passive = EI_INIT; static expert_field ei_tcp_checksum_ffff = EI_INIT; +static expert_field ei_tcp_checksum_partial = EI_INIT; static expert_field ei_tcp_checksum_bad = EI_INIT; static expert_field ei_tcp_urgent_pointer_non_zero = EI_INIT; static expert_field ei_tcp_suboption_malformed = EI_INIT; @@ -8392,8 +8393,12 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) DISSECTOR_ASSERT_NOT_REACHED(); break; } + /* See discussion in packet-udp.c of partial checksums used in + * checksum offloading in Linux and Windows (and possibly others.) + */ + uint16_t partial_cksum; SET_CKSUM_VEC_TVB(cksum_vec[3], tvb, offset, reported_len); - computed_cksum = in_cksum(cksum_vec, 4); + computed_cksum = in_cksum_ret_partial(cksum_vec, 4, &partial_cksum); if (computed_cksum == 0 && th_sum == 0xffff) { item = proto_tree_add_uint_format_value(tcp_tree, hf_tcp_checksum, tvb, offset + 16, 2, th_sum, @@ -8415,11 +8420,23 @@ dissect_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) desegment_ok = TRUE; } else { proto_item* calc_item; - item = proto_tree_add_checksum(tcp_tree, tvb, offset+16, hf_tcp_checksum, hf_tcp_checksum_status, &ei_tcp_checksum_bad, pinfo, computed_cksum, - ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY|PROTO_CHECKSUM_IN_CKSUM); - - calc_item = proto_tree_add_uint(tcp_tree, hf_tcp_checksum_calculated, tvb, - offset + 16, 2, in_cksum_shouldbe(th_sum, computed_cksum)); + uint16_t shouldbe_cksum = in_cksum_shouldbe(th_sum, computed_cksum); + if (th_sum == g_htons(partial_cksum)) { + /* Don't use PROTO_CHECKSUM_IN_CKSUM because we expect the value + * to match what we pass in. */ + item = proto_tree_add_checksum(tcp_tree, tvb, offset+16, hf_tcp_checksum, hf_tcp_checksum_status, &ei_tcp_checksum_bad, pinfo, g_htons(partial_cksum), + ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY); + proto_item_append_text(item, " (matches partial checksum, not 0x%4x, likely caused by \"TCP checksum offload\")", shouldbe_cksum); + expert_add_info(pinfo, item, &ei_tcp_checksum_partial); + computed_cksum = 0; + /* XXX Add a new status, e.g. PROTO_CHECKSUM_E_PARTIAL? */ + } else { + item = proto_tree_add_checksum(tcp_tree, tvb, offset+16, hf_tcp_checksum, hf_tcp_checksum_status, &ei_tcp_checksum_bad, pinfo, computed_cksum, + ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY|PROTO_CHECKSUM_IN_CKSUM); + } + checksum_tree = proto_item_add_subtree(item, ett_tcp_checksum); + calc_item = proto_tree_add_uint(checksum_tree, hf_tcp_checksum_calculated, tvb, + offset + 16, 2, shouldbe_cksum); proto_item_set_generated(calc_item); /* Checksum is valid, so we're willing to desegment it. */ @@ -9718,6 +9735,7 @@ proto_register_tcp(void) */ { &ei_tcp_connection_rst, { "tcp.connection.rst", PI_SEQUENCE, PI_WARN, "Connection reset (RST)", EXPFILL }}, { &ei_tcp_checksum_ffff, { "tcp.checksum.ffff", PI_CHECKSUM, PI_WARN, "TCP Checksum 0xffff instead of 0x0000 (see RFC 1624)", EXPFILL }}, + { &ei_tcp_checksum_partial, { "tcp.checksum.partial", PI_CHECKSUM, PI_NOTE, "Partial (pseudo header) checksum (likely caused by \"TCP checksum offload\")", EXPFILL }}, { &ei_tcp_checksum_bad, { "tcp.checksum_bad.expert", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }}, { &ei_tcp_urgent_pointer_non_zero, { "tcp.urgent_pointer.non_zero", PI_PROTOCOL, PI_NOTE, "The urgent pointer field is nonzero while the URG flag is not set", EXPFILL }}, { &ei_tcp_suboption_malformed, { "tcp.suboption_malformed", PI_MALFORMED, PI_ERROR, "suboption would go past end of option", EXPFILL }}, diff --git a/epan/dissectors/packet-udp.c b/epan/dissectors/packet-udp.c index 180c479899..ba5b5d2170 100644 --- a/epan/dissectors/packet-udp.c +++ b/epan/dissectors/packet-udp.c @@ -81,6 +81,7 @@ static expert_field ei_udp_possible_traceroute = EI_INIT; static expert_field ei_udp_length_bad = EI_INIT; static expert_field ei_udplite_checksum_coverage_bad = EI_INIT; static expert_field ei_udp_checksum_zero = EI_INIT; +static expert_field ei_udp_checksum_partial = EI_INIT; static expert_field ei_udp_checksum_bad = EI_INIT; static expert_field ei_udp_length_bad_zero = EI_INIT; @@ -1128,13 +1129,6 @@ dissect(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 ip_proto) DISSECTOR_ASSERT_NOT_REACHED(); break; } - SET_CKSUM_VEC_TVB(cksum_vec[3], tvb, offset, udph->uh_sum_cov); - computed_cksum = in_cksum(&cksum_vec[0], 4); - - item = proto_tree_add_checksum(udp_tree, tvb, offset + 6, hf_udp_checksum, hf_udp_checksum_status, &ei_udp_checksum_bad, - pinfo, computed_cksum, ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY|PROTO_CHECKSUM_IN_CKSUM); - checksum_tree = proto_item_add_subtree(item, ett_udp_checksum); - /* * in_cksum() should never return 0xFFFF here, because, to quote * RFC 1624 section 3 "Discussion": @@ -1162,15 +1156,49 @@ dissect(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 ip_proto) * all zero, so the sum won't be 0 (+0), and thus the negation * won't be -0, i.e. won't be 0xFFFF. */ + /* + * Linux and Windows, at least, when performing Local Checksum + * Offload (during Generic Segmentation Offload or at other + * times), place the one's complement sum of the pseudo header + * in the checksum fields initially, which provides the necessary + * correction when a device (or driver) computes the one's + * complement checksum of each buffer in the skbuff. Since it is + * not inverted, the partial_cksum will never be 0x0000 by the + * same argument as above. + */ + uint16_t partial_cksum; + SET_CKSUM_VEC_TVB(cksum_vec[3], tvb, offset, udph->uh_sum_cov); + computed_cksum = in_cksum_ret_partial(&cksum_vec[0], 4, &partial_cksum); + uint16_t shouldbe_cksum = in_cksum_shouldbe(udph->uh_sum, computed_cksum); + if (computed_cksum != 0 && udph->uh_sum == g_htons(partial_cksum)) { + /* Don't use PROTO_CHECKSUM_IN_CKSUM because we expect the value + * to match what we pass in. */ + item = proto_tree_add_checksum(udp_tree, tvb, offset + 6, hf_udp_checksum, hf_udp_checksum_status, &ei_udp_checksum_bad, + pinfo, g_htons(partial_cksum), ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY); + proto_item_append_text(item, " (matches partial checksum, not 0x%4x, likely caused by \"UDP checksum offload\")", shouldbe_cksum); + expert_add_info(pinfo, item, &ei_udp_checksum_partial); + computed_cksum = 0; + /* XXX: Find some way hint to QUIC (or other dissectors) that + * GSO is a possibility so that extra effort can be made to + * recover coalesced PDUs? E.g., by searching heuristically + * through the payload for the DCID bytes if they're non-zero? + * Add a new status, e.g. PROTO_CHECKSUM_E_PARTIAL? + */ + } else { + item = proto_tree_add_checksum(udp_tree, tvb, offset + 6, hf_udp_checksum, hf_udp_checksum_status, &ei_udp_checksum_bad, + pinfo, computed_cksum, ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY|PROTO_CHECKSUM_IN_CKSUM); + } + checksum_tree = proto_item_add_subtree(item, ett_udp_checksum); + if (computed_cksum != 0) { - proto_item_append_text(item, " (maybe caused by \"UDP checksum offload\"?)"); - col_append_str(pinfo->cinfo, COL_INFO, " [UDP CHECKSUM INCORRECT]"); - calc_item = proto_tree_add_uint(checksum_tree, hf_udp_checksum_calculated, - tvb, offset + 6, 2, in_cksum_shouldbe(udph->uh_sum, computed_cksum)); + proto_item_append_text(item, " (maybe caused by \"UDP checksum offload\"?)"); + col_append_str(pinfo->cinfo, COL_INFO, " [UDP CHECKSUM INCORRECT]"); + calc_item = proto_tree_add_uint(checksum_tree, hf_udp_checksum_calculated, + tvb, offset + 6, 2, in_cksum_shouldbe(udph->uh_sum, computed_cksum)); } else { - calc_item = proto_tree_add_uint(checksum_tree, hf_udp_checksum_calculated, - tvb, offset + 6, 2, udph->uh_sum); + calc_item = proto_tree_add_uint(checksum_tree, hf_udp_checksum_calculated, + tvb, offset + 6, 2, shouldbe_cksum); } proto_item_set_generated(calc_item); @@ -1399,6 +1427,7 @@ proto_register_udp(void) { &ei_udp_length_bad, { "udp.length.bad", PI_MALFORMED, PI_ERROR, "Bad length value", EXPFILL }}, { &ei_udplite_checksum_coverage_bad, { "udplite.checksum_coverage.bad", PI_MALFORMED, PI_ERROR, "Bad checksum coverage length value", EXPFILL }}, { &ei_udp_checksum_zero, { "udp.checksum.zero", PI_CHECKSUM, PI_ERROR, "Illegal checksum value (0)", EXPFILL }}, + { &ei_udp_checksum_partial, { "udp.checksum.partial", PI_CHECKSUM, PI_NOTE, "Partial (pseudo header) checksum (likely caused by \"UDP checksum offload\")", EXPFILL }}, { &ei_udp_checksum_bad, { "udp.checksum.bad", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }}, { &ei_udp_length_bad_zero, { "udp.length.bad_zero", PI_PROTOCOL, PI_WARN, "Length is zero but payload < 65536", EXPFILL }}, }; diff --git a/epan/in_cksum.c b/epan/in_cksum.c index 3dfc757839..5bb154bb95 100644 --- a/epan/in_cksum.c +++ b/epan/in_cksum.c @@ -28,8 +28,23 @@ #define ADDCARRY(x) {if ((x) > 65535) (x) -= 65535;} #define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);} +/* + * Linux and Windows, at least, when performing Local Checksum Offload + * store the one's complement sum (not inverted to its bitwise complement) + * of the pseudo header in the checksum field (instead of intializing + * to zero), allowing the device driver to calculate the real checksum + * later without needing knowledge of the pseudoheader itself. + * (This is presumably why GSO requires equal length buffers - so that the + * pseudo header contribution to the checksum, which includes the payload + * length, is the same.) + * + * We can output this partial checksum as an intermediate result, + * assuming that the pseudo header is all but the last chunk in the vector. + * Note that unlike the final output it is not inverted, and that it + * (like the final computed checksum) is is network byte order. + */ int -in_cksum(const vec_t *vec, int veclen) +in_cksum_ret_partial(const vec_t *vec, int veclen, uint16_t *partial) { register const guint16 *w; register int sum = 0; @@ -46,6 +61,10 @@ in_cksum(const vec_t *vec, int veclen) } l_util; for (; veclen != 0; vec++, veclen--) { + if (veclen == 1 && partial) { + REDUCE; + *partial = sum; + } if (vec->len == 0) continue; w = (const guint16 *)(const void *)vec->ptr; @@ -122,13 +141,19 @@ in_cksum(const vec_t *vec, int veclen) return (~sum & 0xffff); } +int +in_cksum(const vec_t *vec, int veclen) +{ + return in_cksum_ret_partial(vec, veclen, NULL); +} + guint16 ip_checksum(const guint8 *ptr, int len) { vec_t cksum_vec[1]; SET_CKSUM_VEC_PTR(cksum_vec[0], ptr, len); - return in_cksum(&cksum_vec[0], 1); + return in_cksum_ret_partial(&cksum_vec[0], 1, NULL); } guint16 @@ -137,7 +162,7 @@ ip_checksum_tvb(tvbuff_t *tvb, int offset, int len) vec_t cksum_vec[1]; SET_CKSUM_VEC_TVB(cksum_vec[0], tvb, offset, len); - return in_cksum(&cksum_vec[0], 1); + return in_cksum_ret_partial(&cksum_vec[0], 1, NULL); } /* diff --git a/epan/in_cksum.h b/epan/in_cksum.h index 671546f6df..d0f6fdd68d 100644 --- a/epan/in_cksum.h +++ b/epan/in_cksum.h @@ -33,6 +33,8 @@ WS_DLL_PUBLIC guint16 ip_checksum(const guint8 *ptr, int len); WS_DLL_PUBLIC guint16 ip_checksum_tvb(tvbuff_t *tvb, int offset, int len); +WS_DLL_PUBLIC int in_cksum_ret_partial(const vec_t *vec, int veclen, uint16_t *partial); + WS_DLL_PUBLIC int in_cksum(const vec_t *vec, int veclen); guint16 in_cksum_shouldbe(guint16 sum, guint16 computed_sum); diff --git a/packaging/debian/libwireshark0.symbols b/packaging/debian/libwireshark0.symbols index 88644041cf..a3c3f32fe1 100644 --- a/packaging/debian/libwireshark0.symbols +++ b/packaging/debian/libwireshark0.symbols @@ -1071,6 +1071,7 @@ libwireshark.so.0 libwireshark0 #MINVER# ieee80211_supported_rates_vals_ext@Base 1.99.1 ieee802a_add_oui@Base 1.9.1 in_cksum@Base 1.9.1 + in_cksum_ret_partial@Base 4.3.0 init_srt_table@Base 1.99.8 init_srt_table_row@Base 1.99.8 ip_checksum@Base 1.99.0 diff --git a/test/captures/http-ooo-fuzzed.pcapng b/test/captures/http-ooo-fuzzed.pcapng Binary files differnew file mode 100644 index 0000000000..7cb871c3bc --- /dev/null +++ b/test/captures/http-ooo-fuzzed.pcapng diff --git a/test/suite_clopts.py b/test/suite_clopts.py index d13f96cc86..86aca99eac 100644 --- a/test/suite_clopts.py +++ b/test/suite_clopts.py @@ -226,7 +226,7 @@ class TestTsharkZExpert: def test_tshark_z_expert_all(self, cmd_tshark, capture_file, test_env): proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert', '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) # http2-data-reassembly.pcap has Errors, Warnings, Notes, and Chats # when TCP checksum are verified. assert grep_output(proc.stdout, 'Errors') @@ -237,7 +237,7 @@ class TestTsharkZExpert: def test_tshark_z_expert_error(self, cmd_tshark, capture_file, test_env): proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,error', '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) assert grep_output(proc.stdout, 'Errors') assert not grep_output(proc.stdout, 'Warns') assert not grep_output(proc.stdout, 'Notes') @@ -246,7 +246,7 @@ class TestTsharkZExpert: def test_tshark_z_expert_warn(self, cmd_tshark, capture_file, test_env): proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,warn', '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) assert grep_output(proc.stdout, 'Errors') assert grep_output(proc.stdout, 'Warns') assert not grep_output(proc.stdout, 'Notes') @@ -255,7 +255,7 @@ class TestTsharkZExpert: def test_tshark_z_expert_note(self, cmd_tshark, capture_file, test_env): proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,note', '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) assert grep_output(proc.stdout, 'Errors') assert grep_output(proc.stdout, 'Warns') assert grep_output(proc.stdout, 'Notes') @@ -264,7 +264,7 @@ class TestTsharkZExpert: def test_tshark_z_expert_chat(self, cmd_tshark, capture_file, test_env): proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,chat', '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) assert grep_output(proc.stdout, 'Errors') assert grep_output(proc.stdout, 'Warns') assert grep_output(proc.stdout, 'Notes') @@ -293,7 +293,7 @@ class TestTsharkZExpert: def test_tshark_z_expert_filter(self, cmd_tshark, capture_file, test_env): proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,udp', '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) # Filtering for UDP should produce no expert infos. assert not grep_output(proc.stdout, 'Errors') assert not grep_output(proc.stdout, 'Warns') @@ -301,14 +301,15 @@ class TestTsharkZExpert: assert not grep_output(proc.stdout, 'Chats') def test_tshark_z_expert_error_filter(self, cmd_tshark, capture_file, test_env): - proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,warn,tls', # tls is a filter + proc = subprocesstest.run((cmd_tshark, '-q', '-z', 'expert,note,http', # tls is a filter '-o', 'tcp.check_checksum:TRUE', - '-r', capture_file('http2-data-reassembly.pcap')), capture_output=True, env=test_env) - # Filtering for TLS should produce only Error level expert infos - # with checksumming turned on, because the lower level expert infos - # are on the packets with TCP but not TLS. + '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output=True, env=test_env) + # Filtering for HTTP and Note level expert info should produce only + # Error and Warning level expert infos with checksumming turned on. + # The Note warnings on are packets with TCP but not HTTP, and we're + # filtering out the Chat level. assert grep_output(proc.stdout, 'Errors') - assert not grep_output(proc.stdout, 'Warns') + assert grep_output(proc.stdout, 'Warns') assert not grep_output(proc.stdout, 'Notes') assert not grep_output(proc.stdout, 'Chats') |