Hello!

I have sent similar proposal already in year 2021 [1]. But I have reworked that a bit to reuse existing --local-service option and just add new parameter to it. If --local-service=host is used, dnsmasq will bind to addresses on lo interface only. It will not even open port on other interfaces, preventing possible scanning of running service from outside.

It roughly becomes similar default like other resolvers without configuration use. BIND9 or unbound will listen also on localhost only.

To avoid regressions, it still becomes inactive when any --interface, --listen-address or similar is specified at least once. Then you have to explicitly use --interface=lo to listen *also* on localhost.

The change is related to Fedora bug #1852373 [2], also newly re-opened CVE-2020-14312 issue for RHEL8 [3]. Having explicitly specified bind-interfaces & interface=lo in dnsmasq default configuration has resulted in multiple regressions across different packages, which did not rewrite distribution provided configuration. I think it could be useful also for others.

What do you think?

Looking for any feedback!

Regards,
Petr

1. https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2021q4/015749.html
2. https://bugzilla.redhat.com/show_bug.cgi?id=1852373
3. https://issues.redhat.com/browse/RHEL-9516

--
Petr Menšík
Software Engineer, RHEL
Red Hat, https://www.redhat.com/
PGP: DFCF908DB7C87E8E529925BC4931CA5B6C9FC5CB

From fa6c187cb12d275b6cffc0d7659ed6dd9ae06b1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemen...@redhat.com>
Date: Tue, 5 Oct 2021 13:46:51 +0200
Subject: [PATCH] Introduce new --local-service=host parameter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Similar to local-service, but more strict. Listen only on localhost
unless other interface is specified. Has no effect when interface is
provided explicitly. I had multiple bugs fillen on Fedora, because I have
changed default configuration to:

interface=lo
bind-interfaces

People just adding configuration parts to /etc/dnsmasq.d or appending to
existing configuration often fail to see some defaults are already there.
Give them auto-ignored configuration as smart default.

Signed-off-by: Petr Menšík <pemen...@redhat.com>

Do not add a new parameter on command line. Instead add just parameter
for behaviour modification of existing local-service option. Now it
accepts two optional values:
- net: exactly the same as before
- host: bind only to lo interface, do not listen on any other addresses
  than loopback.
---
 man/dnsmasq.8 |  8 +++++---
 src/dnsmasq.c |  2 ++
 src/dnsmasq.h |  3 ++-
 src/option.c  | 44 +++++++++++++++++++++++++++++++++-----------
 4 files changed, 42 insertions(+), 15 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 6d37360..0f0f0c8 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -270,14 +270,16 @@ the address dnsmasq is listening on. When an interface is specified,
 it may be qualified with "/4" or "/6" to specify only the IPv4 or IPv6
 addresses associated with the interface. Since any defined authoritative zones are also available as part of the normal recusive DNS service supplied by dnsmasq, it can make sense to have an --auth-server declaration with no interfaces or address, but simply specifying the primary external nameserver.
 .TP
-.B --local-service
+.B --local-service[=net|host]
+Without parameter or with net parameter, restricts service to connected network.
 Accept DNS queries only from hosts whose address is on a local subnet,
-ie a subnet for which an interface exists on the server. This option
+ie a subnet for which an interface exists on the server. With host parameter, listens
+only on lo interface and accepts queries from localhost only. This option
 only has effect if there are no \fB--interface\fP, \fB--except-interface\fP,
 \fB--listen-address\fP or \fB--auth-server\fP options. It is intended to be set as
 a default on installation, to allow unconfigured installations to be
 useful but also safe from being used for DNS amplification attacks.
-.TP 
+.TP
 .B \-2, --no-dhcp-interface=<interface name>
 Do not provide DHCP, TFTP or router advertisement on the specified interface, but do provide DNS service.
 .TP
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 65ba334..83174cf 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -861,6 +861,8 @@ int main (int argc, char **argv)
 
       if (option_bool(OPT_LOCAL_SERVICE))
 	my_syslog(LOG_INFO, _("DNS service limited to local subnets"));
