[5/5] resolv: Add test case tst-ns_sprintrr (bug 34033, bug 34069)

Message ID c0191a0afbfd6837bc7bc1b2695eaacf3e41b0fe.1777546194.git.fweimer@redhat.com (mailing list archive)
State Under Review
Delegated to: Carlos O'Donell
Headers
Series Fixes for CVE-2026-5435, CVE-2026-6238 |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
redhat-pt-bot/TryBot-32bit success Build for i686

Commit Message

Florian Weimer April 30, 2026, 10:52 a.m. UTC
  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, CERT records, after writing the rest
of the test by hand.

Assisted-by: LLM
---
 resolv/Makefile          |   2 +
 resolv/tst-ns_sprintrr.c | 322 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 324 insertions(+)
 create mode 100644 resolv/tst-ns_sprintrr.c
  

Patch

diff --git a/resolv/Makefile b/resolv/Makefile
index 971608eff5..4b61d2ce98 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -108,6 +108,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 \
@@ -338,5 +339,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..522d835fe6
--- /dev/null
+++ b/resolv/tst-ns_sprintrr.c
@@ -0,0 +1,322 @@ 
+/* 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>
+
+/* 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),
+    };
+  size_t hdrlen = offsetof (struct rr_header, pad);
+  TEST_COMPARE (hdrlen, 10);
+
+  /* Construct the packet from packet_prefix, hdr, and rdata.  */
+  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 ((unsigned char *) 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;
+      char *packet_start
+        = 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 ((unsigned char *) 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_cert:
+          /* The 5-byte header is sufficient. Any available trailing
+             data is base64-encoded.  */
+          broken = rdata_size < 5;
+          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;
+        }
+
+      if (broken)
+        {
+          if (strstr (result_start, "RR format error") != NULL)
+            /* No further checks if an error indicator has been added
+               to the output.  */
+            ;
+          /* TKEY and TSIG implementations are incomplete.  */
+          else if (rtype != ns_t_tkey && rtype != ns_t_tsig)
+            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\t1 1234 0  YmxvYg==");
+  /* Bug: TKEY output is incomplete.  */
+  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 249\talgo. 1 2 3 4 5 ");
+  /* Bug: Not implemented properly.  */
+  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\talgo.");
+  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 38\t0 2001:db8::1234");
+  T (ns_t_a6,
+     "\0\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\x12\x34",
+     "www.example.org.\t1D IN 38\t0 2001:db8::1234");
+  T (ns_t_a6, "\200\6prefix\xc0\x10",
+     "www.example.org.\t1D IN 38\t128  prefix.example.org.");
+  T (ns_t_a6, "\x20\0\0\0\0\0\0\0\0\0\0\x12\x34\6prefix\xc0\x10",
+     "www.example.org.\t1D IN 38\t32 ::1234 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>