[PATCH 2.42 7/7] resolv: Add test case tst-ns_sprintrr (bug 34033, bug 34069)
Jaipaul Cheernam <[email protected]> Mon, 3 Aug 2026 16:23:27 +0200
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <[email protected]> |
From: Florian Weimer <[email protected]> This test case covers both input buffer overreads and output buffer overflows. It should systematically cover these issues. I used code auto-generation for updating the test expectations for truncated RDATA in TXT, ISDN records, after writing the rest of the test by hand. Assisted-by: LLM Reviewed-by: Carlos O'Donell <[email protected]> Reviewed-by: Adhemerval Zanella <[email protected]> (cherry picked from commit 4ba0b79b9596e5a4951cc9eaa1546a55e543e083) Signed-off-by: Jaipaul Cheernam <[email protected]> --- resolv/Makefile | 2 + resolv/tst-ns_sprintrr.c | 329 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 resolv/tst-ns_sprintrr.c diff --git a/resolv/Makefile b/resolv/Makefile index 088a22ea18..c6d73b411c 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -98,6 +98,7 @@ tests += \ tst-ns_name \ tst-ns_name_compress \ tst-ns_name_pton \ + tst-ns_sprintrr \ tst-res_hconf_reorder \ tst-res_hnok \ tst-resolv-aliases \ @@ -331,5 +332,6 @@ $(objpfx)tst-ns_name: $(objpfx)libresolv.so $(objpfx)tst-ns_name.out: tst-ns_name.data $(objpfx)tst-ns_name_compress: $(objpfx)libresolv.so $(objpfx)tst-ns_name_pton: $(objpfx)libresolv.so +$(objpfx)tst-ns_sprintrr: $(objpfx)libresolv.so $(objpfx)tst-res_hnok: $(objpfx)libresolv.so $(objpfx)tst-p_secstodate: $(objpfx)libresolv.so diff --git a/resolv/tst-ns_sprintrr.c b/resolv/tst-ns_sprintrr.c new file mode 100644 index 0000000000..34739b5924 --- /dev/null +++ b/resolv/tst-ns_sprintrr.c @@ -0,0 +1,329 @@ +/* Tests for the ns_sprintrr function. + Copyright (C) 2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <arpa/nameser.h> + +#include <alloc_buffer.h> +#include <arpa/inet.h> +#include <libc-diag.h> +#include <stdbool.h> +#include <string.h> +#include <support/check.h> +#include <support/next_to_fault.h> + +#include <stdio.h> + +/* Regions that test_one_record uses for input and output. */ +static struct support_next_to_fault ntf_in; +static struct support_next_to_fault ntf_out; + +/* This is used by test_one_record to construct the packet. */ +static const char packet_prefix[] = + /* DNS response with one question, one answer record. */ + "AA\x81\x80\0\1\0\1\0\0\0\0" + /* Question: www.example.org/IN/ANY. */ + "\3www\7example\3org\0\0\xff\0\1" + /* Response: compression reference. */ + "\xc0\x0c"; + +/* Use ns_sprintrr to format a DNS record (starting with + packet_prefix) of type RTYPE, with a record payload of RDATALEN + bytes starting at RDATA. Check successful formatting against + EXPECTED. Try various truncated input and output buffers to catch + overreads and buffer overflows, using ntf_in and ntf_out above. */ +static void +test_one_record (uint16_t rtype, const char *rdata, size_t rdatalen, + const char *expected) +{ + struct rr_header + { + uint16_t typ; + uint16_t cls; + uint32_t ttl; + uint16_t rdatalen; + uint16_t pad; + } hdr = + { + .typ = htons (rtype), + .cls = htons (ns_c_in), + .ttl = htonl (86400), /* One day. */ + .rdatalen = htons (rdatalen), + }; + enum { hdrlen = offsetof (struct rr_header, pad) }; + TEST_COMPARE (hdrlen, 10); + + /* Construct the packet from packet_prefix, hdr, and rdata. */ + unsigned char packet[512]; + size_t packetlen; + { + struct alloc_buffer buf = alloc_buffer_create (packet, sizeof (packet)); + alloc_buffer_copy_bytes (&buf, packet_prefix, sizeof (packet_prefix) - 1); + alloc_buffer_copy_bytes (&buf, &hdr, hdrlen); + alloc_buffer_copy_bytes (&buf, rdata, rdatalen); + packetlen = sizeof (packet) - alloc_buffer_size (&buf); + } + + /* Parse the record. */ + ns_msg msg; + TEST_COMPARE (ns_initparse (packet, packetlen, &msg), 0); + ns_rr rr; + TEST_COMPARE (ns_parserr (&msg, ns_s_an, 0, &rr), 0); + + /* Try sizes up to this limit. Go a bit beyond the expected size to + check for errors. */ + size_t max_result_size = strlen (expected) + 16; + + bool success = false; + for (size_t result_size = 1; result_size <= max_result_size; ++result_size) + { + char *result_start = ntf_out.buffer + ntf_out.length - result_size; + memset (result_start, 'X', result_size); + + /* ns_sprintrr was deprecated in 2.34. */ + DIAG_PUSH_NEEDS_COMMENT; + DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations"); + int ret = ns_sprintrr (&msg, &rr, NULL, NULL, result_start, result_size); + DIAG_POP_NEEDS_COMMENT; + + if (ret > 0) + { + TEST_COMPARE_STRING (result_start, expected); + TEST_COMPARE (ret, strlen (expected)); + success = true; + } + else + { + TEST_VERIFY (!success); + TEST_COMPARE (ret, -1); + } + } + TEST_VERIFY (success); + + /* Test with truncated RDATA. */ + for (size_t rdata_size = 0; rdata_size <= rdatalen; ++rdata_size) + { + size_t truncated_packet_size = packetlen - rdatalen + rdata_size; + unsigned char *packet_start + = ((unsigned char *) ntf_in.buffer + ntf_in.length + - truncated_packet_size); + memcpy (packet_start, packet, truncated_packet_size); + /* Patch in the updated RDATA length field. */ + uint16_t new_rdatalen = htons (rdata_size); + memcpy (packet_start + truncated_packet_size - rdata_size - 2, + &new_rdatalen, 2); + + ns_msg msg; + TEST_COMPARE (ns_initparse (packet_start, truncated_packet_size, &msg), + 0); + ns_rr rr; + TEST_COMPARE (ns_parserr (&msg, ns_s_an, 0, &rr), 0); + + size_t result_size = strlen (expected) + 1; + char *result_start = ntf_out.buffer + ntf_out.length - result_size; + memset (result_start, 'X', result_size); + + /* ns_sprintrr was deprecated in 2.34. */ + DIAG_PUSH_NEEDS_COMMENT; + DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations"); + int ret = ns_sprintrr (&msg, &rr, NULL, NULL, result_start, result_size); + DIAG_POP_NEEDS_COMMENT; + + /* This flag indicates whether the output is syntactically + correct. In some cases, truncation may still yield a valid + payload. */ + bool broken = rdata_size < rdatalen; + switch (rtype) + { + case ns_t_wks: + /* WKS records use all trailing bytes for the port bitmap. */ + broken = rdata_size < 5; + break; + case ns_t_nsap: + /* Uses all bytes that are available. */ + broken = false; + break; + case ns_t_txt: + /* Truncation produces a valid payload if it occurs right + after a complete string in the TXT payload. */ + broken = false; + for (size_t pos = 0; pos < rdata_size; ) + { + unsigned int slen = rdata[pos] & 0xff; + if (pos + 1 + slen > rdata_size) + { + broken = true; + break; + } + pos += 1 + slen; + } + break; + case ns_t_isdn: + /* The second field is optional. If it is present, it must + not be truncated. */ + broken = rdata_size < 6 || (rdata_size > 6 && rdata_size < rdatalen); + break; + case ns_t_a6: + /* The first A6 subtest contains a trailing domain name, + which is ignored and not formatted. */ + if (rdata_size > 0 && rdata[0] == 0) + broken = rdata_size < 17; + break; + case ns_t_cert: + case ns_t_tkey: + case ns_t_tsig: + /* Only generic printing, which does not validate anything. */ + broken = false; + break; + } + + if (broken) + { + if (strstr (result_start, "RR format error") != NULL) + /* No further checks if an error indicator has been added + to the output. */ + ; + else + TEST_COMPARE (ret, -1); + } + else + TEST_VERIFY (ret > 0); + } +} + +static int +do_test (void) +{ + ntf_in = support_next_to_fault_allocate (512); + ntf_out = support_next_to_fault_allocate (256); + +#define T(rtype, rdata, expected) \ + test_one_record (rtype, rdata, sizeof (rdata) - 1, expected) + T (ns_t_a, "\xc0\0\2\1", "www.example.org.\t1D IN A\t\t192.0.2.1"); + T (ns_t_cname, "\4www1\4prod\xc0\x10", + "www.example.org.\t1D IN CNAME\twww1.prod.example.org."); + T (ns_t_hinfo, "\5first\6second", + "www.example.org.\t1D IN HINFO\t\"first\" \"second\""); + T (ns_t_isdn, "\5first\6second", + "www.example.org.\t1D IN ISDN\t\"first\" \"second\""); + /* Bug: Extra space at the end in the text representation of ISDN RRs. */ + T (ns_t_isdn, "\5first", "www.example.org.\t1D IN ISDN\t\"first\" "); + T (ns_t_soa, + "\2ns\xc0\x10\12hostmaster\xc0\x10" + "\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0\5", + "www.example.org.\t1D IN SOA\tns.example.org. hostmaster.example.org. (\n" + "\t\t\t\t\t1\t\t; serial\n" + "\t\t\t\t\t2S\t\t; refresh\n" + "\t\t\t\t\t3S\t\t; retry\n" + "\t\t\t\t\t4S\t\t; expiry\n" + "\t\t\t\t\t5S )\t\t; minimum\n"); + T (ns_t_mx, "\0\xa\2mx\xc0\x10", + "www.example.org.\t1D IN MX\t10 mx.example.org."); + T (ns_t_px, "\0\xa\3px1\xc0\x10\3px2\xc0\x10", + "www.example.org.\t1D IN PX\t10 px1.example.org. px2.example.org."); + T (ns_t_x25, "\4X.25", + "www.example.org.\t1D IN X25\t\"X.25\""); + T (ns_t_txt, "\1A\2BC\3DEF", + "www.example.org.\t1D IN TXT\t\"A\" \"BC\" \"DEF\""); + T (ns_t_nsap, "", + "www.example.org.\t1D IN NSAP\t"); + T (ns_t_nsap, "\1", + "www.example.org.\t1D IN NSAP\t01"); + T (ns_t_nsap, "\1\2", + "www.example.org.\t1D IN NSAP\t01.02"); + T (ns_t_nsap, "\1\2\3", + "www.example.org.\t1D IN NSAP\t01.0203"); + T (ns_t_nsap, "\1\2\3\4", + "www.example.org.\t1D IN NSAP\t01.0203.04"); + T (ns_t_nsap, + "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32" + "\33\34\35\36\37\40\41\42\43\44\45\46\47\50\51\52\53\54\55\56\57\60\61" + "\62\63\64\65\66\67\70\71\72\73\74\75\76\77\100\101\102\103\104\105\106" + "\107\110\111\112\113\114\115\116\117\120\121\122\123\124\125\126\127" + "\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147\150" + "\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171" + "\172\173\174\175\176\177\200\201\202\203\204\205\206\207\210\211\212" + "\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233" + "\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254" + "\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275" + "\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316" + "\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" + "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360" + "\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377", + "www.example.org.\t1D IN NSAP\t" + "01.0203.0405.0607.0809.0A0B.0C0D.0E0F.1011.1213.1415.1617.1819.1A1B" + ".1C1D.1E1F.2021.2223.2425.2627.2829.2A2B.2C2D.2E2F.3031.3233.3435.3637" + ".3839.3A3B.3C3D.3E3F.4041.4243.4445.4647.4849.4A4B.4C4D.4E4F.5051.5253" + ".5455.5657.5859.5A5B.5C5D.5E5F.6061.6263.6465.6667.6869.6A6B.6C6D.6E6F" + ".7071.7273.7475.7677.7879.7A7B.7C7D.7E7F.8081.8283.8485.8687.8889.8A8B" + ".8C8D.8E8F.9091.9293.9495.9697.9899.9A9B.9C9D.9E9F.A0A1.A2A3.A4A5.A6A7" + ".A8A9.AAAB.ACAD.AEAF.B0B1.B2B3.B4B5.B6B7.B8B9.BABB.BCBD.BEBF.C0C1.C2C3" + ".C4C5.C6C7.C8C9.CACB.CCCD.CECF.D0D1.D2D3.D4D5.D6D7.D8D9.DADB.DCDD.DEDF" + ".E0E1.E2E3.E4E5.E6E7.E8E9.EAEB.ECED.EEEF.F0F1.F2F3.F4F5.F6F7.F8F9.FAFB" + ".FCFD.FEFF"); + T (ns_t_aaaa, "\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34", + "www.example.org.\t1D IN AAAA\t2001:db8::1234"); + /* Example from RFC 1876. The loc_ntoa format is different from the + official text representation. */ + T (ns_t_loc, + "\000\063\026\023\211\027\055\320\160\276\025\360\000\230\215\040", + "www.example.org.\t1D IN LOC" + "\t42 21 54.000 N 71 06 18.000 W -24.00m 30.00m 10000.00m 10.00m"); + T (ns_t_naptr, + "\0\1\0\2\5flags\7service\2.*\5naptr\xc0\x10", + "www.example.org.\t1D IN NAPTR\t1 2 \"flags\" \"service\" \".*\"" + " naptr.example.org."); + T (ns_t_srv, + "\0\1\0\2\0\x50\4www1\xc0\x10", + "www.example.org.\t1D IN SRV\t1 2 80 www1.example.org."); + T (ns_t_rp, "\3rp1\xc0\x10\3rp2\xc0\x10", + "www.example.org.\t1D IN RP\trp1.example.org. rp2.example.org."); + T (ns_t_wks, "\xc0\0\2\1\6\0\0\0\0\0\0\0\0\0\0\200", + "www.example.org.\t1D IN WKS\t192.0.2.1 6 ( \n\t\t\t\t80 )"); + T (ns_t_cert, "\0\1\x04\xd2\0blob", + "www.example.org.\t1D IN CERT\t\\# 9 (\n" + "\t00 01 04 d2 00 62 6c 6f 62 )\t\t\t; .....blob"); + T (ns_t_tkey, "\4algo\0\0\0\0\1\0\0\0\2\0\3\0\4" + "\0\5\xa1\xa2\xa3\xa4\xa5\0\3\xb1\xb2\xb3", + "www.example.org.\t1D IN TYPE249\t\\# 30 (\n" + "\t04 61 6c 67 6f 00 00 00 00 01 00 00 00 02 00 03 ; .algo...........\n" + "\t00 04 00 05 a1 a2 a3 a4 a5 00 03 b1 b2 b3 )\t; .............."); + T (ns_t_tsig, "\4algo\0" + "\0\20\xdd\xcd\x64\x10\xe9\x21\x34\x1a\x8e\xe0\xa1\x9a\x30\xfc\x3b\xd1" + "\0\2\0\3\0\5other", + "www.example.org.\t1D IN TSIG\t\\# 35 (\n" + "\t04 61 6c 67 6f 00 00 10 dd cd 64 10 e9 21 34 1a ; .algo.....d..!4.\n" + "\t8e e0 a1 9a 30 fc 3b d1 00 02 00 03 00 05 6f 74 ; ....0.;.......ot\n" + "\t68 65 72 )\t\t\t\t\t; her"); + T (ns_t_a6, + "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34\6prefix\xc0\x10", + "www.example.org.\t1D IN A6\t0 2001:db8::1234"); + T (ns_t_a6, + "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x35", + "www.example.org.\t1D IN A6\t0 2001:db8::1235"); + T (ns_t_a6, "\200\6prefix\xc0\x10", + "www.example.org.\t1D IN A6\t128 prefix.example.org."); + T (ns_t_a6, "\x20\0\0\0\0\0\0\0\0\0\0\x12\x36\6prefix\xc0\x10", + "www.example.org.\t1D IN A6\t32 ::1236 prefix.example.org."); +#undef T + + support_next_to_fault_free (&ntf_in); + support_next_to_fault_free (&ntf_out); + return 0; +} + +#include <support/test-driver.c>