aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuy Harris <gharris@sonic.net>2023-07-18 16:22:12 -0700
committerGuy Harris <gharris@sonic.net>2023-07-18 16:22:12 -0700
commitdf1d38ef680ef4ddcb390d633ddcc7a770ffdb05 (patch)
tree4e55b9356ae8ab407f9daa681120a35518e45190
parent764ff9bbfed654cf37ec9d14b0a17146cdb39305 (diff)
Linux USB: avoid 32-bit unsigned integer wraparound.
When adding values whose sum might overflow an unsigned integer, first check whether the sum *would* overflow an unsigned integer and, if so, clamp the sum at UINT_MAX. Do the same for a multiplication. This should fix #1134, as well as the issue in #1205.
-rw-r--r--pcap-usb-linux-common.c73
1 files changed, 66 insertions, 7 deletions
diff --git a/pcap-usb-linux-common.c b/pcap-usb-linux-common.c
index 267ea568..427b5c52 100644
--- a/pcap-usb-linux-common.c
+++ b/pcap-usb-linux-common.c
@@ -22,12 +22,24 @@
* deal with Linux USB captures.
*/
+#include <limits.h> /* for UINT_MAX */
+
#include "pcap/pcap.h"
#include "pcap/usb.h"
#include "pcap-usb-linux-common.h"
/*
+ * Return the sum of the two u_int arguments if that sum fits in a u_int,
+ * and return UINT_MAX otherwise.
+ */
+static inline u_int
+u_int_sum(u_int a, u_int b)
+{
+ return (((b) <= UINT_MAX - (b)) ? (a) + (b) : UINT_MAX);
+}
+
+/*
* Compute, from the data provided by the Linux USB memory-mapped capture
* mechanism, the amount of packet data that would have been provided
* had the capture mechanism not chopped off any data at the end, if, in
@@ -55,7 +67,10 @@ fix_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp)
pkth->len == sizeof(pcap_usb_header_mmapped) +
(hdr->ndesc * sizeof (usb_isodesc)) + hdr->urb_len) {
usb_isodesc *descs;
- u_int pre_truncation_data_len, pre_truncation_len;
+ u_int pre_truncation_descriptors_len;
+ u_int pre_truncation_header_len;
+ u_int pre_truncation_data_len;
+ u_int pre_truncation_len;
descs = (usb_isodesc *) (bp + sizeof(pcap_usb_header_mmapped));
@@ -82,7 +97,15 @@ fix_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp)
u_int desc_end;
if (descs[desc].len != 0) {
- desc_end = descs[desc].offset + descs[desc].len;
+ /*
+ * Compute the end offset of the data
+ * for this descriptor, i.e. the offset
+ * of the byte after te data. Clamp
+ * the sum at UINT_MAX, so that it fits
+ * in a u_int.
+ */
+ desc_end = u_int_sum(descs[desc].offset,
+ descs[desc].len);
if (desc_end > pre_truncation_data_len)
pre_truncation_data_len = desc_end;
}
@@ -91,14 +114,50 @@ fix_linux_usb_mmapped_length(struct pcap_pkthdr *pkth, const u_char *bp)
/*
* Now calculate the total length based on that data
* length.
+ *
+ * First, make sure the total length of the ISO
+ * descriptors fits in an unsigned int. We know
+ * that sizeof (usb_isodesc) is a small power-of-2
+ * integer (16 bytes), so we just check whether
+ * hdr->ndesc < (UINT_MAX + (uint64_t)1) / sizeof (usb_isodesc),
+ * as that would mean that hdr->ndesc * sizeof (usb_isodesc)
+ * is < (UINT_MAX + (uint64_t)1) and thus <= UINT_MAX.
+ * ((UINT_MAX + (uint64_t)1) will probably be computed
+ * at compile time with most C compilers.)
+ */
+ if (hdr->ndesc < (UINT_MAX + (uint64_t)1) / sizeof (usb_isodesc)) {
+ /*
+ * It fits.
+ */
+ pre_truncation_descriptors_len =
+ hdr->ndesc * sizeof (usb_isodesc);
+ } else {
+ /*
+ * It doesn't fit.
+ */
+ pre_truncation_descriptors_len = UINT_MAX;
+ }
+
+ /*
+ * Now, add the length of the memory-mapped header and
+ * the length of the ISO descriptors, clamping the
+ * result at UINT_MAX.
+ */
+ pre_truncation_header_len = u_int_sum(sizeof(pcap_usb_header_mmapped),
+ pre_truncation_descriptors_len);
+
+ /*
+ * Now, add the total header length (memory-mapped header
+ * and ISO descriptors) and the data length, clamping
+ * the result at UINT_MAX.
*/
- pre_truncation_len = sizeof(pcap_usb_header_mmapped) +
- (hdr->ndesc * sizeof (usb_isodesc)) +
- pre_truncation_data_len;
+ pre_truncation_len = u_int_sum(pre_truncation_header_len,
+ pre_truncation_data_len);
/*
- * If that's greater than or equal to the captured length,
- * use that as the length.
+ * pre_truncation_len is now the smaller of
+ * UINT_MAX and the total header plus data length.
+ * That's guaranteed to fit in a UINT_MAX.
*/
if (pre_truncation_len >= pkth->caplen)
pkth->len = pre_truncation_len;