Compare commits

...
11 Commits
Author SHA1 Message Date
Simon Kelley b95d5a2577 Further tidying of DHCPv6 packet dissection code.
Make things more logical, and put buffer overflow detection in one
new function, opt6_first().
2026-03-25 12:11:54 +00:00
Simon Kelley 5be5dc1f16 New heuristic for disabling DNSSEC for domain-specific nameservers.
If there's no configured server for the parent  of the domain
covered by a domain-specific server, assume that said domain is
not signed. This extends the existing logic which makes similar
decisions for replies to DS queries from tghe parent server.
2026-03-24 12:35:46 +00:00
Simon Kelley 48348a2d77 Fix broken DHCPv6 userclass data in DHCP script.
Exactly the same principle as 53313014b5
2026-03-17 13:40:10 +00:00
Simon Kelley b4a0663654 Accept DHCPv6 vendorclasses with any enterprise number in --dhcp-vendorclass
if not enterprise number is specified. Also accept and match on
enterprise number only.
2026-03-17 13:29:49 +00:00
Simon Kelley 53313014b5 Fix broken DHCPv6 vendorclass data in DHCP script.
The code sending vendorclass data to the DHCP script
was confused about the format of the vendorclass in
the DHCP packet, resulting in broken data passed
to the packet.
2026-03-17 12:09:36 +00:00
Simon Kelley cf08eeee12 Complete 32a54fc8a5 2026-03-15 16:03:29 +00:00
Simon Kelley 0597828b74 Tighten length checking to avoid possible buffer read-overrun in DHCPv6.
Thanks to Dejan Alvadzijevic for spotting the problem.
2026-03-15 16:01:58 +00:00
Simon Kelley c8168e888e Don't call qsort() with a NULL array when no servers defined. 2026-03-10 21:17:41 +00:00
Simon Kelley 3a052d55fb Fix regression handling non-QUERY requests over TCP.
Regression introduced in 729c16a8ac
2026-03-08 22:19:30 +00:00
Simon Kelley 1eab169173 Fix missed hash->digest calls in 4070a74862 2026-03-01 12:33:38 +00:00
Simon Kelley 4070a74862 Fix FTBFS with nettle 4.0.
Thanks to Andreas Metzler for the heads-up.
2026-03-01 12:19:11 +00:00
9 changed files with 174 additions and 133 deletions

No files matched your search

