NLnetLabs / ldns

LDNS is a DNS library that facilitates DNS tool programming
https://nlnetlabs.nl/ldns
BSD 3-Clause "New" or "Revised" License
285 stars 94 forks source link

[BUG] Resolver uses nameserver commented out in /etc/resolv.conf #237

Closed grembo closed 2 months ago

grembo commented 2 months ago

TL;DR

Comment parsing in /etc/resolv.conf is broken since 889f7c7195bbd3

How to reproduce

Create a basic resolv.conf containing comments:

cat<<EOF >/etc/resolv.conf
# x

# nameserver 8.8.8.8
EOF

Then run drill on some zone:

drill wikipedia.org

Expected outcome

Since there are no nameservers configured, this outcome would be expected:

Error: error sending query: No (valid) nameservers defined in the resolver

Actual outcome

The commented out nameserver entry is used:

;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 17996
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 
;; QUESTION SECTION:
;; wikipedia.org.   IN  A

;; ANSWER SECTION:
wikipedia.org.  77  IN  A   185.15.59.224

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 20 msec
;; SERVER: 8.8.8.8
;; WHEN: Fri May  3 15:40:07 2024
;; MSG SIZE  rcvd: 47

Analysis

Commit 889f7c7195bbd306f8c introduced this change:

diff --git a/parse.c b/parse.c
index 3c737620..9698ba71 100644
--- a/parse.c
+++ b/parse.c
@@ -187,6 +187,9 @@ ldns_fget_token_l_st(FILE *f, char **token, size_t *limit, bool fixed
                if (c != '\0' && c != '\n') {
                        *t++ = c;
                }
+               if (c == '\n' && line_nr) {
+                       *line_nr = *line_nr + 1;
+               }
                if (c == '\\' && prev_c == '\\')
                        prev_c = 0;
                else    prev_c = c;

Which breaks comment handling in resolver.c, which only removes characters until the end of line, in case line_nr doesn't change:

                if (word[0] == '#') {
                        word[0]='x';
                        if(oldline == *line_nr) {
                                /* skip until end of line */
                                int c;
                                do {
                                        c = fgetc(myfp);
                                } while(c != EOF && c != '\n');
                                if(c=='\n') (*line_nr)++;
                        }
                        /* and read next to prepare for further parsing */
                        oldline = *line_nr;
                        continue;
                }

Potential Fixes

The parser isn't necessarily the kind of code one just jumps into and reasons about, yet the three suggestions below worked for my limited use case:

  1. Rollback 889f7c7195bbd306f8c (from the commit it's unclear, if it fixed a cosmetic or a functional issue)
  2. Make ldns_fget_token_l_st understand comments starting with # (unclear which side-effects this might have)

    diff --git a/parse.c b/parse.c
    index 9698ba71..a2478f66 100644
    --- a/parse.c
    +++ b/parse.c
    @@ -98,7 +98,7 @@ ldns_fget_token_l_st(FILE *f, char **token, size_t *limit, bool fixed
                }
    
                /* do something with comments ; */
    -               if (c == ';' && quoted == 0) {
    +               if ((c == ';' || c == '#') && quoted == 0) {
                        if (prev_c != '\\') {
                                com = 1;
                        }
  3. Patch resolver.c's comment handling (again, might have unwanted side effects):
    diff --git a/resolver.c b/resolver.c
    index f9ec65a5..e8921dbc 100644
    --- a/resolver.c
    +++ b/resolver.c
    @@ -815,14 +815,12 @@ ldns_resolver_new_frm_fp_l(ldns_resolver **res, FILE *fp, int *line_nr)
                /* check comments */
                if (word[0] == '#') {
                         word[0]='x';
    -                        if(oldline == *line_nr) {
                                 /* skip until end of line */
                                 int c;
                                 do {
                                         c = fgetc(myfp);
                                 } while(c != EOF && c != '\n');
                                 if(c=='\n') (*line_nr)++;
    -                        }
                        /* and read next to prepare for further parsing */
                         oldline = *line_nr;
                        continue;

Real life resolv.conf

This is the configuration where we first encountered issues (it would request a local zone from google DNS and fail with NXDOMAIN, even though we would expect the call to be handled by unbound running locally)

# Generated by resolvconf
# nameserver 192.168.1.1

# nameserver 8.8.8.8
nameserver 127.0.0.1
options edns0
grembo commented 2 months ago

FreeBSD bug tracker: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=278721

grembo commented 2 months ago

Hopefully found a title that makes more sense now 😄

wtoorop commented 2 months ago
  1. Make ldns_fget_token_l_st understand comments starting with # (unclear which side-effects this might have)

Thank you @grembo . I prefer that one, and then remove all comment handling from resolver.c. I just added a bit more (in PR #238) so that zonefile parsing does not accept comments to start with '#' too.

grembo commented 2 months ago

Fixed by #238, thank you guys!

wtoorop commented 2 months ago

And thank you for reporting!

@dag-erling I've put a bugfix release on the calender for June.