<div dir="ltr"><p>Hi all,</p><p>I am reporting an issue found in <code>src/util.c</code>. This is a pointer underflow vulnerability in the <code>hostname_issubdomain()</code> function, which I have verified on the latest development version: <b>dnsmasq 2.93test4-11-gcf08eee</b>.</p><h3>Summary</h3><p>The function <code>hostname_issubdomain(char *a, char *b)</code> fails to handle cases where the parameter <code>b</code> is an empty string. When <code>b</code> is empty, the pointer <code>bp</code> underflows during the first iteration of the <code>do-while</code> loop, leading to an out-of-bounds read.</p><h3>Root Cause Analysis</h3><p>In <code>src/util.c</code> (around line 436 in the current master):</p><span class="gmail-"><span class="gmail-ng-tns-c2858875814-254 gmail-ng-star-inserted"><div class="gmail-code-block gmail-ng-tns-c2858875814-254 gmail-ng-animate-disabled gmail-ng-trigger gmail-ng-trigger-codeBlockRevealAnimation"><div class="gmail-formatted-code-block-internal-container gmail-ng-tns-c2858875814-254"><div class="gmail-animated-opacity gmail-ng-tns-c2858875814-254"><pre class="gmail-ng-tns-c2858875814-254"><code role="text" class="gmail-code-container gmail-formatted gmail-ng-tns-c2858875814-254">  <span class="gmail-hljs-comment">/* move to the end */</span>
  <span class="gmail-hljs-keyword">for</span> (bp = b; *bp; bp++); <span class="gmail-hljs-comment">// If b is "", bp remains equal to b</span>
  ...
  <span class="gmail-hljs-keyword">do</span>
    {
      c1 = (<span class="gmail-hljs-keyword">unsigned</span> <span class="gmail-hljs-keyword">char</span>) *(--ap);
      c2 = (<span class="gmail-hljs-keyword">unsigned</span> <span class="gmail-hljs-keyword">char</span>) *(--bp);  <span class="gmail-hljs-comment">// <span class="gmail-hljs-doctag">BUG:</span> bp becomes b-1 (Underflow)</span>
      ...
    } <span class="gmail-hljs-keyword">while</span> (bp != b); <span class="gmail-hljs-comment">// Since bp is already < b, this condition stays true</span>
</code></pre></div></div></div></span></span><p>When <code>b</code> is an empty string (length 0), the initialization loop for <code>bp</code> does nothing. The subsequent <code>do-while</code> loop immediately decrements <code>bp</code> to <code>b-1</code>, reading memory outside the allocated buffer.</p><h3>Trigger Path & Verification</h3><p>This can be triggered by sending a CHAOS class DNS query for the root domain (<code>.</code>):</p><ol start="1"><li><p><code>extract_name()</code> parses the root domain as an empty string <code>""</code>.</p></li><li><p>In <code>src/rfc1035.c</code>, it calls <code>hostname_issubdomain("bind", name)</code>.</p></li><li><p>With <code>name</code> as <code>""</code>, the underflow occurs.</p></li></ol><p><b>Reproducer:</b>
<code>dig @<a href="http://127.0.0.1">127.0.0.1</a> -p [PORT] -c CH -t TXT .</code></p><h3>Severity & Impact</h3><p>I performed dynamic testing with >100,000 queries. While a persistent infinite loop (DoS) is difficult to achieve because the loop usually breaks when <code>c1 != c2</code> (random stack data), Valgrind consistently reports an <b>Invalid read of size 1</b>.</p><p>This memory safety issue leads to <b>Undefined Behavior</b> and potential minor information leakage from the stack or heap area immediately preceding the buffer.</p><h3>Evidence (Valgrind Output)</h3><span class="gmail-"><span class="gmail-ng-tns-c2858875814-255 gmail-ng-star-inserted"><div class="gmail-code-block gmail-ng-tns-c2858875814-255 gmail-ng-animate-disabled gmail-ng-trigger gmail-ng-trigger-codeBlockRevealAnimation"><div class="gmail-code-block-decoration gmail-header-formatted gmail-gds-title-s gmail-ng-tns-c2858875814-255 gmail-ng-star-inserted">==73== Invalid read of size 1<br>==73==    at 0x11E139: hostname_issubdomain (in /root/dnsmasq/src/dnsmasq)<br>==73==    by 0x11B424: answer_request (in /root/dnsmasq/src/dnsmasq)<br>==73==    by 0x12FE31: receive_query (in /root/dnsmasq/src/dnsmasq)<br>==73==    by 0x1129D4: main (in /root/dnsmasq/src/dnsmasq)<br>==73==  Address 0x4a8b48f is 1 bytes before a block of size 2,051 alloc'd<br>==73==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)<br>==73==    by 0x11DE34: safe_malloc (in /root/dnsmasq/src/dnsmasq)<br>==73==    by 0x12AB68: read_opts (in /root/dnsmasq/src/dnsmasq)<br>==73==    by 0x1113F1: main (in /root/dnsmasq/src/dnsmasq)</div></div></span></span></div>