From 3ea905b98b8756f0652a9626a98384e96bab3a97 Mon Sep 17 00:00:00 2001 From: Dominik Date: Thu, 19 Mar 2026 22:33:02 +0100 Subject: [PATCH] rfc3315: fix integer underflow and heap overflow in log6_opts STATUS_CODE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In log6_opts(), the OPTION6_STATUS_CODE branch did: memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2); daemon->namebuff[len + opt6_len(opt) - 2] = 0; Two bugs: 1. Integer underflow: if opt6_len(opt) < 2, the subtraction wraps to a huge size_t, making memcpy write gigabytes and crash with SIGSEGV. 2. Heap overflow: no upper-bound check. opt6_len() can return up to 65535, but daemon->namebuff is only (MAXDNAME*2)+1 = 2051 bytes. A STATUS_CODE payload larger than ~2045 bytes would overflow the heap buffer. Exploitability: log6_opts() is only ever called on daemon->outpacket, the outgoing DHCPv6 reply built by dnsmasq itself — NOT on incoming client packets. Consequently, these bugs are NOT directly triggerable by a remote attacker under the current code. The fix is defensive: put_opt6_short() (which writes STATUS_CODE options in outgoing packets) always produces opt6_len >= 2, so neither bug fires in practice today. However, the unchecked arithmetic is a latent hazard that could become exploitable if future refactoring passes incoming option data through log6_opts(): - Guard the block with opt6_len(opt) >= 2 to prevent underflow. - Cap slen to the remaining space in daemon->namebuff. Signed-off-by: Dominik --- src/rfc3315.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/rfc3315.c b/src/rfc3315.c index 66d9192..2fa46d9 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -2037,9 +2037,15 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op } else if (type == OPTION6_STATUS_CODE) { - int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2)); - memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2); - daemon->namebuff[len + opt6_len(opt) - 2] = 0; + if (opt6_len(opt) >= 2) + { + int slen = opt6_len(opt) - 2; + int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2)); + if (slen > (MAXDNAME * 2) - len - 1) + slen = (MAXDNAME * 2) - len - 1; + memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), slen); + daemon->namebuff[len + slen] = 0; + } optname = "status"; } else