+      else if (option_bool(OPT_LOCALHOST_SERVICE))
+	my_syslog(LOG_INFO, _("DNS service limited to localhost"));
     }
   
   my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts);
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 67c083b..d6985b6 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -281,7 +281,8 @@ struct event_desc {
 #define OPT_NORR           69
 #define OPT_NO_IDENT       70
 #define OPT_CACHE_RR       71
-#define OPT_LAST           72
+#define OPT_LOCALHOST_SERVICE  72
+#define OPT_LAST           73
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
diff --git a/src/option.c b/src/option.c
index 9423582..5988ca3 100644
--- a/src/option.c
+++ b/src/option.c
@@ -222,7 +222,7 @@ static const struct myoption opts[] =
     { "domain-suffix", 1, 0, 's' },
     { "interface", 1, 0, 'i' },
     { "listen-address", 1, 0, 'a' },
-    { "local-service", 0, 0, LOPT_LOCAL_SERVICE },
+    { "local-service", 2, 0, LOPT_LOCAL_SERVICE },
     { "bogus-priv", 0, 0, 'b' },
     { "bogus-nxdomain", 1, 0, 'B' },
     { "ignore-address", 1, 0, LOPT_IGNORE_ADDR },
@@ -573,7 +573,7 @@ static struct {
   { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
   { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
   { LOPT_LOG_DEBUG, OPT_LOG_DEBUG, NULL, gettext_noop("Log debugging information."), NULL }, 
-  { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL },
+  { LOPT_LOCAL_SERVICE, ARG_ONE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL },
   { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops."), NULL },
   { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, 
   { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL }, 
@@ -1411,6 +1411,16 @@ static void dhcp_opt_free(struct dhcp_opt *opt)
   free(opt);
 }
 
+static void if_names_add(const char *ifname)
+{
+  struct iname *new = opt_malloc(sizeof(struct iname));
+  new->next = daemon->if_names;
+  daemon->if_names = new;
+  /* new->name may be NULL if someone does
+     "interface=" to disable all interfaces except loop. */
+  new->name = opt_string_alloc(ifname);
+  new->flags = 0;
+}
 
 /* This is too insanely large to keep in-line in the switch */
 static int parse_dhcp_opt(char *errstr, char *arg, int flags)
@@ -2835,14 +2845,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
       
     case 'i':  /* --interface */
       do {
-	struct iname *new = opt_malloc(sizeof(struct iname));
-	comma = split(arg);
-	new->next = daemon->if_names;
-	daemon->if_names = new;
-	/* new->name may be NULL if someone does
-	   "interface=" to disable all interfaces except loop. */
-	new->name = opt_string_alloc(arg);
-	new->flags = 0;
+        comma = split(arg);
+	if_names_add(arg);
 	arg = comma;
       } while (arg);
       break;
@@ -3408,6 +3412,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	ret_err(gen_err);
       else if (daemon->max_logs > 100)
 	daemon->max_logs = 100;
+      break;
+
+    case LOPT_LOCAL_SERVICE:  /* --local-service */
+      if (!arg || !strcmp(arg, "net"))
+	set_option_bool(OPT_LOCAL_SERVICE);
+      else if (!strcmp(arg, "host"))
+	set_option_bool(OPT_LOCALHOST_SERVICE);
+      else
+	ret_err(gen_err);
       break;  
 
     case 'P': /* --edns-packet-max */
@@ -6152,7 +6165,16 @@ void read_opts(int argc, char **argv, char *compile_opts)
   /* If there's access-control config, then ignore --local-service, it's intended
      as a system default to keep otherwise unconfigured installations safe. */
   if (daemon->if_names || daemon->if_except || daemon->if_addrs || daemon->authserver)
-    reset_option_bool(OPT_LOCAL_SERVICE); 
+    {
+      reset_option_bool(OPT_LOCAL_SERVICE);
+      reset_option_bool(OPT_LOCALHOST_SERVICE);
+    }
+  else if (option_bool(OPT_LOCALHOST_SERVICE) && !option_bool(OPT_LOCAL_SERVICE))
+    {
+      /* listen only on localhost, emulate --interface=lo --bind-interfaces */
+      if_names_add(NULL);
+      set_option_bool(OPT_NOWILD);
+    }
 
   if (testmode)
     {
-- 
2.43.0

Attachment: OpenPGP_0x4931CA5B6C9FC5CB.asc
Description: OpenPGP public key

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to