dnsmasq received SIGSEGV!
Jan screwed up!
Simon says: `fortune`
New patch in attachment
reproducer:
set up a line like: server=/somedomain/$some_ip
add an regex address match
query for somedomain
Depending on order of config statements &&/|| if $some_ip could be reached
dnsmasq dies in forward.c because that part is not "regex ready" (NULL deref,
because regex do not have a domain string).
Jepp, i have totally overseen this interaction (like prop. many other).
Solution:
Let's also match server lines with a regex!
I also changed the order in which an "if" is evaluated, after a little run in my
brains logic evaluator this should be a NOP (besides doing a cheap flag test
before an expensive string compare/regex compare), but i could be wrong...
Regex matches on "server" do not try to find the longest match, this could give
you some funky problems if you use very elaborate matching rules.
Maybe i will take that server match out again, it's enough if dnsmasq does not
crash in that place...
Nearby, do never ever set a regex on "local", dragons be there...
Which reminds me to state:
Patch is only lightly tested in my small environment, there are prop. a million
bugs left (as before), this is just a quick fix for the problem at hand.
Take this incident is a friendly little remainder that this change is a deep
poke into dnsmasq's internals.
I better do not touch any source code ever again...
New patch against 2.52 attached. Apply with -p1.
Also with conditional compilation, so don't forget to set HAVE_REGEX to you
COPTS like:
make COPTS=-DHAVE_REGEX
or uncomment it in config.h
Greetings
Jan
--
John encountered the following Zen-like line in his generated XML:
<>There is no phenotype</>
He was enlightened.
=== modified file 'Makefile'
--- upstream/Makefile 2010-01-23 19:48:37 +0000
+++ regex/Makefile 2010-01-23 19:49:07 +0000
@@ -32,8 +32,9 @@
PO = po
MAN = man
-DNSMASQ_CFLAGS=`echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1`
-DNSMASQ_LIBS= `echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1`
+DNSMASQ_CFLAGS=`echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1`
+DNSMASQ_LIBS= `echo $(COPTS) | ../bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1`
+DNSMASQ_LIBS +=`echo $(COPTS) | ../bld/pkg-wrapper HAVE_REGEX $(PKG_CONFIG) --libs libpcre`
SUNOS_LIBS= `if uname | grep SunOS 2>&1 >/dev/null; then echo -lsocket -lnsl -lposix4; fi`
OBJS = cache.o rfc1035.o util.o option.o forward.o network.o \
=== modified file 'src/config.h'
--- upstream/src/config.h 2010-01-23 19:48:37 +0000
+++ regex/src/config.h 2010-01-23 19:49:07 +0000
@@ -147,6 +147,10 @@
define some methods to allow (re)configuration of the upstream DNS
servers via DBus.
+HAVE_REGEX
+ Define this if you want to link against lib pcre to get regex
+ support in "address=" matches
+
NOTES:
For Linux you should define
HAVE_LINUX_NETWORK
@@ -171,6 +175,7 @@
#define HAVE_SCRIPT
/* #define HAVE_BROKEN_RTC */
/* #define HAVE_DBUS */
+/* #define HAVE_REGEX */
/* Allow TFTP to be disabled with COPTS=-DNO_TFTP */
#ifdef NO_TFTP
=== modified file 'src/dnsmasq.c'
--- upstream/src/dnsmasq.c 2010-01-23 19:48:37 +0000
+++ regex/src/dnsmasq.c 2010-01-23 19:49:08 +0000
@@ -37,6 +37,10 @@
"no-"
#endif
"DBus "
+#ifndef HAVE_REGEX
+"no-"
+#endif
+"regex "
#ifndef LOCALEDIR
"no-"
#endif
=== modified file 'src/dnsmasq.h'
--- upstream/src/dnsmasq.h 2010-01-23 19:48:37 +0000
+++ regex/src/dnsmasq.h 2010-01-23 19:49:08 +0000
@@ -120,6 +120,10 @@
#include <priv.h>
#endif
+#ifdef HAVE_REGEX
+# include <pcre.h>
+#endif
+
/* daemon is function in the C library.... */
#define daemon dnsmasq_daemon
@@ -319,6 +323,7 @@
#define SERV_MARK 256 /* for mark-and-delete */
#define SERV_TYPE (SERV_HAS_DOMAIN | SERV_FOR_NODOTS)
#define SERV_COUNTED 512 /* workspace for log code */
+#define SERV_IS_REGEX 1024 /* server entry is a regex */
struct serverfd {
int fd;
@@ -337,6 +342,10 @@
char interface[IF_NAMESIZE+1];
struct serverfd *sfd;
char *domain; /* set if this server only handles a domain. */
+#ifdef HAVE_REGEX
+ pcre *regex;
+ pcre_extra *pextra;
+#endif
int flags, tcpfd;
unsigned int queries, failed_queries;
struct server *next;
=== modified file 'src/forward.c'
--- upstream/src/forward.c 2010-01-23 19:48:37 +0000
+++ regex/src/forward.c 2010-02-04 14:39:20 +0000
@@ -149,12 +149,38 @@
}
else if (serv->flags & SERV_HAS_DOMAIN)
{
- unsigned int domainlen = strlen(serv->domain);
- char *matchstart = qdomain + namelen - domainlen;
- if (namelen >= domainlen &&
- hostname_isequal(matchstart, serv->domain) &&
- domainlen >= matchlen &&
- (domainlen == 0 || namelen == domainlen || *(serv->domain) == '.' || *(matchstart-1) == '.' ))
+ unsigned int domainlen = matchlen;
+ int serverhit = 0;
+
+#ifdef HAVE_REGEX
+ if (serv->flags & SERV_IS_REGEX)
+ {
+ int captcount = 0;
+ if (pcre_fullinfo(serv->regex, serv->pextra, PCRE_INFO_CAPTURECOUNT, &captcount) == 0)
+ {
+ /* C99 dyn-array, or alloca must be used */
+ int ovect[(captcount + 1) * 3];
+ if (pcre_exec(serv->regex, serv->pextra, qdomain, namelen, 0, 0, ovect, (captcount + 1) * 3) > 0)
+ {
+ domainlen = (unsigned int) (ovect[1] - ovect[0]);
+ if (domainlen >= matchlen)
+ serverhit = 1;
+ }
+ }
+ }
+ else
+#endif
+ {
+ domainlen = strlen(serv->domain);
+ char *matchstart = qdomain + namelen - domainlen;
+ if (namelen >= domainlen &&
+ hostname_isequal(matchstart, serv->domain) &&
+ domainlen >= matchlen &&
+ (domainlen == 0 || namelen == domainlen || *(serv->domain) == '.' || *(matchstart-1) == '.' ))
+ serverhit = 1;
+ }
+
+ if (serverhit)
{
unsigned short sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6;
*type = SERV_HAS_DOMAIN;
@@ -201,6 +227,27 @@
return flags;
}
+static int match_domain_for_forward(char *domain, struct server *serv)
+{
+ int ret_val = 0;
+ if(serv->flags & SERV_IS_REGEX)
+ {
+#ifdef HAVE_REGEX
+ int captcount = 0;
+ if (pcre_fullinfo(serv->regex, serv->pextra, PCRE_INFO_CAPTURECOUNT, &captcount) == 0)
+ {
+ /* C99 dyn-array, or alloca must be used */
+ int ovect[(captcount + 1) * 3];
+ ret_val = pcre_exec(serv->regex, serv->pextra, domain,
+ strlen(domain), 0, 0, ovect, (captcount + 1) * 3) > 0;
+ }
+#endif
+ }
+ else
+ ret_val = hostname_isequal(domain, serv->domain);
+ return ret_val;
+}
+
static int forward_query(int udpfd, union mysockaddr *udpaddr,
struct all_addr *dst_addr, unsigned int dst_iface,
HEADER *header, size_t plen, time_t now, struct frec *forward)
@@ -286,8 +333,8 @@
must be NULL also. */
if (type == (start->flags & SERV_TYPE) &&
- (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
- !(start->flags & SERV_LITERAL_ADDRESS))
+ !(start->flags & SERV_LITERAL_ADDRESS) &&
+ (type != SERV_HAS_DOMAIN || match_domain_for_forward(domain, start)))
{
int fd;
=== modified file 'src/network.c'
--- upstream/src/network.c 2010-01-23 19:48:37 +0000
+++ regex/src/network.c 2010-01-23 19:49:08 +0000
@@ -741,7 +741,7 @@
char *s1, *s2;
if (!(new->flags & SERV_HAS_DOMAIN))
s1 = _("unqualified"), s2 = _("names");
- else if (strlen(new->domain) == 0)
+ else if (new->domain && strlen(new->domain) == 0)
s1 = _("default"), s2 = "";
else
s1 = _("domain"), s2 = new->domain;
=== modified file 'src/option.c'
--- upstream/src/option.c 2010-01-23 19:48:37 +0000
+++ regex/src/option.c 2010-01-23 19:49:08 +0000
@@ -1405,10 +1405,16 @@
arg++;
while ((end = split_chr(arg, '/')))
{
- char *domain = NULL;
+ char *domain = NULL, *regex = NULL;
+ char *real_end = arg + strlen(arg);
/* # matches everything and becomes a zero length domain string */
if (strcmp(arg, "#") == 0)
domain = "";
+ else if (*arg == ':' && *(real_end - 1) == ':')
+ {
+ *(real_end - 1) = '\0';
+ regex = arg + 1;
+ }
else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg)))
option = '?';
serv = opt_malloc(sizeof(struct server));
@@ -1416,7 +1422,28 @@
serv->next = newlist;
newlist = serv;
serv->domain = domain;
- serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS;
+ serv->flags = domain || regex ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS;
+ if (regex)
+ {
+#ifdef HAVE_REGEX
+ const char *error;
+ int erroff;
+ serv->regex = pcre_compile(regex, 0, &error, &erroff, NULL);
+
+ if (!serv->regex)
+ {
+ option = '?';
+ problem = (char *) error;
+ break;
+ }
+ serv->flags |= SERV_IS_REGEX;
+ serv->pextra = pcre_study(serv->regex, 0, &error);
+#else
+ option = '?';
+ problem = "Using a regex while server was configured without regex support!";
+ break;
+#endif
+ }
arg = end;
}
if (!newlist)