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

Message ID fe15e94efb7c54f4a996cc17ec2fead0a10ae615.1781627187.git.fweimer@redhat.com (mailing list archive)
State Accepted
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
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Florian Weimer June 16, 2026, 4:32 p.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 records, after writing the rest
of the test by hand.

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

Comments

Adhemerval Zanella June 17, 2026, 4:16 p.m. UTC | #1
On 16/06/26 13:32, Florian Weimer wrote:
> 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

LGTM, some suggestions below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  resolv/Makefile          |   2 +
>  resolv/tst-ns_sprintrr.c | 328 +++++++++++++++++++++++++++++++++++++++
>  2 files changed, 330 insertions(+)
>  create mode 100644 resolv/tst-ns_sprintrr.c
> 
> diff --git a/resolv/Makefile b/resolv/Makefile
> index 68b3a4dbf3..02cc751732 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 \

Ok.

> @@ -341,5 +342,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..9083d28d2d
> --- /dev/null
> +++ b/resolv/tst-ns_sprintrr.c
> @@ -0,0 +1,328 @@
> +/* 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),
> +    };
> +  size_t hdrlen = offsetof (struct rr_header, pad);
> +  TEST_COMPARE (hdrlen, 10);

I haven't checked this on all supported ABIs, but this seems to be true for
all supported ABIs.  I think you can make it a _Static_assert instead.

> +
> +  /* Construct the packet from packet_prefix, hdr, and rdata.  */
> +  char packet[512];

I think you can make this unsigned and remove the cast below.

> +  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");

Maybe use 12.0, since it is the current minimal support version anyway.

> +      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;

I think you can make it unsigned and remove the cast below.

> +      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");

Same as before.

> +      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>
  
Carlos O'Donell June 17, 2026, 7:49 p.m. UTC | #2
On 6/16/26 12:32 PM, Florian Weimer wrote:
> 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

LGTM.

Despite the GNU Project's current request not to include additional
LLM code into projects, this is just a test, and as such I consider this
the lowest risk which can be removed. I'm approving this as GNU Project
maintainer to have coverage for the change. No runtime code is added that
is based on LLM generated code.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>


> ---
>   resolv/Makefile          |   2 +
>   resolv/tst-ns_sprintrr.c | 328 +++++++++++++++++++++++++++++++++++++++
>   2 files changed, 330 insertions(+)
>   create mode 100644 resolv/tst-ns_sprintrr.c
> 
> diff --git a/resolv/Makefile b/resolv/Makefile
> index 68b3a4dbf3..02cc751732 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 \

OK.

>     tst-res_hconf_reorder \
>     tst-res_hnok \
>     tst-resolv-aliases \
> @@ -341,5 +342,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

OK.

>   $(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..9083d28d2d
> --- /dev/null
> +++ b/resolv/tst-ns_sprintrr.c
> @@ -0,0 +1,328 @@
> +/* 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)
> +{

OK.

> +  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.  */

OK. Agreed.

> +      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;

OK

> +          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>
  
Florian Weimer June 19, 2026, 2:50 p.m. UTC | #3
* Adhemerval Zanella Netto:

> On 16/06/26 13:32, Florian Weimer wrote:
>> 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
>
> LGTM, some suggestions below.
>
> Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
>
>> ---
>>  resolv/Makefile          |   2 +
>>  resolv/tst-ns_sprintrr.c | 328 +++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 330 insertions(+)
>>  create mode 100644 resolv/tst-ns_sprintrr.c
>> 
>> diff --git a/resolv/Makefile b/resolv/Makefile
>> index 68b3a4dbf3..02cc751732 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 \
>
> Ok.
>
>> @@ -341,5 +342,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..9083d28d2d
>> --- /dev/null
>> +++ b/resolv/tst-ns_sprintrr.c
>> @@ -0,0 +1,328 @@
>> +/* 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),
>> +    };
>> +  size_t hdrlen = offsetof (struct rr_header, pad);
>> +  TEST_COMPARE (hdrlen, 10);
>
> I haven't checked this on all supported ABIs, but this seems to be true for
> all supported ABIs.  I think you can make it a _Static_assert instead.

It has to be 10.  But hdrlen is not a constant expression, so I would
have to repeat it.  I'm going to change it to enum constant.

>> +  /* Construct the packet from packet_prefix, hdr, and rdata.  */
>> +  char packet[512];
>
> I think you can make this unsigned and remove the cast below.

Yeah, unsigned char * leads to fewer casts in the end.

>> +  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");
>
> Maybe use 12.0, since it is the current minimal support version
> anyway.

Not sure what the rules for permanent exemptions are.  Should I use
16.1?

Thanks,
Florian
  
Adhemerval Zanella June 19, 2026, 4:26 p.m. UTC | #4
On 19/06/26 11:50, Florian Weimer wrote:

> 
>>> +  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");
>>
>> Maybe use 12.0, since it is the current minimal support version
>> anyway.
> 
> Not sure what the rules for permanent exemptions are.  Should I use
> 16.1?
My understanding is the first supported version this warning happens, so 
we can easily remove them if/when we raise de minimum supported version.
  
Florian Weimer June 19, 2026, 4:41 p.m. UTC | #5
* Adhemerval Zanella Netto:

> On 19/06/26 11:50, Florian Weimer wrote:
>
>> 
>>>> +  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");
>>>
>>> Maybe use 12.0, since it is the current minimal support version
>>> anyway.
>> 
>> Not sure what the rules for permanent exemptions are.  Should I use
>> 16.1?

> My understanding is the first supported version this warning happens, so 
> we can easily remove them if/when we raise de minimum supported version.

But this doesn't really apply here.  It's not a compiler deficiency we
working around, but an expectation deprecation message triggered by our
headers.  Leaving it at 4.9 may actually be helpful because it's not
something we want to delete when we increase the baseline from GCC 12 to
GCC 13.

Thanks,
Florian
  
Adhemerval Zanella June 19, 2026, 4:54 p.m. UTC | #6
On 19/06/26 13:41, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
> 
>> On 19/06/26 11:50, Florian Weimer wrote:
>>
>>>
>>>>> +  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");
>>>>
>>>> Maybe use 12.0, since it is the current minimal support version
>>>> anyway.
>>>
>>> Not sure what the rules for permanent exemptions are.  Should I use
>>> 16.1?
> 
>> My understanding is the first supported version this warning happens, so 
>> we can easily remove them if/when we raise de minimum supported version.
> 
> But this doesn't really apply here.  It's not a compiler deficiency we
> working around, but an expectation deprecation message triggered by our
> headers.  Leaving it at 4.9 may actually be helpful because it's not
> something we want to delete when we increase the baseline from GCC 12 to
> GCC 13.

Ok, that's make sense then.
  

Patch

diff --git a/resolv/Makefile b/resolv/Makefile
index 68b3a4dbf3..02cc751732 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 \
@@ -341,5 +342,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..9083d28d2d
--- /dev/null
+++ b/resolv/tst-ns_sprintrr.c
@@ -0,0 +1,328 @@ 
+/* 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),
+    };
+  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_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>