1
0
Fork 0
dnscap/plugins/shared/edns0_ecs.c
Daniel Baumann 7fadabf04f
Merging upstream version 2.2.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-08 12:16:03 +01:00

222 lines
No EOL
6.7 KiB
C

/*
* Copyright (c) 2018-2024 OARC, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#define DNS_MSG_HDR_SZ 12
#define RFC1035_MAXLABELSZ 63
#define nptohs(p) ((((uint8_t*)(p))[0] << 8) | ((uint8_t*)(p))[1])
static int rfc1035NameSkip(const u_char* buf, size_t sz, off_t* off)
{
unsigned char c;
size_t len;
/*
* loop_detect[] tracks which position in the DNS message it has
* jumped to so it can't jump to the same twice, aka loop
*/
static unsigned char loop_detect[0x3FFF] = { 0 };
do {
if ((*off) >= sz)
break;
c = *(buf + (*off));
if (c > 191) {
/* blasted compression */
int rc;
unsigned short s;
off_t ptr, loop_ptr;
s = nptohs(buf + (*off));
(*off) += sizeof(s);
/* Sanity check */
if ((*off) >= sz)
return 1; /* message too short */
ptr = s & 0x3FFF;
/* Make sure the pointer is inside this message */
if (ptr >= sz)
return 2; /* bad compression ptr */
if (ptr < DNS_MSG_HDR_SZ)
return 2; /* bad compression ptr */
if (loop_detect[ptr])
return 4; /* compression loop */
loop_detect[(loop_ptr = ptr)] = 1;
rc = rfc1035NameSkip(buf, sz, &ptr);
loop_detect[loop_ptr] = 0;
return rc;
} else if (c > RFC1035_MAXLABELSZ) {
/*
* "(The 10 and 01 combinations are reserved for future use.)"
*/
return 3; /* reserved label/compression flags */
} else {
(*off)++;
len = (size_t)c;
if (len == 0)
break;
if ((*off) + len > sz)
return 4; /* message is too short */
(*off) += len;
}
} while (c > 0);
return 0;
}
static off_t skip_question(const u_char* buf, int len, off_t offset)
{
if (rfc1035NameSkip(buf, len, &offset))
return 0;
if (offset + 4 > len)
return 0;
offset += 4;
return offset;
}
static off_t skip_rr(const u_char* buf, int len, off_t offset)
{
if (rfc1035NameSkip(buf, len, &offset))
return 0;
if (offset + 10 > len)
return 0;
unsigned short us = nptohs(buf + offset + 8);
offset += 10;
if (offset + us > len)
return 0;
offset += us;
return offset;
}
#define EDNS0_TYPE_ECS 8
typedef void (*edns0_ecs_cb)(int family, u_char* buf, size_t len);
static void process_edns0_options(u_char* buf, int len, edns0_ecs_cb cb)
{
unsigned short edns0_type;
unsigned short edns0_len;
off_t offset = 0;
while (len >= 4) {
edns0_type = nptohs(buf + offset);
edns0_len = nptohs(buf + offset + 2);
if (len < 4 + edns0_len)
break;
if (edns0_type == EDNS0_TYPE_ECS) {
if (edns0_len < 5)
break;
if (cb)
cb(nptohs(buf + offset + 4), buf + offset + 8, edns0_len - 4);
}
offset += 4 + edns0_len;
len -= 4 + edns0_len;
}
}
#define T_OPT 41
static off_t grok_additional_for_opt_rr(u_char* buf, int len, off_t offset, edns0_ecs_cb cb)
{
unsigned short us;
/*
* OPT RR for EDNS0 MUST be 0 (root domain), so if the first byte of
* the name is anything it can't be a valid EDNS0 record.
*/
if (*(buf + offset)) {
if (rfc1035NameSkip(buf, len, &offset))
return 0;
if (offset + 10 > len)
return 0;
} else {
offset++;
if (offset + 10 > len)
return 0;
if (nptohs(buf + offset) == T_OPT) {
u_char version = *(buf + offset + 5);
us = nptohs(buf + offset + 8); // rd len
offset += 10;
if (offset + us > len)
return 0;
if (!version && us > 0)
process_edns0_options(buf + offset, us, cb);
offset += us;
return offset;
}
}
/* get rdlength */
us = nptohs(buf + offset + 8);
offset += 10;
if (offset + us > len)
return 0;
offset += us;
return offset;
}
static void parse_for_edns0_ecs(u_char* payload, size_t payloadlen, edns0_ecs_cb cb)
{
off_t offset;
int qdcount, ancount, nscount, arcount;
qdcount = nptohs(payload + 4);
ancount = nptohs(payload + 6);
nscount = nptohs(payload + 8);
arcount = nptohs(payload + 10);
offset = DNS_MSG_HDR_SZ;
while (qdcount > 0 && offset < payloadlen) {
if (!(offset = skip_question(payload, payloadlen, offset))) {
return;
}
qdcount--;
}
while (ancount > 0 && offset < payloadlen) {
if (!(offset = skip_rr(payload, payloadlen, offset))) {
return;
}
ancount--;
}
while (nscount > 0 && offset < payloadlen) {
if (!(offset = skip_rr(payload, payloadlen, offset))) {
return;
}
nscount--;
}
while (arcount > 0 && offset < payloadlen) {
if (!(offset = grok_additional_for_opt_rr(payload, payloadlen, offset, cb))) {
return;
}
arcount--;
}
}