From 50aed6a873d044cb771e6e7f34a83aaf4a350836 Mon Sep 17 00:00:00 2001 From: Josephine Pfeiffer Date: Fri, 23 Oct 2026 12:36:19 +0200 Subject: [PATCH 1/2] Expand IPv6 addresses with inet_pton oscap_expand_ipv6() counted one group too many when an address ended in "::", so fd19:c33d:98bc:: and "::" expanded to seven groups and the ARF failed the Asset Identification ip-v6 pattern. The parser also wrote into a fixed 40-byte buffer without bounds checks and read past the NUL when the input ended in a single ':'. Parse with inet_pton and print eight lowercase groups without leading zeros, which the pattern accepts. Input that is not an IPv6 address or carries a %zone is returned unchanged, since macOS inet_pton accepts zones and writes the interface index into link-local addresses. Signed-off-by: Josephine Pfeiffer --- src/common/util.c | 77 +++++++++++------------------------------------ src/common/util.h | 3 ++ 2 files changed, 20 insertions(+), 60 deletions(-) diff --git a/src/common/util.c b/src/common/util.c index 51dfd2e3da..a807369f86 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -43,10 +43,14 @@ #ifdef OS_WINDOWS #include +#include +#include #include #else +#include #include #include +#include #endif #define PATH_SEPARATOR '/' @@ -200,67 +204,20 @@ void oscap_strtoupper(char *str) char *oscap_expand_ipv6(const char *input) { - // we have to do a double pass because we need to know the number of - // specified components, :: will fill up to a total of 8 if encountered - - const char* input_it = input; - unsigned short component_count = 1; // fencepost, we start with 1 - while (*input_it) { - if (*input_it == ':') { - // don't count ::1 as 2 components - if (input_it != input) - component_count++; - - // the next character is either a part of a component or another ':' - // in any case we don't want to count it - input_it++; - } - - input_it++; - } + unsigned char b[16]; - // IPv6 is at most eight 4-tuples of [0-9a-f] with 7 separators, plus \0 - char* ret = malloc(8 * 4 * sizeof(char) + 7 + 1); - char* output_it = ret; - - input_it = input; - while (*input_it) { - // signifies that we have finished writing a component this iteration - bool closed_component = false; - - if (*input_it == ':') { - const char next = *(input_it + 1 * sizeof(char)); - if (next == ':') { - // skip one extra char to skip over the whole :: - input_it++; - - for (unsigned short i = component_count; i < 8; ++i) { - // don't add leading separators - if (output_it != ret) - *output_it++ = ':'; - - *output_it++ = '0'; - closed_component = true; - } - } - else { - closed_component = true; - } - } - else { - *output_it++ = *input_it; - } - - input_it++; - - // if this was not the last char of the input add a separator - if (closed_component && *input_it) - *output_it++ = ':'; - } - - *output_it = '\0'; - - return ret; + /* + * glibc rejects a %zone; macOS inet_pton drops it, writing the + * interface index into the second group of link-local addresses. + */ + if (strchr(input, '%') != NULL || inet_pton(AF_INET6, input, b) != 1) + return oscap_strdup(input); + + return oscap_sprintf("%x:%x:%x:%x:%x:%x:%x:%x", + b[0] * 256u + b[1], b[2] * 256u + b[3], + b[4] * 256u + b[5], b[6] * 256u + b[7], + b[8] * 256u + b[9], b[10] * 256u + b[11], + b[12] * 256u + b[13], b[14] * 256u + b[15]); } char *oscap_realpath(const char *path, char *resolved_path) diff --git a/src/common/util.h b/src/common/util.h index dab8c62c68..b9536f1b98 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -431,6 +431,9 @@ bool oscap_ptr_cmp(void *node1, void *node2); * Asset Identification specification requires fully expanded IPv6 addresses. * Despite this being greatly discouraged by RFC 5952 we have to comply and * expand given IPv6, otherwise the resulting AI content would be invalid. + * + * @return newly allocated expanded address, or a copy of input if it is not + * an IPv6 address or has a %zone suffix */ char *oscap_expand_ipv6(const char *input); From b190571ba16742483e186926a17b7adcb9c50762 Mon Sep 17 00:00:00 2001 From: Josephine Pfeiffer Date: Fri, 23 Oct 2026 12:36:26 +0200 Subject: [PATCH 2/2] Add unit test for oscap_expand_ipv6 No test exercised this function directly, so the "::" miscount only showed up as ARF validation failures on hosts with such an address. Pin the eight-group output for compressed, uppercase, zero-padded and IPv4-embedded input, including one longer than the old 40-byte buffer, and the pass-through of zoned and malformed strings. Global addresses come from the documentation ranges in RFC 3849 and RFC 5737. Signed-off-by: Josephine Pfeiffer --- tests/common/test_oscap_util.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/common/test_oscap_util.c b/tests/common/test_oscap_util.c index 8a09738e97..8078d62fb9 100644 --- a/tests/common/test_oscap_util.c +++ b/tests/common/test_oscap_util.c @@ -34,6 +34,7 @@ int test_oscap_path_startswith(void); int test_oscap_strrm(void); int test_oscap_indent(void); int test_oscap_remove_excess_whitespace(void); +int test_oscap_expand_ipv6(void); int test_oscap_path_startswith() { @@ -168,6 +169,36 @@ int test_oscap_remove_excess_whitespace() return 0; } +int test_oscap_expand_ipv6() +{ + const char *cases[][2] = { + { "::1", "0:0:0:0:0:0:0:1" }, + { "::", "0:0:0:0:0:0:0:0" }, + { "2001:db8::1", "2001:db8:0:0:0:0:0:1" }, + { "2001:db8:1::", "2001:db8:1:0:0:0:0:0" }, + { "2001:DB8:1::", "2001:db8:1:0:0:0:0:0" }, + { "2001:db8::00ff", "2001:db8:0:0:0:0:0:ff" }, + { "2001:db8::ff00:42:8329", "2001:db8:0:0:0:ff00:42:8329" }, + { "2001:db8::192.0.2.1", "2001:db8:0:0:0:0:c000:201" }, + { "2001:db8:0000:0000:0000:0000:198.51.100.200", + "2001:db8:0:0:0:0:c633:64c8" }, + { "2001:db8:1:2:3:4:5:6", "2001:db8:1:2:3:4:5:6" }, + { "fe80::1%eth0", "fe80::1%eth0" }, + { "fe80::1:", "fe80::1:" }, + { "00:11:22:33:44:55", "00:11:22:33:44:55" }, + }; + + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + char *result = oscap_expand_ipv6(cases[i][0]); + int mismatch = strcmp(result, cases[i][1]); + free(result); + if (mismatch != 0) + return (int)i + 1; + } + + return 0; +} + int main (int argc, char *argv[]) { int retval = 0; @@ -180,6 +211,7 @@ int main (int argc, char *argv[]) return retval; if ((retval = test_oscap_remove_excess_whitespace()) != 0) return retval; + retval = test_oscap_expand_ipv6(); return retval; }