xref: /freebsd/lib/libc/tests/net/link_addr_test.cc (revision a1215090416b8afb346fb2ff5b38f25ba0134a3a)
1 /*
2  * Copyright (c) 2025 Lexi Winter
3  *
4  * SPDX-License-Identifier: ISC
5  */
6 
7 /*
8  * Tests for link_addr() and link_ntoa().
9  *
10  * link_addr converts a string representing an (optionally null) interface name
11  * followed by an Ethernet address into a sockaddr_dl.  The expected format is
12  * "[ifname]:lladdr".  This means if ifname is not specified, the leading colon
13  * is still required.
14  *
15  * link_ntoa does the inverse of link_addr, returning a string representation
16  * of the address.
17  *
18  * Note that the output format of link_ntoa is not valid input for link_addr
19  * since the leading colon may be omitted.  This is by design.
20  */
21 
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 
25 #include <net/ethernet.h>
26 #include <net/if_dl.h>
27 
28 #include <format>
29 #include <iostream>
30 #include <ranges>
31 #include <span>
32 #include <utility>
33 #include <vector>
34 
35 #include <cstddef>
36 #include <cstdint>
37 
38 #include <atf-c++.hpp>
39 
40 using namespace std::literals;
41 
42 /*
43  * Define operator== and operator<< for ether_addr so we can use them in
44  * ATF_EXPECT_EQ expressions.
45  */
46 
47 bool
operator ==(ether_addr a,ether_addr b)48 operator==(ether_addr a, ether_addr b)
49 {
50 	return (std::ranges::equal(a.octet, b.octet));
51 }
52 
53 std::ostream &
operator <<(std::ostream & s,ether_addr a)54 operator<<(std::ostream &s, ether_addr a)
55 {
56 	for (unsigned i = 0; i < ETHER_ADDR_LEN; ++i) {
57 		if (i > 0)
58 			s << ":";
59 
60 		s << std::format("{:02x}", static_cast<int>(a.octet[i]));
61 	}
62 
63 	return (s);
64 }
65 
66 /*
67  * Create a sockaddr_dl from a string using link_addr(), and ensure the
68  * returned struct looks valid.
69  */
70 sockaddr_dl
make_linkaddr(const std::string & addr)71 make_linkaddr(const std::string &addr)
72 {
73 	auto sdl = sockaddr_dl{};
74 	int ret;
75 
76 	sdl.sdl_len = sizeof(sdl);
77 	ret = ::link_addr(addr.c_str(), &sdl);
78 
79 	ATF_REQUIRE_EQ(0, ret);
80 	ATF_REQUIRE_EQ(AF_LINK, static_cast<int>(sdl.sdl_family));
81 	ATF_REQUIRE_EQ(true, sdl.sdl_len > 0);
82 	ATF_REQUIRE_EQ(true, sdl.sdl_nlen >= 0);
83 
84 	return (sdl);
85 }
86 
87 /*
88  * Return the data stored in a sockaddr_dl as a span.
89  */
90 std::span<const char>
data(const sockaddr_dl & sdl)91 data(const sockaddr_dl &sdl)
92 {
93 	// sdl_len is the entire structure, but we only want the length of the
94 	// data area.
95 	auto dlen = sdl.sdl_len - offsetof(sockaddr_dl, sdl_data);
96 	return {&sdl.sdl_data[0], dlen};
97 }
98 
99 /*
100  * Return the interface name stored in a sockaddr_dl as a string.
101  */
102 std::string_view
ifname(const sockaddr_dl & sdl)103 ifname(const sockaddr_dl &sdl)
104 {
105 	auto name = data(sdl).subspan(0, sdl.sdl_nlen);
106 	return {name.begin(), name.end()};
107 }
108 
109 /*
110  * Return the Ethernet address stored in a sockaddr_dl as an ether_addr.
111  */
112 ether_addr
addr(const sockaddr_dl & sdl)113 addr(const sockaddr_dl &sdl)
114 {
115 	ether_addr ret{};
116 	ATF_REQUIRE_EQ(ETHER_ADDR_LEN, sdl.sdl_alen);
117 	std::ranges::copy(data(sdl).subspan(sdl.sdl_nlen, ETHER_ADDR_LEN),
118 			  &ret.octet[0]);
119 	return (ret);
120 }
121 
122 /*
123  * Return the link address stored in a sockaddr_dl as a span of octets.
124  */
125 std::span<const std::uint8_t>
lladdr(const sockaddr_dl & sdl)126 lladdr(const sockaddr_dl &sdl)
127 {
128 	auto data = reinterpret_cast<const uint8_t *>(LLADDR(&sdl));
129 	return {data, data + sdl.sdl_alen};
130 }
131 
132 
133 /*
134  * Some sample addresses we use for testing.  Include at least one address for
135  * each format we want to support.
136  */
137 
138 struct test_address {
139 	std::string input;	/* value passed to link_addr */
140 	std::string ntoa;	/* expected return from link_ntoa */
141 	ether_addr addr{};	/* expected return from link_addr */
142 };
143 
144 std::vector<test_address> test_addresses{
145 	// No delimiter
146 	{"001122334455"s, "0.11.22.33.44.55",
147 	 ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
148 
149 	// Colon delimiter
150 	{"00:11:22:33:44:55"s, "0.11.22.33.44.55",
151 	 ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
152 
153 	// Dash delimiter
154 	{"00-11-22-33-44-55"s, "0.11.22.33.44.55",
155 	 ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
156 
157 	// Period delimiter (link_ntoa format)
158 	{"00.11.22.33.44.55"s, "0.11.22.33.44.55",
159 	 ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
160 
161 	// Period delimiter (Cisco format)
162 	{"0011.2233.4455"s, "0.11.22.33.44.55",
163 	 ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
164 
165 	// An addresses without leading zeroes.
166 	{"0:1:02:30:4:55"s, "0.1.2.30.4.55",
167 	 ether_addr{0x00, 0x01, 0x02, 0x30, 0x04, 0x55}},
168 
169 	// An address with some uppercase letters.
170 	{"AA:B:cC:Dd:e0:1f"s, "aa.b.cc.dd.e0.1f",
171 	 ether_addr{0xaa, 0x0b, 0xcc, 0xdd, 0xe0, 0x1f}},
172 
173 	// Addresses composed only of letters, to make sure they're not
174 	// confused with an interface name.
175 
176 	{"aabbccddeeff"s, "aa.bb.cc.dd.ee.ff",
177 	 ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
178 
179 	{"aa:bb:cc:dd:ee:ff"s, "aa.bb.cc.dd.ee.ff",
180 	 ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
181 
182 	// Address with a blank octet; this is an old form of Ethernet address.
183 	{"00:11::33:44:55"s, "0.11.0.33.44.55",
184 	 ether_addr{0x00, 0x11, 0x00, 0x33, 0x44, 0x55}},
185 };
186 
187 /*
188  * Test without an interface name.
189  */
190 ATF_TEST_CASE_WITHOUT_HEAD(basic)
ATF_TEST_CASE_BODY(basic)191 ATF_TEST_CASE_BODY(basic)
192 {
193 	for (const auto &ta : test_addresses) {
194 		// This does basic tests on the returned value.
195 		auto sdl = make_linkaddr(":" + ta.input);
196 
197 		// Check the ifname and address.
198 		ATF_REQUIRE_EQ(""s, ifname(sdl));
199 		ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));
200 		ATF_REQUIRE_EQ(ta.addr, addr(sdl));
201 
202 		// Check link_ntoa returns the expected value.
203 		auto ntoa = std::string(::link_ntoa(&sdl));
204 		ATF_REQUIRE_EQ(ta.ntoa, ntoa);
205 	}
206 
207 }
208 
209 /*
210  * Test with an interface name.
211  */
212 ATF_TEST_CASE_WITHOUT_HEAD(ifname)
ATF_TEST_CASE_BODY(ifname)213 ATF_TEST_CASE_BODY(ifname)
214 {
215 	for (const auto &ta : test_addresses) {
216 		auto sdl = make_linkaddr("ix0:" + ta.input);
217 
218 		ATF_REQUIRE_EQ("ix0", ifname(sdl));
219 		ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));
220 		ATF_REQUIRE_EQ(ta.addr, addr(sdl));
221 
222 		auto ntoa = std::string(::link_ntoa(&sdl));
223 		ATF_REQUIRE_EQ("ix0:" + ta.ntoa, ntoa);
224 	}
225 
226 }
227 
228 /*
229  * Test with some invalid addresses.
230  */
231 ATF_TEST_CASE_WITHOUT_HEAD(invalid)
ATF_TEST_CASE_BODY(invalid)232 ATF_TEST_CASE_BODY(invalid)
233 {
234 	auto const invalid_addresses = std::vector{
235 		// Invalid separator
236 		":1/2/3"s,
237 		"ix0:1/2/3"s,
238 
239 		// Multiple different separators
240 		":1.2-3"s,
241 		"ix0:1.2-3"s,
242 
243 		// An IP address
244 		":10.1.2.200/28"s,
245 		"ix0:10.1.2.200/28"s,
246 
247 		// Valid address followed by garbage
248 		":1.2.3xxx"s,
249 		":1.2.3.xxx"s,
250 		"ix0:1.2.3xxx"s,
251 		"ix0:1.2.3.xxx"s,
252 	};
253 
254 	for (auto const &addr : invalid_addresses) {
255 		int ret;
256 
257 		auto sdl = sockaddr_dl{};
258 		sdl.sdl_len = sizeof(sdl);
259 
260 		ret = link_addr(addr.c_str(), &sdl);
261 		ATF_REQUIRE_EQ(-1, ret);
262 	}
263 }
264 
265 /*
266  * Test some non-Ethernet addresses.
267  */
268 ATF_TEST_CASE_WITHOUT_HEAD(nonether)
ATF_TEST_CASE_BODY(nonether)269 ATF_TEST_CASE_BODY(nonether)
270 {
271 	sockaddr_dl sdl;
272 
273 	/* A short address */
274 	sdl = make_linkaddr(":1:23:cc");
275 	ATF_REQUIRE_EQ("", ifname(sdl));
276 	ATF_REQUIRE_EQ("1.23.cc"s, ::link_ntoa(&sdl));
277 	ATF_REQUIRE_EQ(3, sdl.sdl_alen);
278 	ATF_REQUIRE_EQ(true,
279 	    std::ranges::equal(std::vector{0x01u, 0x23u, 0xccu}, lladdr(sdl)));
280 
281 	/* A long address */
282 	sdl = make_linkaddr(":1:23:cc:a:b:c:d:e:f");
283 	ATF_REQUIRE_EQ("", ifname(sdl));
284 	ATF_REQUIRE_EQ("1.23.cc.a.b.c.d.e.f"s, ::link_ntoa(&sdl));
285 	ATF_REQUIRE_EQ(9, sdl.sdl_alen);
286 	ATF_REQUIRE_EQ(true, std::ranges::equal(
287 	    std::vector{0x01u, 0x23u, 0xccu, 0xau, 0xbu, 0xcu, 0xdu, 0xeu, 0xfu},
288 	    lladdr(sdl)));
289 }
290 
291 /*
292  * Test link_addr behaviour with undersized buffers.
293  */
294 ATF_TEST_CASE_WITHOUT_HEAD(smallbuf)
ATF_TEST_CASE_BODY(smallbuf)295 ATF_TEST_CASE_BODY(smallbuf)
296 {
297 	static constexpr auto garbage = std::byte{0xcc};
298 	auto buf = std::vector<std::byte>();
299 	sockaddr_dl *sdl;
300 	int ret;
301 
302 	/*
303 	 * Make an sdl with an sdl_data member of the appropriate size, and
304 	 * place it in buf.  Ensure it's followed by a trailing byte of garbage
305 	 * so we can test that link_addr doesn't write past the end.
306 	 */
307 	auto mksdl = [&buf](std::size_t datalen) -> sockaddr_dl * {
308 		auto actual_size = datalen + offsetof(sockaddr_dl, sdl_data);
309 
310 		buf.resize(actual_size + 1);
311 		std::ranges::fill(buf, garbage);
312 
313 		auto *sdl = new(reinterpret_cast<sockaddr_dl *>(&buf[0]))
314 		    sockaddr_dl;
315 		sdl->sdl_len = actual_size;
316 		return (sdl);
317 	};
318 
319 	/* An sdl large enough to store the interface name */
320 	sdl = mksdl(3);
321 	ret = link_addr("ix0:1.2.3", sdl);
322 	ATF_REQUIRE(*rbegin(buf) == garbage);
323 	ATF_REQUIRE_EQ(-1, ret);
324 	ATF_REQUIRE_EQ(ENOSPC, errno);
325 	ATF_REQUIRE_EQ(3, sdl->sdl_nlen);
326 	ATF_REQUIRE_EQ("ix0", ifname(*sdl));
327 	ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
328 
329 	/*
330 	 * For these tests, test both with and without an interface name, since
331 	 * that will overflow the buffer in different places.
332 	 */
333 
334 	/* An empty sdl.  Nothing may grow on this cursed ground. */
335 
336 	sdl = mksdl(0);
337 	ret = link_addr("ix0:1.2.3", sdl);
338 	ATF_REQUIRE(*rbegin(buf) == garbage);
339 	ATF_REQUIRE_EQ(-1, ret);
340 	ATF_REQUIRE_EQ(ENOSPC, errno);
341 	ATF_REQUIRE_EQ(0, sdl->sdl_nlen);
342 	ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
343 
344 	sdl = mksdl(0);
345 	ret = link_addr(":1.2.3", sdl);
346 	ATF_REQUIRE(*rbegin(buf) == garbage);
347 	ATF_REQUIRE_EQ(-1, ret);
348 	ATF_REQUIRE_EQ(ENOSPC, errno);
349 	ATF_REQUIRE_EQ(0, sdl->sdl_nlen);
350 	ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
351 
352 	/*
353 	 * An sdl large enough to store the interface name and two octets of the
354 	 * address.
355 	 */
356 
357 	sdl = mksdl(5);
358 	ret = link_addr("ix0:1.2.3", sdl);
359 	ATF_REQUIRE(*rbegin(buf) == garbage);
360 	ATF_REQUIRE_EQ(-1, ret);
361 	ATF_REQUIRE_EQ(ENOSPC, errno);
362 	ATF_REQUIRE_EQ("ix0", ifname(*sdl));
363 	ATF_REQUIRE(std::ranges::equal(
364 	    std::vector{ 0x01, 0x02 }, lladdr(*sdl)));
365 
366 	sdl = mksdl(2);
367 	ret = link_addr(":1.2.3", sdl);
368 	ATF_REQUIRE(*rbegin(buf) == garbage);
369 	ATF_REQUIRE_EQ(-1, ret);
370 	ATF_REQUIRE_EQ(ENOSPC, errno);
371 	ATF_REQUIRE_EQ("", ifname(*sdl));
372 	ATF_REQUIRE(std::ranges::equal(
373 	    std::vector{ 0x01, 0x02 }, lladdr(*sdl)));
374 
375 	/*
376 	 * An sdl large enough to store the entire address.
377 	 */
378 
379 	sdl = mksdl(6);
380 	ret = link_addr("ix0:1.2.3", sdl);
381 	ATF_REQUIRE(*rbegin(buf) == garbage);
382 	ATF_REQUIRE_EQ(0, ret);
383 	ATF_REQUIRE_EQ("ix0", ifname(*sdl));
384 	ATF_REQUIRE(std::ranges::equal(
385 	    std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));
386 
387 	sdl = mksdl(3);
388 	ret = link_addr(":1.2.3", sdl);
389 	ATF_REQUIRE(*rbegin(buf) == garbage);
390 	ATF_REQUIRE_EQ(0, ret);
391 	ATF_REQUIRE_EQ("", ifname(*sdl));
392 	ATF_REQUIRE(std::ranges::equal(
393 	    std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));
394 }
395 
396 /*
397  * Test an extremely long address which would overflow link_ntoa's internal
398  * buffer.  It should handle this by truncating the output.
399  * (Test for SA-16:37.libc / CVE-2016-6559.)
400  */
401 ATF_TEST_CASE_WITHOUT_HEAD(overlong)
ATF_TEST_CASE_BODY(overlong)402 ATF_TEST_CASE_BODY(overlong)
403 {
404 	auto sdl = make_linkaddr(
405 	    ":01.02.03.04.05.06.07.08.09.0a.0b.0c.0d.0e.0f."
406 	    "11.12.13.14.15.16.17.18.19.1a.1b.1c.1d.1e.1f."
407 	    "22.22.23.24.25.26.27.28.29.2a.2b.2c.2d.2e.2f");
408 
409 	ATF_REQUIRE_EQ(
410 	    "1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.11.12.13.14.15.16.17.18.19.1a.1b."s,
411 	    ::link_ntoa(&sdl));
412 }
413 
414 /*
415  * Test link_ntoa_r, the re-entrant version of link_ntoa().
416  */
417 ATF_TEST_CASE_WITHOUT_HEAD(link_ntoa_r)
ATF_TEST_CASE_BODY(link_ntoa_r)418 ATF_TEST_CASE_BODY(link_ntoa_r)
419 {
420 	static constexpr char garbage = 0x41;
421 	std::vector<char> buf;
422 	sockaddr_dl sdl;
423 	size_t len;
424 	int ret;
425 
426 	// Return the contents of buf as a string, using the NUL terminator to
427 	// determine length.  This is to ensure we're using the return value in
428 	// the same way C code would, but we do a bit more verification to
429 	// elicit a test failure rather than a SEGV if it's broken.
430 	auto bufstr = [&buf]() -> std::string_view {
431 		// Find the NUL.
432 		auto end = std::ranges::find(buf, '\0');
433 		ATF_REQUIRE(end != buf.end());
434 
435 		// Intentionally chopping the NUL off.
436 		return {begin(buf), end};
437 	};
438 
439 	// Resize the buffer and set the contents to a known garbage value, so
440 	// we don't accidentally have a NUL in the right place when link_ntoa_r
441 	// didn't put it there.
442 	auto resetbuf = [&buf, &len](std::size_t size) {
443 		len = size;
444 		buf.resize(len);
445 		std::ranges::fill(buf, garbage);
446 	};
447 
448 	// Test a short address with a large buffer.
449 	sdl = make_linkaddr("ix0:1.2.3");
450 	resetbuf(64);
451 	ret = ::link_ntoa_r(&sdl, &buf[0], &len);
452 	ATF_REQUIRE_EQ(0, ret);
453 	ATF_REQUIRE_EQ(10, len);
454 	ATF_REQUIRE_EQ("ix0:1.2.3"s, bufstr());
455 
456 	// Test a buffer which is exactly the right size.
457 	sdl = make_linkaddr("ix0:1.2.3");
458 	resetbuf(10);
459 	ret = ::link_ntoa_r(&sdl, &buf[0], &len);
460 	ATF_REQUIRE_EQ(0, ret);
461 	ATF_REQUIRE_EQ(10, len);
462 	ATF_REQUIRE_EQ("ix0:1.2.3"sv, bufstr());
463 
464 	// Test various short buffers, using a table of buffer length and the
465 	// output we expect.  All of these should produce valid but truncated
466 	// strings, with a trailing NUL and with buflen set correctly.
467 
468 	auto buftests = std::vector<std::pair<std::size_t, std::string_view>>{
469 		{1u, ""sv},
470 		{2u, ""sv},
471 		{3u, ""sv},
472 		{4u, "ix0"sv},
473 		{5u, "ix0:"sv},
474 		{6u, "ix0:1"sv},
475 		{7u, "ix0:1."sv},
476 		{8u, "ix0:1.2"sv},
477 		{9u, "ix0:1.2."sv},
478 	};
479 
480 	for (auto const &[buflen, expected] : buftests) {
481 		sdl = make_linkaddr("ix0:1.2.3");
482 		resetbuf(buflen);
483 		ret = ::link_ntoa_r(&sdl, &buf[0], &len);
484 		ATF_REQUIRE_EQ(-1, ret);
485 		ATF_REQUIRE_EQ(10, len);
486 		ATF_REQUIRE_EQ(expected, bufstr());
487 	}
488 
489 	// Test a NULL buffer, which should just set buflen.
490 	sdl = make_linkaddr("ix0:1.2.3");
491 	len = 0;
492 	ret = ::link_ntoa_r(&sdl, NULL, &len);
493 	ATF_REQUIRE_EQ(-1, ret);
494 	ATF_REQUIRE_EQ(10, len);
495 
496 	// A NULL buffer with a non-zero length should also be accepted.
497 	sdl = make_linkaddr("ix0:1.2.3");
498 	len = 64;
499 	ret = ::link_ntoa_r(&sdl, NULL, &len);
500 	ATF_REQUIRE_EQ(-1, ret);
501 	ATF_REQUIRE_EQ(10, len);
502 
503 	// Test a non-NULL buffer, but with a length of zero.
504 	sdl = make_linkaddr("ix0:1.2.3");
505 	resetbuf(1);
506 	len = 0;
507 	ret = ::link_ntoa_r(&sdl, &buf[0], &len);
508 	ATF_REQUIRE_EQ(-1, ret);
509 	ATF_REQUIRE_EQ(10, len);
510 	// Check we really didn't write anything.
511 	ATF_REQUIRE_EQ(garbage, buf[0]);
512 
513 	// Test a buffer which would be truncated in the middle of a two-digit
514 	// hex octet, which should not write the truncated octet at all.
515 	sdl = make_linkaddr("ix0:1.22.3");
516 	resetbuf(8);
517 	ret = ::link_ntoa_r(&sdl, &buf[0], &len);
518 	ATF_REQUIRE_EQ(-1, ret);
519 	ATF_REQUIRE_EQ(11, len);
520 	ATF_REQUIRE_EQ("ix0:1."sv, bufstr());
521 }
522 
ATF_INIT_TEST_CASES(tcs)523 ATF_INIT_TEST_CASES(tcs)
524 {
525 	ATF_ADD_TEST_CASE(tcs, basic);
526 	ATF_ADD_TEST_CASE(tcs, ifname);
527 	ATF_ADD_TEST_CASE(tcs, smallbuf);
528 	ATF_ADD_TEST_CASE(tcs, invalid);
529 	ATF_ADD_TEST_CASE(tcs, nonether);
530 	ATF_ADD_TEST_CASE(tcs, overlong);
531 	ATF_ADD_TEST_CASE(tcs, link_ntoa_r);
532 }
533