Version in base suite: 1.9.15-0+deb13u1 Base version: dnsdist_1.9.15-0+deb13u1 Target version: dnsdist_1.9.16-0+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/d/dnsdist/dnsdist_1.9.15-0+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/d/dnsdist/dnsdist_1.9.16-0+deb13u1.dsc configure | 20 +++++++++---------- configure.ac | 2 - debian/changelog | 7 ++++++ dnsdist-nghttp2-in.cc | 12 +++++++++++ dnsdist-nghttp2-in.hh | 1 dnsdist-tcp-upstream.hh | 3 +- dnsdist-tcp.cc | 49 ++++++++++++++++++++++++++---------------------- dnsdist.1 | 2 - dnsname.cc | 13 ++++++++---- iputils.cc | 7 +++++- iputils.hh | 9 ++++++-- test-iputils_hh.cc | 23 ++++++++++++++++++++++ 12 files changed, 106 insertions(+), 42 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpek43jb6q/dnsdist_1.9.15-0+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpek43jb6q/dnsdist_1.9.16-0+deb13u1.dsc: no acceptable signature found diff -Nru dnsdist-1.9.15/configure dnsdist-1.9.16/configure --- dnsdist-1.9.15/configure 2026-06-09 07:55:41.000000000 +0000 +++ dnsdist-1.9.16/configure 2026-08-05 14:29:27.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for dnsdist 1.9.15. +# Generated by GNU Autoconf 2.71 for dnsdist 1.9.16. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -618,8 +618,8 @@ # Identity of this package. PACKAGE_NAME='dnsdist' PACKAGE_TARNAME='dnsdist' -PACKAGE_VERSION='1.9.15' -PACKAGE_STRING='dnsdist 1.9.15' +PACKAGE_VERSION='1.9.16' +PACKAGE_STRING='dnsdist 1.9.16' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1645,7 +1645,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures dnsdist 1.9.15 to adapt to many kinds of systems. +\`configure' configures dnsdist 1.9.16 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1716,7 +1716,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of dnsdist 1.9.15:";; + short | recursive ) echo "Configuration of dnsdist 1.9.16:";; esac cat <<\_ACEOF @@ -1951,7 +1951,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -dnsdist configure 1.9.15 +dnsdist configure 1.9.16 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2440,7 +2440,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by dnsdist $as_me 1.9.15, which was +It was created by dnsdist $as_me 1.9.16, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3932,7 +3932,7 @@ # Define the identity of the package. PACKAGE='dnsdist' - VERSION='1.9.15' + VERSION='1.9.16' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -28627,7 +28627,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by dnsdist $as_me 1.9.15, which was +This file was extended by dnsdist $as_me 1.9.16, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -28695,7 +28695,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -dnsdist config.status 1.9.15 +dnsdist config.status 1.9.16 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff -Nru dnsdist-1.9.15/configure.ac dnsdist-1.9.16/configure.ac --- dnsdist-1.9.15/configure.ac 2026-06-09 07:55:30.000000000 +0000 +++ dnsdist-1.9.16/configure.ac 2026-08-05 14:29:19.000000000 +0000 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([dnsdist], [1.9.15]) +AC_INIT([dnsdist], [1.9.16]) AM_INIT_AUTOMAKE([foreign tar-ustar dist-bzip2 no-dist-gzip parallel-tests 1.11 subdir-objects]) AM_SILENT_RULES([yes]) AC_CONFIG_MACRO_DIR([m4]) diff -Nru dnsdist-1.9.15/debian/changelog dnsdist-1.9.16/debian/changelog --- dnsdist-1.9.15/debian/changelog 2026-06-16 22:17:43.000000000 +0000 +++ dnsdist-1.9.16/debian/changelog 2026-08-07 16:09:22.000000000 +0000 @@ -1,3 +1,10 @@ +dnsdist (1.9.16-0+deb13u1) trixie-security; urgency=medium + + * New upstream version 1.9.16, fixing security issue + CVE-2026-52682 + + -- Chris Hofstaedtler Fri, 07 Aug 2026 18:09:22 +0200 + dnsdist (1.9.15-0+deb13u1) trixie-security; urgency=medium * New upstream version 1.9.15, fixing security issues diff -Nru dnsdist-1.9.15/dnsdist-nghttp2-in.cc dnsdist-1.9.16/dnsdist-nghttp2-in.cc --- dnsdist-1.9.15/dnsdist-nghttp2-in.cc 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/dnsdist-nghttp2-in.cc 2026-08-05 14:29:01.000000000 +0000 @@ -1250,10 +1250,22 @@ return now; } +void IncomingHTTP2Connection::updateIO(IOState newState, const timeval& now) +{ + (void)now; + updateIO(newState, newState == IOState::NeedWrite ? handleWritableIOCallback : handleReadableIOCallback); +} + void IncomingHTTP2Connection::updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback) { boost::optional ttd{boost::none}; + if (newState == IOState::Async) { + auto shared = shared_from_this(); + updateIOForAsync(shared); + return; + } + auto shared = std::dynamic_pointer_cast(shared_from_this()); if (!shared || !d_ioState) { return; diff -Nru dnsdist-1.9.15/dnsdist-nghttp2-in.hh dnsdist-1.9.16/dnsdist-nghttp2-in.hh --- dnsdist-1.9.15/dnsdist-nghttp2-in.hh 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/dnsdist-nghttp2-in.hh 2026-08-05 14:29:01.000000000 +0000 @@ -89,6 +89,7 @@ void stopIO(); std::unordered_map::iterator getStreamContext(StreamID streamID); uint32_t getConcurrentStreamsCount() const; + void updateIO(IOState newState, const timeval& now) override; void updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback); void handleIOError(); bool sendResponse(StreamID streamID, PendingQuery& context, uint16_t responseCode, const HeadersMap& customResponseHeaders, const std::string& contentType = "", bool addContentType = true); diff -Nru dnsdist-1.9.15/dnsdist-tcp-upstream.hh dnsdist-1.9.16/dnsdist-tcp-upstream.hh --- dnsdist-1.9.15/dnsdist-tcp-upstream.hh 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/dnsdist-tcp-upstream.hh 2026-08-05 14:29:01.000000000 +0000 @@ -124,12 +124,13 @@ static void handleIOCallback(int desc, FDMultiplexer::funcparam_t& param); static void handleAsyncReady(int desc, FDMultiplexer::funcparam_t& param); - static void updateIO(std::shared_ptr& state, IOState newState, const struct timeval& now); static void queueResponse(std::shared_ptr& state, const struct timeval& now, TCPResponse&& response, bool fromBackend); static void handleTimeout(std::shared_ptr& state, bool write); + static void updateIOForAsync(std::shared_ptr& conn); virtual void handleIO(); + virtual void updateIO(IOState newState, const timeval& now); QueryProcessingResult handleQuery(PacketBuffer&& query, const struct timeval& now, std::optional streamID); virtual void handleResponse(const struct timeval& now, TCPResponse&& response) override; diff -Nru dnsdist-1.9.15/dnsdist-tcp.cc dnsdist-1.9.16/dnsdist-tcp.cc --- dnsdist-1.9.15/dnsdist-tcp.cc 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/dnsdist-tcp.cc 2026-08-05 14:29:01.000000000 +0000 @@ -432,7 +432,7 @@ // for the same reason we need to update the state right away, nobody will do that for us if (state->active()) { - updateIO(state, iostate, now); + state->updateIO(iostate, now); // if we have not finished reading every available byte, we _need_ to do an actual read // attempt before waiting for the socket to become readable again, because if there is // buffered data available the socket might never become readable again. @@ -474,18 +474,23 @@ } } -void IncomingTCPConnectionState::updateIO(std::shared_ptr& state, IOState newState, const struct timeval& now) +void IncomingTCPConnectionState::updateIOForAsync(std::shared_ptr& conn) { - if (newState == IOState::Async) { - auto fds = state->d_handler.getAsyncFDs(); - for (const auto desc : fds) { - state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state); - } - state->d_ioState->update(IOState::Done, handleIOCallback, state); + auto fds = conn->d_handler.getAsyncFDs(); + for (const auto desc : fds) { + conn->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, conn); } - else { - state->d_ioState->update(newState, handleIOCallback, state, newState == IOState::NeedWrite ? state->getClientWriteTTD(now) : state->getClientReadTTD(now)); + conn->d_ioState->update(IOState::Done, handleIOCallback, conn); +} + +void IncomingTCPConnectionState::updateIO(IOState newState, const struct timeval& now) +{ + auto sharedPtrToConn = shared_from_this(); + if (newState == IOState::Async) { + updateIOForAsync(sharedPtrToConn); + return; } + d_ioState->update(newState, handleIOCallback, sharedPtrToConn, newState == IOState::NeedWrite ? getClientWriteTTD(now) : getClientReadTTD(now)); } /* called from the backend code when a new response has been received */ @@ -1213,12 +1218,12 @@ return; } - auto state = shared_from_this(); + auto sharedPtrToConn = shared_from_this(); if (iostate == IOState::Done) { - d_ioState->update(iostate, handleIOCallback, state); + d_ioState->update(iostate, handleIOCallback, sharedPtrToConn); } else { - updateIO(state, iostate, now); + updateIO(iostate, now); } ioGuard.release(); } while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !d_lastIOBlocked); @@ -1233,21 +1238,21 @@ return; } - std::shared_ptr state = shared_from_this(); - --state->d_currentQueriesCount; - state->d_hadErrors = true; + auto sharedPtrToConn = shared_from_this(); + --sharedPtrToConn->d_currentQueriesCount; + sharedPtrToConn->d_hadErrors = true; - if (state->d_state == State::sendingResponse) { + if (sharedPtrToConn->d_state == State::sendingResponse) { /* if we have responses to send, let's do that first */ } - else if (!state->d_queuedResponses.empty()) { + else if (!sharedPtrToConn->d_queuedResponses.empty()) { /* stop reading and send what we have */ try { - auto iostate = sendQueuedResponses(state, now); + auto iostate = sendQueuedResponses(sharedPtrToConn, now); - if (state->active() && iostate != IOState::Done) { + if (sharedPtrToConn->active() && iostate != IOState::Done) { // we need to update the state right away, nobody will do that for us - updateIO(state, iostate, now); + updateIO(iostate, now); } } catch (const std::exception& e) { @@ -1256,7 +1261,7 @@ } else { // the backend code already tried to reconnect if it was possible - state->terminateClientConnection(); + sharedPtrToConn->terminateClientConnection(); } } diff -Nru dnsdist-1.9.15/dnsdist.1 dnsdist-1.9.16/dnsdist.1 --- dnsdist-1.9.15/dnsdist.1 2026-06-09 07:56:31.000000000 +0000 +++ dnsdist-1.9.16/dnsdist.1 2026-08-05 14:29:58.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSDIST" "1" "Jun 09, 2026" "" "dnsdist" +.TH "DNSDIST" "1" "Aug 05, 2026" "" "dnsdist" .SH NAME dnsdist \- A DNS and DoS aware, scriptable loadbalancer .SH SYNOPSIS diff -Nru dnsdist-1.9.15/dnsname.cc dnsdist-1.9.16/dnsname.cc --- dnsdist-1.9.15/dnsname.cc 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/dnsname.cc 2026-08-05 14:29:01.000000000 +0000 @@ -127,6 +127,8 @@ size_t DNSName::parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t pos, bool uncompress) { const size_t initialPos = pos; + auto existingSize = d_storage.size(); + const size_t neededSizeForFinalLabel = /* final empty label length */ (existingSize == 0 ? 1U : 0U); size_t totalLength = 0; unsigned char labellen = 0; @@ -151,24 +153,27 @@ throw std::range_error("Found an invalid label length in qname (only one of the first two bits is set)"); } checkLabelLength(labellen); - // reserve one byte for the label length - if (totalLength + labellen > s_maxDNSNameLength - 1) { + + // reserve one byte for the label length, plus one byte for the final empty label if we were empty before + if ((existingSize + totalLength + labellen + 1U + neededSizeForFinalLabel) > s_maxDNSNameLength) { throw std::range_error("name too long to append"); } if (pos + labellen >= view.size()) { throw std::range_error("Found an invalid label length in qname"); } pos += labellen; - totalLength += 1 + labellen; + totalLength += 1U + labellen; } while (pos < view.size()); if (totalLength != 0) { - auto existingSize = d_storage.size(); if (existingSize > 0) { // remove the last label count, we are about to override it */ --existingSize; } + if ((existingSize + totalLength + 1U) > s_maxDNSNameLength) { + throw std::range_error("name too long to append"); + } d_storage.reserve(existingSize + totalLength + 1); d_storage.resize(existingSize + totalLength); memcpy(&d_storage.at(existingSize), &view.at(initialPos), totalLength); diff -Nru dnsdist-1.9.15/iputils.cc dnsdist-1.9.16/iputils.cc --- dnsdist-1.9.15/iputils.cc 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/iputils.cc 2026-08-05 14:29:01.000000000 +0000 @@ -358,7 +358,12 @@ memset(start + len - tozero/8, 0, tozero/8); // blot out the whole bytes on the right - auto bitsleft=tozero % 8; // 2 bits left to clear + auto bitsleft = tozero % 8; // 2 bits left to clear + if (bitsleft == 0) { + // the memset() above cleared whole bytes only, and for 0 bits the byte we + // would look at is the one before the address + return; + } // a b c d, to truncate to 22 bits, we just zeroed 'd' and need to zero 2 bits from c // so and by '11111100', which is ~((1<<2)-1) = ~3 diff -Nru dnsdist-1.9.15/iputils.hh dnsdist-1.9.16/iputils.hh --- dnsdist-1.9.15/iputils.hh 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/iputils.hh 2026-08-05 14:29:01.000000000 +0000 @@ -604,8 +604,13 @@ } } // still here, now match remaining bits - uint8_t bits= d_bits % 8; - uint8_t mask= (uint8_t) ~(0xFF>>bits); + uint8_t bits = d_bits % 8; + if (bits == 0) { + // no partial byte left to match, and lhs[index] would be one past the + // address for a /128 + return true; + } + auto mask = static_cast(~(0xFF >> bits)); return((us[n]) == (them[n] & mask)); } diff -Nru dnsdist-1.9.15/test-iputils_hh.cc dnsdist-1.9.16/test-iputils_hh.cc --- dnsdist-1.9.15/test-iputils_hh.cc 2026-06-09 07:55:15.000000000 +0000 +++ dnsdist-1.9.16/test-iputils_hh.cc 2026-08-05 14:29:01.000000000 +0000 @@ -144,6 +144,19 @@ ca6.truncate(8); BOOST_CHECK_EQUAL(ca6.toString(), "2000::"); + /* truncating to 0 bits leaves no partial byte, so nothing before the address + may be touched */ + ca4 = ComboAddress("130.161.252.29", 53); + ca4.truncate(0); + BOOST_CHECK_EQUAL(ca4.toString(), "0.0.0.0"); + BOOST_CHECK_EQUAL(ca4.sin4.sin_port, htons(53)); + + ca6 = ComboAddress("2001:888:2000:1d::2", 53); + ca6.sin6.sin6_flowinfo = htonl(0x0badcafe); + ca6.truncate(0); + BOOST_CHECK_EQUAL(ca6.toString(), "::"); + BOOST_CHECK_EQUAL(ca6.sin6.sin6_flowinfo, htonl(0x0badcafe)); + orig=ca6=ComboAddress("2001:888:2000:1d::2"); for(int n=128; n; --n) { @@ -218,6 +231,16 @@ BOOST_CHECK(nmp6.match("fe80::92fb:a6ff:fe4a:51da")); BOOST_CHECK(!nmp6.match("fe81::92fb:a6ff:fe4a:51db")); + /* a /128 leaves no partial byte to compare, so the scope id sitting right + after the address must not be taken into account */ + ComboAddress scoped("fe80::92fb:a6ff:fe4a:51da"); + scoped.sin6.sin6_scope_id = 1; + Netmask nmscoped(scoped); + BOOST_CHECK_EQUAL(nmscoped.getBits(), 128); + BOOST_CHECK(nmscoped.match(scoped)); + BOOST_CHECK(nmscoped.match("fe80::92fb:a6ff:fe4a:51da")); + BOOST_CHECK(!nmscoped.match("fe80::92fb:a6ff:fe4a:51db")); + Netmask all("0.0.0.0/0"); BOOST_CHECK(all.match(local) && all.match(remote));