Fix rare problem allocating frec for DNSSEC.

A call to get_new_frec() for a DNSSEC query could manage to
free the original frec that we're doing the DNSSEC query to validate.
Bad things then happen.

This requires that the original frec is old, so it doesn't happen
in practice. I found it when running under gdb, and there have been
reports of SEGV associated with large system-clock warps which are
probably the same thing.
This commit is contained in:
Simon Kelley
2020-04-04 17:00:32 +01:00
parent d162bee356
commit 8caf3d7c6c
3 changed files with 11 additions and 8 deletions

View File

@@ -1683,7 +1683,7 @@ static int set_dns_listeners(time_t now)
/* will we be able to get memory? */
if (daemon->port != 0)
get_new_frec(now, &wait, 0);
get_new_frec(now, &wait, NULL);
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
poll_listen(serverfdp->fd, POLLIN);

View File

@@ -1324,7 +1324,7 @@ void receive_query(struct listener *listen, time_t now);
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
struct frec *get_new_frec(time_t now, int *wait, int force);
struct frec *get_new_frec(time_t now, int *wait, struct frec *force);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, union all_addr *source,
unsigned int iface);

View File

@@ -348,7 +348,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
type &= ~SERV_DO_DNSSEC;
if (daemon->servers && !flags)
forward = get_new_frec(now, NULL, 0);
forward = get_new_frec(now, NULL, NULL);
/* table full - flags == 0, return REFUSED */
if (forward)
@@ -1039,7 +1039,9 @@ void reply_query(int fd, int family, time_t now)
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
/* Make sure we don't expire and free the orig frec during the
allocation of a new one. */
if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, orig)))
status = STAT_ABANDONED;
else
{
@@ -2251,9 +2253,10 @@ static void free_frec(struct frec *f)
else return *wait zero if one available, or *wait is delay to
when the oldest in-use record will expire. Impose an absolute
limit of 4*TIMEOUT before we wipe things (for random sockets).
If force is set, always return a result, even if we have
to allocate above the limit. */
struct frec *get_new_frec(time_t now, int *wait, int force)
If force is non-NULL, always return a result, even if we have
to allocate above the limit, and never free the record pointed
to by the force argument. */
struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
{
struct frec *f, *oldest, *target;
int count;
@@ -2270,7 +2273,7 @@ struct frec *get_new_frec(time_t now, int *wait, int force)
/* Don't free DNSSEC sub-queries here, as we may end up with
dangling references to them. They'll go when their "real" query
is freed. */
if (!f->dependent)
if (!f->dependent && f != force)
#endif
{
if (difftime(now, f->time) >= 4*TIMEOUT)