From e64dc10ea1d5be38681191d9f4d2aa9018ab59dc Mon Sep 17 00:00:00 2001
From: Masatake YAMATO <yamato@redhat.com>
Date: Sun, 4 Oct 2020 03:50:26 +0900
Subject: [PATCH] [linux] handle ffff:ffff in ipv6 addr correctly

Close #102
Close #109

The listen address and port of an AF_INET6 socket were not display if
the socket listened at an ipv6 address including ffff:ffff.

Here is a command session demonstrating the bug:

    # ip -6 addr add abcd:ef10:ffff:ffff:ffff:ffff:ffff:ff62 dev lo
    # nc -6      -l  abcd:ef10:ffff:ffff:ffff:ffff:ffff:ff62 8888 &
    [1] 6762
    # ./lsof -p 6762 -a -d fd -P -n
    COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
    nc      6762 yamato    0u   CHR  136,6      0t0       9 /dev/pts/6
    nc      6762 yamato    1u   CHR  136,6      0t0       9 /dev/pts/6
    nc      6762 yamato    2u   CHR  136,6      0t0       9 /dev/pts/6
    nc      6762 yamato    3u  sock    0,9      0t0 5833594 protocol: TCPv6

The last line should be:

    nc      6762 yamato    3u  IPv6 5833594      0t0  TCP [abcd:ef10:ffff:ffff:ffff:ffff:ffff:ff62]:8888 (LISTEN)

The original code decoding an ipv6 address uses UINT32_MAX constant
incorrect way.

@zhrf2020 reported this bug in #102.
The test case is based on the report.

@zhrf2020 provided the initial version of fix, #109.
This change is based on the version.
---
 dialects/linux/dsock.c                        | 33 ++++++++++++-
 1 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/dialects/linux/dsock.c b/dialects/linux/dsock.c
index 4b71460..0aaaf6d 100644
--- a/dialects/linux/dsock.c
+++ b/dialects/linux/dsock.c
@@ -3632,9 +3632,38 @@ net6a2in6(as, ad)
 	    (void) strncpy(buf, as, 8);
 	    buf[8] = '\0';
 	    ep = (char *)NULL;
-	    if ((ad->s6_addr32[i] = (uint32_t)strtoul(buf, &ep, 16))
-	    ==  (uint32_t)UINT32_MAX || !ep || *ep)
+
+	    errno = 0;
+	    unsigned long ul_addr = strtoul(buf, &ep, 16);
+	    if (!ep || *ep)
+		break;
+	    else if (ul_addr == ULONG_MAX && errno == ERANGE)
+	    {
+		/* Quoted from strtoul(3)
+		   ---------------------------------------------------
+		   The strtoul() function returns either the result of
+		   the conversion or, if there was a leading minus
+		   sign, the negation of the result of the conversion
+		   represented as an unsigned value, unless the
+		   original (nonnegated) value would overflow; in the
+		   latter case, strtoul() returns ULONG_MAX and sets
+		   errno to ERANGE.
+		   ---------------------------------------------------
+		   NOTE: even if the value doesn't overflow, a
+		   negative is not acceptable. */
 		break;
+	    }
+	    else if (ul_addr > (unsigned long)UINT32_MAX)
+	    {
+		/* This will never happen:
+		   The maximum length of BUF is 8 characters.
+		   The possible maximum value represented by BUF is
+		   "FFFFFFFF". This is UINT32_MAX.
+		   If you agree with what I write here, make a pull
+		   request for removing this block. */
+		break;
+	    }
+	    ad->s6_addr32[i] = (uint32_t)ul_addr;
 	}
 	return((*as || (i != 4) || len) ? 1 : 0);
 }