+5 -3
View File
@@ -1515,7 +1515,7 @@ The normal default DNS server (the same address as the DHCP server)
will not be appropriate when there is no route bewteen the
two, so this will have to be explicitly configured.
.TP
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,]<vendor-class>
.B \-U, --dhcp-vendorclass=set:<tag>,[enterprise:<IANA-enterprise number>,][<vendor-class>]
Map from a vendor-class string to a tag. Most DHCP clients provide a
"vendor class" which represents, in some sense, the type of host. This option
maps vendor classes to tags, so that DHCP options may be selectively delivered
@@ -1529,9 +1529,11 @@ allow fuzzy matching. The set: prefix is optional but allowed for
consistency.
Note that in IPv6 only, vendorclasses are namespaced with an
IANA-allocated enterprise number. This is given with enterprise:
IANA-allocated enterprise number. This may be given with an enterprise:
keyword and specifies that only vendorclasses matching the specified
number should be searched.
number should be searched. Similarly, an enterprise number may be given
with no vendor-class string. This always matches vendorclasses with
the given enterprise number.
.TP
.B \-j, --dhcp-userclass=set:<tag>,<user-class>
Map from a user-class string to a tag (with substring
+22 -1
View File
@@ -93,7 +93,15 @@ static void null_hash_update(void *ctxv, size_t length, const uint8_t *src)
memcpy(null_hash_buff + ctx->len, src, length);
ctx->len += length;
}
/* The prototype changes in nettle 4.0 to omit the length argument */
#if MIN_VERSION(4, 0)
static void null_hash_digest(void *ctx, uint8_t *dst)
{
((struct null_hash_digest *)dst)->buff = null_hash_buff;
((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
}
#else
static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
{
(void)length;
@@ -101,6 +109,7 @@ static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
((struct null_hash_digest *)dst)->buff = null_hash_buff;
((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
}
#endif
static struct nettle_hash null_hash = {
"null_hash",
@@ -501,4 +510,16 @@ const struct nettle_hash *hash_find(char *name)
#endif
}
/* The prototype changes in nettle 4.0 to omit the length argument */
void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst)
{
#if MIN_VERSION(4, 0)
(void)length;
hash->digest(ctx, dst);
#else
hash->digest(ctx, length, dst);
#endif
}
#endif /* defined(HAVE_DNSSEC) */
+1 -1
View File
@@ -199,7 +199,7 @@ int main (int argc, char **argv)
/* Must have at least a root trust anchor, or the DNSSEC code
can loop forever. */
for (ds = daemon->ds; ds; ds = ds->next)
if (ds->name[0] == 0)
if (ds->name && ds->name[0] == 0)
break;
if (!ds)
+2
View File
@@ -1473,6 +1473,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
char *keyname, int class, int *validate_count);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
int cache_neg_ds(char *name, int flags, int class, time_t now, int neg_ttl);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
int check_unsigned, int *neganswer, int *prim_ok, int *nons, int *nsec_ttl, int *validate_count);
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
@@ -1489,6 +1490,7 @@ int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig,
char *ds_digest_name(int digest);
char *algo_digest_name(int algo);
char *nsec3_digest_name(int digest);
void nettle_digest_wrapper(const struct nettle_hash *hash, void *ctx, size_t length, uint8_t *dst);
/* util.c */
void rand_init(void);
+21 -18
View File
@@ -656,7 +656,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
}
}
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
/* namebuff used for workspace above, restore to leave unchanged on exit */
p = (unsigned char*)(rrset[0]);
@@ -836,7 +836,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
rather then O(keys x DSs) */
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, (unsigned int)rdlen, psave);
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
from_wire(name);
@@ -1027,21 +1027,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
(flags = in_arpa_name_2_addr(name, &a)) &&
((flags == F_IPV6 && private_net6(&a.addr6, 0)) || (flags == F_IPV4 && private_net(a.addr4, 0))))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming that's OK for a RFC-1918 address."), name);
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming that's OK for a RFC-1918 address"), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_INFO, _("Insecure reply received for DS %s, assuming non-DNSSEC domain-specific server."), name);
my_syslog(LOG_INFO, _("insecure reply received for DS %s, assuming non-DNSSEC domain-specific server"), name);
neganswer = 1;
nons = 0; /* If we're faking a DS, fake one with an NS. */
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
my_syslog(LOG_WARNING, _("insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure", 0);
return STAT_BOGUS | DNSSEC_FAIL_INDET;
}
@@ -1141,44 +1141,47 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
}
flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
if (neganswer)
{
if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN;
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
if (nons)
{
if (lookup_domain(name, F_DOMAINSRV, NULL, NULL))
{
my_syslog(LOG_WARNING, _("Negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server."), name);
my_syslog(LOG_WARNING, _("negative DS reply without NS record received for %s, assuming non-DNSSEC domain-specific server"), name);
nons = 0;
neg_ttl = DNSSEC_ASSUMED_DS_TTL;
}
else
/* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */
flags &= ~F_DNSSECOK;
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
}
return cache_neg_ds(name, flags, class, now, neg_ttl);
}
int cache_neg_ds(char *name, int flags, int class, time_t now, int ttl)
{
cache_start_insert();
/* Use TTL from NSEC for negative cache entries */
if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
if (!cache_insert(name, NULL, class, now, ttl, flags))
return STAT_ABANDONED;
cache_end_insert();
if (neganswer)
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"), 0);
return STAT_OK;
}
/* 4034 6.1 */
static int hostname_cmp(const char *a, const char *b)
{
@@ -1385,13 +1388,13 @@ static int hash_name(char *in, unsigned char **out, struct nettle_hash const *ha
hash->update(ctx, to_wire(in), (unsigned char *)in);
hash->update(ctx, salt_len, salt);
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
for(i = 0; i < iterations; i++)
{
hash->update(ctx, hash->digest_size, digest);
hash->update(ctx, salt_len, salt);
hash->digest(ctx, hash->digest_size, digest);
nettle_digest_wrapper(hash, ctx, hash->digest_size, digest);
}
from_wire(in);
+3 -1
View File
@@ -79,7 +79,9 @@ void build_server_array(void)
for (serv = daemon->local_domains; serv; serv = serv->next, count++)
daemon->serverarray[count] = serv;
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
/* serverarray may be unallocated if we have no servers yet. */
if (daemon->serverarray)
qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
/* servers need the location in the array to find all the whole
set of equivalent servers from a pointer to a single one. */
+27 -12
View File
@@ -954,6 +954,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */
ds_retry:
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else if (forward->flags & FREC_DS_QUERY)
@@ -1083,6 +1084,18 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
STAT_ISEQUAL(status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
return;
}
/* If there's no server for the parent of a domain-specific server's domain,
assume that said server's contents it legitimately unsigned, as if the parent
contained a negative DS record. This is part of the same logic that's found
in dnssec_validate_ds() when it gets a negative DS repsonse. */
if (STAT_ISEQUAL(status, STAT_NEED_DS) && serverind == -1 && lookup_domain(daemon->keyname, F_DOMAINSRV, NULL, NULL) &&
cache_neg_ds(daemon->keyname, F_FORWARD | F_DS | F_NEG | F_DNSSECOK, forward->class, now, DNSSEC_ASSUMED_DS_TTL) == STAT_OK)
{
my_syslog(LOG_WARNING, _("no server for parent domain of %s, assuming unsigned domain"), daemon->keyname);
blockdata_free(stash);
goto ds_retry;
}
/* error unwind */
free_rfds(&rfds);
@@ -2442,10 +2455,10 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
break;
/* Now get the query into the normal UDP packet buffer.
Ignore queries long than this. If we're answering locally,
Ignore queries longer than this. If we're answering locally,
copy the query into the output buffer, but for forwarding, tcp_talk()
wants the query in a a different buffer from the reply.
Note that we overwrote any saved UDP query - this onlt matters in debug mode. */
wants the query in different buffer from the reply.
Note that we overwrote any saved UDP query - this only matters in debug mode. */
daemon->srv_save = NULL;
if (!read_write(confd, (unsigned char *)&tcp_len, sizeof(tcp_len), RW_READ) ||
!(size = ntohs(tcp_len)) || size > (size_t)daemon->packet_buff_sz ||
@@ -2461,6 +2474,15 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
/* header == query */
header = (struct dns_header *)daemon->packet;
/* Add edns0 pheader to query */
size = add_edns0_config(header, size, ((unsigned char *) header) + daemon->edns_pktsz, &peer_addr, now, &cacheable);
/* Clear buffer to avoid risk of information disclosure. */
memset(bigbuff->iov_base, 0, bigbuff->iov_len);
/* Copy query into output buffer for local answering */
memcpy(out_header, header, size);
query_count++;
/* log_query gets called indirectly all over the place, so
@@ -2493,13 +2515,6 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
do_bit = 1; /* do bit */
}
size = add_edns0_config(header, size, ((unsigned char *) header) + daemon->edns_pktsz, &peer_addr, now, &cacheable);
/* Clear buffer to avoid risk of information disclosure. */
memset(bigbuff->iov_base, 0, bigbuff->iov_len);
/* Copy query into output buffer for local answering */
memcpy(out_header, header, size);
log_query_mysockaddr((auth_dns ? F_NOERR | F_AUTH : 0) | F_QUERY | F_FORWARD, daemon->namebuff,
&peer_addr, NULL, qtype);
@@ -2552,7 +2567,7 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
else if (!allowed)
{
ede = EDE_BLOCKED;
m = answer_disallowed(header, size, (u32)mark, daemon->namebuff);
m = answer_disallowed(out_header, size, (u32)mark, daemon->namebuff);
}
#endif
#ifdef HAVE_AUTH
@@ -2568,7 +2583,7 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
/* Do this by steam now we're not in the select() loop */
check_log_writer(1);
if (m == 0 && ede == EDE_UNSET)
if (!flags && m == 0 && ede == EDE_UNSET)
{
struct server *master;
int start;
+30 -28
View File
@@ -4580,39 +4580,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
only allowed for agent-options. */
arg = comma;
if ((comma = split(arg)))
if (option == 'U' && strstr(arg, "enterprise:") == arg)
{
if (option != 'U' || strstr(arg, "enterprise:") != arg)
comma = split(arg);
new->enterprise = atoi(arg+11);
arg = comma;
}
if (arg)
{
for (dig = 0, colon = 0, p = (unsigned char *)arg; *p; p++)
if (isxdigit(*p))
dig = 1;
else if (*p == ':')
colon = 1;
else
break;
unhide_metas(arg);
if (option == 'U' || option == 'j' || *p || !dig || !colon)
{
free(new->netid.net);
ret_err_free(gen_err, new);
new->len = strlen(arg);
new->data = opt_malloc(new->len);
memcpy(new->data, arg, new->len);
}
else
new->enterprise = atoi(arg+11);
{
new->len = parse_hex(comma, (unsigned char *)arg, strlen(arg), NULL, NULL);
new->data = opt_malloc(new->len);
memcpy(new->data, arg, new->len);
}
}
else
comma = arg;
for (dig = 0, colon = 0, p = (unsigned char *)comma; *p; p++)
if (isxdigit(*p))
dig = 1;
else if (*p == ':')
colon = 1;
else
break;
unhide_metas(comma);
if (option == 'U' || option == 'j' || *p || !dig || !colon)
else if (option != 'U' || new->enterprise == 0)
{
new->len = strlen(comma);
new->data = opt_malloc(new->len);
memcpy(new->data, comma, new->len);
}
else
{
new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL);
new->data = opt_malloc(new->len);
memcpy(new->data, comma, new->len);
free(new->netid.net);
ret_err_free(gen_err, new);
}
switch (option)
@@ -4635,7 +4637,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
new->next = daemon->dhcp_vendors;
daemon->dhcp_vendors = new;
break;
}
+63 -69
View File
@@ -39,8 +39,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts);
static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string);
static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string);
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, unsigned int minsize);
static void *opt6_next(uint8_t *opts, uint8_t *end);
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, int minsize);
static void *opt6_first(uint8_t *opt, uint8_t *end);
static unsigned int opt6_uint(unsigned char *opt, int offset, int size);
static void get_context_tag(struct state *state, struct dhcp_context *context);
static int check_ia(struct state *state, void *opt, void **endp, void **ia_option);
@@ -62,6 +62,7 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2)))
#define opt6_type(opt) (opt6_uint(opt, -4, 2))
#define opt6_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[4+(i)]))
#define opt6_next(opt, end) (opt6_first(opt6_ptr((opt), opt6_len((opt))), (end)))
#define opt6_user_vendor_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[2+(i)]))
#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2)))
@@ -112,11 +113,18 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
{
uint8_t *end = inbuff + sz;
uint8_t *opts = inbuff + 34;
int msg_type = *inbuff;
int msg_type;
unsigned char *outmsgtypep;
uint8_t *opt;
struct dhcp_vendor *vendor;
/* must have at least msg_type+trans_id
which is 1 + 3 = 4 */
if (sz < 4)
return 0;
msg_type = *inbuff;
/* if not an encapsulated relayed message, just do the stuff */
if (msg_type != DHCP6RELAYFORW)
{
@@ -233,11 +241,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);
}
for (opt = opts; opt; opt = opt6_next(opt, end))
for (opt = opt6_first(opts, end); opt; opt = opt6_next(opt, end))
{
if ((uint8_t *)opt6_ptr(opt, 0) + opt6_len(opt) > end)
return 0;
/* Don't copy MAC address into reply. */
if (opt6_type(opt) != OPTION6_CLIENT_MAC)
{
@@ -290,7 +295,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
state->hostname = NULL;
state->client_hostname = NULL;
state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */
/* set tag with name == interface */
iface_id.net = state->iface_name;
iface_id.next = state->tags;
@@ -387,13 +392,26 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
if (opt6_len(opt) < 4)
continue;
if (vendor->enterprise != opt6_uint(opt, 0, 4))
if (vendor->enterprise != 0 && vendor->enterprise != opt6_uint(opt, 0, 4))
continue;
/* matching enterprise, no string match. */
if (vendor->enterprise != 0 && vendor->len == 0)
{
vendor->netid.next = state->tags;
state->tags = &vendor->netid;
break;
}
offset = 4;
/* If we're going to search the strings below, there must be at least one empty string to search
I think a vendor_class option with just the enterprise number is valid. */
if (opt6_len(opt) < 6)
continue;
}
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++)
if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)
@@ -657,7 +675,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
for (c = state->context; c; c = c->current)
c->flags &= ~CONTEXT_CONF_USED;
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
unsigned int min_time = 0xffffffff;
@@ -821,7 +839,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
if (ignore)
return 0;
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
unsigned int min_time = 0xffffffff;
@@ -932,7 +950,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
unsigned int min_time = 0xffffffff;
@@ -1067,7 +1085,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, "DHCPCONFIRM", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
@@ -1143,7 +1161,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, "DHCPRELEASE", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
int made_ia = 0;
@@ -1208,7 +1226,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
log6_quiet(state, "DHCPDECLINE", NULL, NULL);
for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
for (opt = opt6_first(state->packet_options, state->end); opt; opt = opt6_next(opt, state->end))
{
void *ia_option, *ia_end;
int made_ia = 0;
@@ -1580,10 +1598,7 @@ static int check_ia(struct state *state, void *opt, void **endp, void **ia_optio
{
*ia_option = NULL;
/* must be a minimal option to check without stepping outside received packet. */
if (opt6_ptr(opt, 4) > state->end)
return 0;
/* callee ensures packet is long enough for opt6_len(opt) to be valid and believe-able. */
state->ia_type = opt6_type(opt);
if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
@@ -1595,10 +1610,7 @@ static int check_ia(struct state *state, void *opt, void **endp, void **ia_optio
if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4)
return 0;
/* Check we don't overflow the received packet. */
if ((*endp = opt6_ptr(opt, opt6_len(opt))) > state->end)
return 0;
*endp = opt6_ptr(opt, opt6_len(opt));
state->iaid = opt6_uint(opt, 0, 4);
*ia_option = opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24);
@@ -1913,10 +1925,10 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0);
if (opt6_len(opt) >= 6)
for (enc_opt = opt6_ptr(opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
for (enc_opt = opt6_ptr(opt, 4); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
{
lease->vendorclass_count++;
lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
}
}
@@ -1973,8 +1985,8 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
if ((opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
{
void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt));
for (enc_opt = opt6_ptr(opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
for (enc_opt = opt6_ptr(opt, 0); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
}
}
#endif
@@ -1989,10 +2001,10 @@ static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_op
void *opt;
char *desc = nest ? "nest" : "sent";
if (!option_bool(OPT_LOG_OPTS) || start_opts == end_opts)
if (!option_bool(OPT_LOG_OPTS))
return;
for (opt = start_opts; opt; opt = opt6_next(opt, end_opts))
for (opt = opt6_first(start_opts, end_opts); opt; opt = opt6_next(opt, end_opts))
{
int type = opt6_type(opt);
void *ia_options = NULL;
@@ -2086,49 +2098,31 @@ static void log6_packet(struct state *state, char *type, struct in6_addr *addr,
string ? string : "");
}
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, unsigned int minsize)
static void *opt6_find(uint8_t *opts, uint8_t *end, unsigned int search, int minsize)
{
u16 opt, opt_len;
void *start;
if (!opts)
return NULL;
while (1)
{
if (end - opts < 4)
return NULL;
start = opts;
GETSHORT(opt, opts);
GETSHORT(opt_len, opts);
if (opt_len > (end - opts))
return NULL;
if (opt == search && (opt_len >= minsize))
return start;
opts += opt_len;
}
for (opts = opt6_first(opts, end); opts; opts = opt6_next(opts, end))
if (opt6_type(opts) == search && opt6_len(opts) >= minsize)
return opts;
return NULL;
}
static void *opt6_next(uint8_t *opts, uint8_t *end)
static void *opt6_first(uint8_t *opt, uint8_t *end)
{
u16 opt_len;
if (end - opts < 4)
if (!opt)
return NULL;
opts += 2;
GETSHORT(opt_len, opts);
if (opt_len >= (end - opts))
return NULL;
return opts + opt_len;
}
/* make sure we have option number and length. */
if ((uint8_t *)opt6_ptr(opt, 0) > end)
return NULL;
/* make sure we have bytes promised by length. */
if ((uint8_t *)opt6_ptr(opt, opt6_len(opt)) > end)
return NULL;
return opt;
}
static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
{
/* this worries about unaligned data and byte order */