Fix for case-sensitivity problems in DNS.

Fix a case sensitivity problem which has been lurking for a long while.
When we get example.com and Example.com and combine them, we send whichever
query arrives first upstream and then later answer it, and we also
answer the second with the same answer. That means that if example.com
arrives first, it will get the answer example.com - good - but Example.com
will _also_ get the answer example.com - not so good.

In theory, fixing this is simple without having to keep seperate
copies of all the queries: Just use the bit-vector representation
of case flipping that we have for 0x20-encoding to keep the
differences in case. The complication comes from the fact that
the existing bit-vector code only holds data on the first 32 alpha
letters, because we only flip that up to many for 0x20 encoding.

In practise, the delta between combined queries can almost always
be represented with that data, since almost all queries are
all lower case and we only purturb the first 32 letters with
0x20 encoding. It's therefore worth keeping the existing,
efficient data structure for the 99.9% of the time it works.
For the 0.1% it doesn't, however, one needs an arbitrary-length data
structure with the resource implications of that.

Thanks to Peter Tirsek for the well researched bug report which set me
on to these problems.
This commit is contained in:
Simon Kelley
2025-02-06 16:01:57 +00:00
parent e44165c0f7
commit 77c4e95d4a
4 changed files with 178 additions and 31 deletions

View File

@@ -19,7 +19,11 @@
/* EXTR_NAME_EXTRACT -> extract name
EXTR_NAME_COMPARE -> compare name, case insensitive
EXTR_NAME_NOCASE -> compare name, case sensitive
EXTR_NAME_FLIP -> flip 0x20 bits in packet, controlled by bitmap in parm. name may be NULL
EXTR_NAME_FLIP -> flip 0x20 bits in packet.
For flip, name is an array of ints, whose size
is given in parm, which forms the bitmap. Bits beyond the size
are assumed to be zero.
return = 0 -> error
return = 1 -> extract OK, compare OK, flip OK
@@ -31,14 +35,21 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
{
unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL;
unsigned int j, l, namelen = 0, hops = 0;
unsigned int bigmap_counter = 0, bigmap_posn = 0, bigmap_size, bitmap;
int retvalue = 1, case_insens = 1, isExtract = 0, flip = 0, extrabytes = (int)parm;
unsigned int *bigmap;
if (func == EXTR_NAME_EXTRACT)
isExtract = 1, *cp = 0;
else if (func == EXTR_NAME_NOCASE)
case_insens = 0;
else if (func == EXTR_NAME_FLIP)
flip = 1, extrabytes = 0;
{
flip = 1, extrabytes = 0;
bigmap = (unsigned int *)name;
name = NULL;
bigmap_size = parm;
}
while (1)
{
@@ -116,12 +127,18 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
{
unsigned char c = *p;
/* parm is unsigned. We only flip up to the first 32 alpha-chars. */
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
if (parm & 1)
/* Get the next int of the bitmap */
if (bigmap_posn < bigmap_size && bigmap_counter-- == 0)
{
bitmap = bigmap[bigmap_posn++];
bigmap_counter = (sizeof(unsigned int) * 8) - 1;
}
if (bitmap & 1)
*p ^= 0x20;
parm >>= 1;
bitmap >>= 1;
}
}
else