Closed lanodan closed 1 year ago
static void reverse_host6(
struct in6_addr *addr,
char *buff,
int buff_length)
{
int i;
char *b = buff;
for (i = (sizeof(*addr) / 2 - 1); i >= 0; i--, b += 4) /* 64b portion */
snprintf(b, buff_length,
"%x.%x.", addr->s6_addr[i] & 0xf, addr->s6_addr[i] >> 4);
buff[strlen(buff) - 1] = '\0';
}
I'm guessing because buff_length
as an argument to snprintf
needs to be decremented as b
is incremented?
The size of the destination buffer can be predicted 100% statically. 128 prints in 32 hex characters with a dot between each of them. So 32*2 = 64. Now the last digit doesn't have a trailing . but there is a tailing \0, so the buffer needs to be 64 characters.... Except that the trailing . IS printed and then removed.
So... the buffer needs to be at least 65 characters. With the current price of say 10 bytes of RAM being less than a microcent, I'd say that taking some margin and declaring the buffer to be say at least 80 characters to be "safe" against such stupid off-by-one errors. (Save on programer-hours at the cost of a few bytes of extra RAM at runtime).
Ah. I missed another detail at first: Only 64 bits are printed, so we're talking about using only 32/33 bytes of the buffer.
I checked the caller of "reverse_host6" and... it uses a buffer of 128 characters where just over 65 would've sufficed. Then those 65 characters are concatenated with another 30 or so characters and placed into another 128-byte buffer. So, although the code is not perfect, this shouldn't cause the buffer overrun.
To fix the code: i = sizeof (... ) should be changed into i = 7 / process the top 8 bytes of the address / (The /2 hinted to me that the s6_addr array was defined as being 16 bit SHORTs... That's the wrong hint to be conveying. ) (*) The b+=4 should have buff_length-=4 added.
The "strlen" at the end is unnecessary: *--b = '\0'; // Remove the last '.'
Then.... The error says that a buffer overflow was detected inside snprintf. IMHO I don't see how that could happen.
I've implemented these changes. Can you guys double check that nothing changed?
(*) P.S. So things would go wrong (not only possibly overflowing the buffer, but also generating wrong results!) when someone extends in6_addr to bigger than 16 bytes.
It's not entirely clear what caused a buffer overflow there, but along the way what if we eliminate *printf() there, and clean up the code around its call?
diff --git a/ui/asn.c b/ui/asn.c
index 4790d6a..75c0f62 100644
--- a/ui/asn.c
+++ b/ui/asn.c
@@ -28,6 +28,7 @@
#include "portability/error.h"
#endif
#include <errno.h>
+#include <assert.h>
#ifdef __APPLE__
#define BIND_8_COMPAT
@@ -199,19 +200,26 @@ static char *split_txtrec(
}
#ifdef ENABLE_IPV6
-/* from dns.c:addr2ip6arpa() */
+#define BYTES_TO_PROCESS 8
static void reverse_host6(
struct in6_addr *addr,
char *buff,
int buff_length)
{
- int i;
- char *b = buff;
- // We need to process the top 64 bits, or 8 bytes.
- for (i = 8-1; i >= 0; i--, b += 4, buff_length -= 4)
- snprintf(b, buff_length,
- "%x.%x.", addr->s6_addr[i] & 0xf, addr->s6_addr[i] >> 4);
- *--b = 0;
+ static char hex4bits[] = "0123456789abcdef";
+ int i, j;
+ char c;
+ assert(buff_length >= (4 * BYTES_TO_PROCESS));
+ // We need to process the top 64 bits, or 8 bytes.
+ for (i = 0; i < BYTES_TO_PROCESS; i++) {
+ c = addr->s6_addr[BYTES_TO_PROCESS - 1 - i];
+ j = i * 4;
+ buff[j] = hex4bits[c & 0xf];
+ buff[j + 1] = '.';
+ buff[j + 2] = hex4bits[(c >> 4) & 0xf];
+ buff[j + 3] = '.';
+ }
+ buff[4 * BYTES_TO_PROCESS - 1] = 0;
}
#endif
@@ -229,22 +237,16 @@ static char *get_ipinfo(
if (ctl->af == AF_INET6) {
#ifdef ENABLE_IPV6
- reverse_host6(addr, key, NAMELEN);
- if (snprintf(lookup_key, NAMELEN, "%s.origin6.asn.cymru.com", key)
- >= NAMELEN)
+ reverse_host6(addr, key, sizeof(key));
+ if (snprintf(lookup_key, sizeof(lookup_key), "%s.origin6.asn.cymru.com", key) < 0)
return NULL;
#else
return NULL;
#endif
} else {
- unsigned char buff[4];
- memcpy(buff, addr, 4);
- if (snprintf
- (key, NAMELEN, "%d.%d.%d.%d", buff[3], buff[2], buff[1],
- buff[0]) >= NAMELEN)
+ if (snprintf(key, sizeof(key), "%d.%d.%d.%d", ((unsigned char*)addr)[3], ((unsigned char*)addr)[2], ((unsigned char*)addr)[1], ((unsigned char*)addr)[0]) < 0)
return NULL;
- if (snprintf(lookup_key, NAMELEN, "%s.origin.asn.cymru.com", key)
- >= NAMELEN)
+ if (snprintf(lookup_key, sizeof(lookup_key), "%s.origin.asn.cymru.com", key) < 0)
return NULL;
}
If you do i=BYTES_TOPROCESS-1, j=0 and change the j's to j++ in the assignments that makes things a bit clearer I think.
+ for (i = BYTES_TO_PROCESS-1, j=0; i>= 0; i++) {
+ c = addr->s6_addr[i];
+ buff[j++] = hex4bits[c & 0xf];
+ buff[j++] = '.';
+ buff[j++] = hex4bits[(c >> 4) & 0xf];
+ buff[j++] = '.';
+ }
That said: Minor nitpick, it is worth a try. Is @lanodan still available to test patches?
If you do i=BYTES_TOPROCESS-1, j=0 and change the j's to j++ in the assignments that makes things a bit clearer I think.
+ for (i = BYTES_TO_PROCESS-1, j=0; i>= 0; i++) { + c = addr->s6_addr[BYTES_TO_PROCESS - 1 - i]; + buff[j++] = hex4bits[c & 0xf]; + buff[j++] = '.'; + buff[j++] = hex4bits[(c >> 4) & 0xf]; + buff[j++] = '.'; + }
Good point, thanks. Then
+ char *b;
+ for (i = BYTES_TO_PROCESS - 1, b = buff; i >= 0; i--) {
+ c = addr->s6_addr[i];
+ *b++ = hex4bits[c & 0xf];
+ *b++ = '.';
+ *b++ = hex4bits[(c >> 4) & 0xf];
+ *b++ = '.';
+ }
Even nicer. :-)
Sorry for the lag, I'm mostly using email so I didn't get the mention in https://github.com/traviscross/mtr/issues/392#issuecomment-821952944 on testing the patches.
With current HEAD being at dca7750f19319fb3d51d9ce9710b9c64acfa9d08:
btw here is more info on how it's built that I should have given in the OP:
CC="clang"
CFLAGS="-O2 -pipe -march=native -mtune=native -Wall -Wextra -Wformat-security -fstack-protector-strong -Qunused-arguments -D_FORTIFY_SOURCE=2"
LDFLAGS="-Wl,-z,relro -Wl,-z,now -Wl,-as-needed"
ELIBC="glibc"
net-analyzer/mtr-9999::gentoo was built with the following:
USE="filecaps ipinfo ipv6 ncurses -gtk -jansson" ABI_X86="(64)"
Backtrace: