Page MenuHomeFreeBSD

D53488.id.diff
No OneTemporary

D53488.id.diff

diff --git a/lib/libc/inet/inet_cidr_ntop.c b/lib/libc/inet/inet_cidr_ntop.c
--- a/lib/libc/inet/inet_cidr_ntop.c
+++ b/lib/libc/inet/inet_cidr_ntop.c
@@ -221,13 +221,7 @@
(best.len == 5 && words[5] == 0xffff))) {
int n;
- if (src[15] || bits == -1 || bits > 120)
- n = 4;
- else if (src[14] || bits > 112)
- n = 3;
- else
- n = 2;
- n = decoct(src+12, n, tp, sizeof tmp - (tp - tmp));
+ n = decoct(src+12, 4, tp, sizeof tmp - (tp - tmp));
if (n == 0) {
errno = EMSGSIZE;
return (NULL);
diff --git a/lib/libc/tests/net/Makefile b/lib/libc/tests/net/Makefile
--- a/lib/libc/tests/net/Makefile
+++ b/lib/libc/tests/net/Makefile
@@ -5,9 +5,11 @@
ATF_TESTS_C+= eui64_ntoa_test
ATF_TESTS_CXX+= link_addr_test
ATF_TESTS_CXX+= inet_net_test
+ATF_TESTS_CXX+= inet_cidr_test
CXXSTD.link_addr_test= c++20
CXXSTD.inet_net_test= c++20
+CXXSTD.inet_cidr_test= c++20
CFLAGS+= -I${.CURDIR}
diff --git a/lib/libc/tests/net/inet_cidr_test.cc b/lib/libc/tests/net/inet_cidr_test.cc
new file mode 100644
--- /dev/null
+++ b/lib/libc/tests/net/inet_cidr_test.cc
@@ -0,0 +1,373 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2025 Lexi Winter <ivy@FreeBSD.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Tests for inet_cidr_pton() and inet_cidr_ntop().
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+using namespace std::literals;
+
+/*
+ * inet_cidr_ntop() and inet_cidr_pton() for IPv4.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_inet4)
+ATF_TEST_CASE_BODY(inet_cidr_inet4)
+{
+ /*
+ * Define a list of addresses we want to check. Each address is passed
+ * to inet_cidr_pton() to convert it to an in_addr, then we convert the
+ * in_addr back to a string and compare it with the expected value. We
+ * want to test over-long prefixes here (such as 10.0.0.1/8), so we also
+ * specify what the result is expected to be.
+ */
+
+ struct test_addr {
+ std::string input;
+ int bits;
+ std::string output;
+ };
+
+ auto test_addrs = std::vector<test_addr>{
+ // Simple prefixes that fall on octet boundaries.
+ { "10.0.0.0/8", 8, "10/8" },
+ { "10.1.0.0/16", 16, "10.1/16" },
+ { "10.1.2.0/24", 24, "10.1.2/24" },
+ { "10.1.2.3/32", 32, "10.1.2.3/32" },
+
+ // Simple prefixes with the short-form address.
+ { "10/8", 8, "10/8" },
+ { "10.1/16", 16, "10.1/16" },
+ { "10.1.2/24", 24, "10.1.2/24" },
+
+ // A prefix that doesn't fall on an octet boundary.
+ { "10.1.64/18", 18, "10.1.64/18" },
+
+ // An overlong prefix with bits that aren't part of the prefix.
+ { "10.0.0.1/8", 8, "10.0.0.1/8" },
+
+ // An address with no mask is treated as a /32.
+ { "10.1.2.3", 32, "10.1.2.3/32" },
+ };
+
+ for (auto const &addr: test_addrs) {
+ /*
+ * Convert the input string to an in_addr + bits, and make
+ * sure the result produces the number of bits we expected.
+ */
+
+ auto in = in_addr{};
+ auto bits = int{};
+ auto ret = inet_cidr_pton(AF_INET, addr.input.c_str(),
+ &in, &bits);
+ ATF_REQUIRE(ret != -1);
+ ATF_REQUIRE_EQ(bits, addr.bits);
+
+ /*
+ * Convert the in_addr back to a string
+ */
+
+ /*
+ * XXX: Should there be a constant for the size of the result
+ * buffer? For now, use ADDRSTRLEN + 3 ("/32") + 1 (NUL).
+ *
+ * Fill the buffer with 'Z', so we can check the result was
+ * properly terminated.
+ */
+ auto strbuf = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto *pret = inet_cidr_ntop(AF_INET, &in, bits,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(pret != nullptr);
+ ATF_REQUIRE_EQ(pret, strbuf.data());
+
+ /* Make sure the result was NUL-terminated and find the NUL */
+ ATF_REQUIRE(strbuf.size() >= 1);
+ auto end = std::ranges::find(strbuf, '\0');
+ ATF_REQUIRE(end != strbuf.end());
+
+ /*
+ * Check the result matches what we expect. Use a temporary
+ * string here instead of std::ranges::equal because this
+ * means ATF can print the mismatch.
+ */
+ auto str = std::string(std::ranges::begin(strbuf), end);
+ ATF_REQUIRE_EQ(str, addr.output);
+
+ /*
+ * Call inet_cidr_ntop() again, but pass bits as -1. This
+ * skips printing the prefix length, which makes the function
+ * identical to inet_ntop() (so what is the point of this?).
+ */
+ auto buf1 = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto *ret1 = inet_cidr_ntop(AF_INET, &in, -1,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret1 != NULL);
+
+ auto buf2 = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto *ret2 = inet_ntop(AF_INET, &in,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret2 != NULL);
+
+ auto str1 = std::string(buf1.begin(), buf1.end());
+ auto str2 = std::string(buf2.begin(), buf2.end());
+ ATF_REQUIRE_EQ(str1, str2);
+ }
+}
+
+/*
+ * inet_cidr_ntop() and inet_cidr_pton() for IPv6.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_inet6)
+ATF_TEST_CASE_BODY(inet_cidr_inet6)
+{
+ /*
+ * Define a list of addresses we want to check. Each address is
+ * passed to inet_cidr_pton() to convert it to an in6_addr, then we
+ * convert the in6_addr back to a string and compare it with the
+ * expected value. We want to test over-long prefixes here (such
+ * as 2001:db8::1/32), so we also specify what the result is
+ * expected to be.
+ */
+
+ struct test_addr {
+ std::string input;
+ int bits;
+ std::string output;
+ };
+
+ auto test_addrs = std::vector<test_addr>{
+ // An address with no prefix length
+ { "::1", -1, "::1" },
+ { "2001:db8::1", -1, "2001:db8::1" },
+
+ // A prefix with a trailing ::
+ { "2001:db8::/32", 32, "2001:db8::/32" },
+
+ // A prefix with a leading ::. Note that the output is
+ // different from the input because inet_ntop() renders
+ // this prefix with an IPv4 suffix for legacy reasons.
+ { "::ffff:0:0/96", 96, "::ffff:0.0.0.0/96" },
+ { "::ffff:0:1/96", 96, "::ffff:0.0.0.1/96" },
+ { "::ffff:1:0/96", 96, "::ffff:0.1.0.0/96" },
+
+ // The same prefix but with the IPv4 legacy form as input.
+ { "::ffff:0.0.0.0/96", 96, "::ffff:0.0.0.0/96" },
+ { "::ffff:0.0.3.0/96", 96, "::ffff:0.0.3.0/96" },
+
+ // A prefix with an infix ::.
+ { "2001:db8::1/128", 128, "2001:db8::1/128" },
+
+ // A prefix with bits set which are outside the prefix;
+ // these should be silently ignored.
+ { "2001:db8:1:1:1:1:1:1/32", 32, "2001:db8:1:1:1:1:1:1/32" },
+
+ // As above but with infix ::.
+ { "2001:db8::1/32", 32, "2001:db8::1/32" },
+
+ // A prefix with only ::, commonly used to represent the
+ // entire address space.
+ { "::/0", 0, "::/0" },
+
+ // A single address with no ::.
+ { "2001:db8:1:1:1:1:1:1/128", 128, "2001:db8:1:1:1:1:1:1/128" },
+
+ // A prefix with no ::.
+ { "2001:db8:1:1:0:0:0:0/64", 64, "2001:db8:1:1::/64" },
+
+ // A prefix which isn't on a 16-bit boundary.
+ { "2001:db8:c000::/56", 56, "2001:db8:c000::/56" },
+
+ // A prefix which isn't on a nibble boundary.
+ { "2001:db8:c100::/57", 57, "2001:db8:c100::/57" },
+
+ // Test vectors provided in PR bin/289198.
+ { "fe80::1/64", 64, "fe80::1/64" },
+ { "fe80::f000:74ff:fe54:bed2/64",
+ 64, "fe80::f000:74ff:fe54:bed2/64" },
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64",
+ 64,
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64" },
+ };
+
+ for (auto const &addr: test_addrs) {
+ /*
+ * Convert the input string to an in6_addr + bits, and make
+ * sure the result produces the number of bits we expected.
+ */
+
+ auto in6 = in6_addr{};
+ auto bits = int{};
+ errno = 0;
+
+ auto ret = inet_cidr_pton(AF_INET6, addr.input.c_str(),
+ &in6, &bits);
+ ATF_REQUIRE_EQ(ret, 0);
+ ATF_REQUIRE_EQ(bits, addr.bits);
+
+ /*
+ * Convert the in6_addr back to a string
+ */
+
+ /*
+ * XXX: Should there be a constant for the size of the result
+ * buffer? For now, use ADDRSTRLEN + 4 ("/128") + 1 (NUL).
+ *
+ * Fill the buffer with 'Z', so we can check the result was
+ * properly terminated.
+ */
+ auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto *pret = inet_cidr_ntop(AF_INET6, &in6, bits,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(pret != NULL);
+ ATF_REQUIRE_EQ(pret, strbuf.data());
+
+ /* Make sure the result was NUL-terminated and find the NUL */
+ ATF_REQUIRE(strbuf.size() >= 1);
+ auto end = std::ranges::find(strbuf, '\0');
+ ATF_REQUIRE(end != strbuf.end());
+
+ /*
+ * Check the result matches what we expect. Use a temporary
+ * string here instead of std::ranges::equal because this
+ * means ATF can print the mismatch.
+ */
+ auto str = std::string(std::ranges::begin(strbuf), end);
+ ATF_REQUIRE_EQ(str, addr.output);
+
+ /*
+ * Call inet_cidr_ntop() again, but pass bits as -1. This
+ * skips printing the prefix length, which makes the function
+ * identical to inet_ntop() (so what is the point of this?).
+ */
+ auto buf1 = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto *ret1 = inet_cidr_ntop(AF_INET6, &in6, -1,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret1 != NULL);
+
+ auto buf2 = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto *ret2 = inet_ntop(AF_INET6, &in6,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret2 != NULL);
+
+ auto str1 = std::string(buf1.begin(), buf1.end());
+ auto str2 = std::string(buf2.begin(), buf2.end());
+ ATF_REQUIRE_EQ(str1, str2);
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_pton_invalid)
+ATF_TEST_CASE_BODY(inet_cidr_pton_invalid)
+{
+ auto bits = int{};
+ auto addr4 = in_addr{};
+ auto str4 = "10.0.0.0"s;
+ auto addr6 = in6_addr{};
+ auto str6 = "2001:db8::"s;
+
+ /* Test some generally invalid addresses. */
+ auto invalid4 = std::vector<std::string>{
+ // Partial address with no prefix length
+ "10",
+ "10.0",
+ "10.0.0",
+ // Prefix length too big
+ "10.0.0.0/33",
+ // Prefix length is negative
+ "10.0.0.0/-1",
+ // Prefix length is not a number
+ "10.0.0.0/foo",
+ // Input is not a network prefix
+ "this is not an IP address",
+ };
+
+ for (auto const &addr: invalid4) {
+ auto ret = inet_cidr_pton(AF_INET, addr.c_str(), &addr4, &bits);
+ ATF_REQUIRE_EQ(ret, -1);
+ }
+
+ auto invalid6 = std::vector<std::string>{
+ // Prefix length too big
+ "2001:db8::/129",
+ // Prefix length is negative
+ "2001:db8::/-1",
+ // Prefix length is not a number
+ "2001:db8::/foo",
+ // Input is not a network prefix
+ "this is not an IP address",
+ };
+
+ for (auto const &addr: invalid6) {
+ auto ret = inet_cidr_pton(AF_INET6, addr.c_str(), &addr6, &bits);
+ ATF_REQUIRE_EQ(ret, -1);
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(inet_cidr_ntop_invalid)
+ATF_TEST_CASE_BODY(inet_cidr_ntop_invalid)
+{
+ auto addr4 = in_addr{};
+ auto addr6 = in6_addr{};
+ auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1);
+
+ /*
+ * Passing a buffer which is too small should not overrun the buffer.
+ * Test this by initialising the buffer to 'Z', and only providing
+ * part of it to the function.
+ */
+
+ std::ranges::fill(strbuf, 'Z');
+ auto ret = inet_cidr_ntop(AF_INET6, &addr6, 128, strbuf.data(), 1);
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ATF_REQUIRE_EQ(strbuf[1], 'Z');
+
+ std::ranges::fill(strbuf, 'Z');
+ ret = inet_cidr_ntop(AF_INET, &addr4, 32, strbuf.data(), 1);
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ATF_REQUIRE_EQ(strbuf[1], 'Z');
+
+ /* Check that invalid prefix lengths return an error */
+
+ ret = inet_cidr_ntop(AF_INET6, &addr6, 129, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ret = inet_cidr_ntop(AF_INET6, &addr6, -2, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+
+ ret = inet_cidr_ntop(AF_INET, &addr4, 33, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ret = inet_cidr_ntop(AF_INET, &addr4, -2, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_inet4);
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_inet6);
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_pton_invalid);
+ ATF_ADD_TEST_CASE(tcs, inet_cidr_ntop_invalid);
+}

File Metadata

Mime Type
text/plain
Expires
Wed, Mar 11, 2:47 PM (6 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29524065
Default Alt Text
D53488.id.diff (12 KB)

Event Timeline