From 777428cf673a715c599185b8d0a6891ffdff90ae Mon Sep 17 00:00:00 2001
From: hanishkvc <hanishkvc@gmail.com>
Date: Sat, 7 May 2022 00:04:46 +0530
Subject: [PATCH] DnsLookup: Configurable dnsQueryId generation including
 random

Dns lookup logic has been updated to provide a configurable compile
time selection of dns query id generation logics, including random,
where possible, instead of the previous simple counter mode.

Given that embedded systems may or may not have sources for trying
to generate random numbers, and also to try and keep the load on
the system low, by default it uses the standard random prng based
logic to generate the ids.

However if either urandom or else if realtime clock is available on
the target, then the same is used to reseed the prng periodically
in a slightly non deterministic manner, to try and avoid long runs
of unbroken random sequences from the prng.

This should make dns poison attempts more difficult. The uclibc
developers wish to thank the white hat teams which alerted the
community about the possible weakness in the dns path, given the
increased resources with adversaries today.

The dns lookup logic maintains its own state wrt the random prng
functions, so that other users of the library's random prng are
not affected wrt their operations with the prng.

Note to Platform developers:

During compile time you can select from among these additional
choices wrt the dns query id generation, using make config and
companions.

If your platform doesnt support urandom nor realtime clocks, and or
for some reason if you want to revert to the previous simple counter,
rather than the random prng, you can force the same at compile time
by selecting the SimpleCounter mode.

If you want to increase the randomness of the generated ids, and
dont mind increasing the load on the system/latency then you could
select the Urandom mode during config.

If you have a clock with nanosecond granularity, in your system and
inturn want to keep the underlying logic simple, you could also try
using the time from generating the ids. You need to select the clock
option during config.

If you want to change the underlying logic to make it more random
and or more simple, look at dnsrand_setup and dnsrand_next.
---
 extra/Configs/Config.in |  46 +++++++++++
 libc/inet/resolv.c      | 167 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 211 insertions(+), 2 deletions(-)

diff --git a/extra/Configs/Config.in b/extra/Configs/Config.in
index a06a17864..7ece75d91 100644
--- a/extra/Configs/Config.in
+++ b/extra/Configs/Config.in
@@ -1344,6 +1344,52 @@ config UCLIBC_HAS_RESOLVER_SUPPORT
 	  ns_name_pack, ns_name_compress, ns_name_skip, dn_skipname,
 	  ns_get16, ns_get32, ns_put16, ns_put32
 
+choice
+	prompt "DNS Query ID generation"
+	default UCLIBC_DNSRAND_MODE_PRNGPLUS
+	help
+	  Control how successive dns query ids' are generated during
+	  dns lookup.
+
+config UCLIBC_DNSRAND_MODE_SIMPLECOUNTER
+	bool "simplecounter"
+	help
+	  "simplecounter" uses a simple counter to generate dns query id.
+	  This is a very simple logic and can be subjected to dns poison
+	  attack relatively easily.
+	  It is recommended to avoid this option.
+
+config UCLIBC_DNSRAND_MODE_URANDOM
+	bool "urandom"
+	help
+	  "urandom" uses /dev/urandom available under many unix flavours
+	  to generate dns query id. This can generate good random ids.
+	  However this is relatively slow compared to the other options,
+	  as it involves a handshake across the kernel-userspace boundary.
+
+config UCLIBC_DNSRAND_MODE_CLOCK
+	bool "clock"
+	depends on UCLIBC_HAS_REALTIME
+	help
+	  "clock" uses realtime clock of the system to generate plausibly
+	  random dns query id. Systems require to have nanosec granularity
+	  wrt the time info for this to generate plausibly random values.
+
+config UCLIBC_DNSRAND_MODE_PRNGPLUS
+	bool "prngplus"
+	help
+	  "prngplus" uses the random prng available within uclibc, to
+	  generate the dns query id. This is fast compared to the other
+	  good options available. It periodically reseeds the prng using
+	  random value generated from either urandom or else clock, if
+	  either of them is available, to try and make it more difficult
+	  to infer internal state of prng from a sequence of generated
+	  random values.
+	  This is the default.
+
+endchoice
+
+
 endif
 
 
diff --git a/libc/inet/resolv.c b/libc/inet/resolv.c
index 92a65d0dc..e98143b48 100644
--- a/libc/inet/resolv.c
+++ b/libc/inet/resolv.c
@@ -255,6 +255,7 @@ Domain name in a message can be represented as either:
 #include <sys/stat.h>
 #include <sys/param.h>
 #include <bits/uClibc_mutex.h>
+#include <fcntl.h>
 #include "internal/parse_config.h"
 
 /* poll() is not supported in kernel <= 2.0, therefore if __NR_poll is
@@ -1045,6 +1046,167 @@ static int __decode_answer(const unsigned char *message, /* packet */
 	return i + RRFIXEDSZ + a->rdlength;
 }
 
+/*
+ * Get a random int from urandom.
+ * Return 0 on success and -1 on failure.
+ */
+int _dnsrand_getrandom_urandom(int urand_fd, int *rand_value) {
+	if (urand_fd != -1) {
+		if(read(urand_fd, rand_value, sizeof(int)) == sizeof(int)) { /* small reads like few bytes here should be safe in general. */
+			printf("uCLibC:DBUG:DnsRandGetRand:URandom:0x%lx\n", *rand_value);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+/*
+ * Get a sort of random int by looking at current time in system realtime clock.
+ * Return 0 on success and -1 on failure.
+ * If realtime clock is not enabled, return failure.
+ */
+int _dnsrand_getrandom_clock(int *rand_value) {
+#if defined __USE_POSIX199309 && defined __UCLIBC_HAS_REALTIME__
+	struct timespec ts;
+	if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
+		*rand_value = (ts.tv_sec + ts.tv_nsec) % INT_MAX;
+		printf("uCLibC:DBUG:DnsRandGetRand:Clocky:0x%lx\n", *rand_value);
+		return 0;
+	}
+#endif
+	return -1;
+}
+
+/*
+ * Try get a random int by first checking at urandom and then at realtime clock.
+ * Return 0 on success and -1 on failure.
+ *
+ * Chances are most embedded targets using linux/bsd/... could have urandom and
+ * also it can potentially give better random values, so try urandom first.
+ * However if failure wrt urandom, then try realtime clock based helper.
+ */
+int _dnsrand_getrandom_urcl(int urand_fd, int *rand_value) {
+	if(_dnsrand_getrandom_urandom(urand_fd, rand_value) == 0) {
+		return 0;
+	}
+	if (_dnsrand_getrandom_clock(rand_value) == 0) {
+		return 0;
+	}
+	printf("uCLibC:DBUG:DnsRandGetRand:Nothing:0x%lx\n", *rand_value);
+	return -1;
+}
+
+#define DNSRAND_PRNGSTATE_INT32LEN 32
+#define DNSRAND_RESEED_CNT 128
+/*
+ * This logic uses uclibc's random PRNG to generate random int. This keeps the
+ * logic fast by not depending on a more involved CPRNG kind of logic nor on a
+ * kernel to user space handshake at the core.
+ *
+ * However to ensure that pseudo random sequences based on a given seeding of the
+ * PRNG logic, is not generated for too long so as to allow a advarsary to guess
+ * the internal states of the prng logic and inturn the next number, srandom is
+ * used periodically to reseed PRNG logic, when and where possible.
+ *
+ * To help with this periodic reseeding, by default the logic will first try to
+ * see if it can get some relatively random number using /dev/urandom. If not it
+ * will try to use the current time as a substitute. If neither of them work out
+ * and or are not available, then the same pseudo random sequence will continue,
+ * for now.
+ *
+ * Also to add bit more of variance wrt this periodic reseeding, the period interval
+ * at which this reseeding occurs keeps changing within a predefined window. The
+ * window is controlled based on how often this logic is called (which currently
+ * will depend on how often requests for dns query (and inturn dnsrand_next) occurs,
+ * as well as a self driven periodically changing request count.
+ *
+ */
+int _dnsrand_getrandom_prng(int urand_fd, int *rand_value) {
+	static int cnt = -1;
+	static int nextReSeedWindow = DNSRAND_RESEED_CNT;
+	static int32_t prngState[DNSRAND_PRNGSTATE_INT32LEN]; /* prng logic internally assumes int32_t wrt state array, so to help align if required */
+	static struct random_data prngData;
+	int32_t val;
+	int prngSeed = 0x19481869;
+
+	if (cnt == -1) {
+		_dnsrand_getrandom_urcl(urand_fd, &prngSeed);
+		memset(&prngData, 0, sizeof(prngData));
+		initstate_r(prngSeed, (char*)&prngState, DNSRAND_PRNGSTATE_INT32LEN*4, &prngData);
+	}
+	cnt += 1;
+	if ((cnt % nextReSeedWindow) == 0) {
+		if (_dnsrand_getrandom_urcl(urand_fd, &prngSeed) == 0) {
+			srandom_r(prngSeed, &prngData);
+		}
+		random_r(&prngData, &val);
+		nextReSeedWindow = DNSRAND_RESEED_CNT + (val % DNSRAND_RESEED_CNT);
+		printf("uCLibC:DBUG:DnsRandNext:Window:%d\n", nextReSeedWindow);
+	}
+	random_r(&prngData, &val);
+	*rand_value = val;
+	return 0;
+}
+
+/**
+ * If DNS query's id etal is generated using a simple counter, then it can be
+ * subjected to dns poisoning relatively easily, so adding some randomness can
+ * increase the difficulty wrt dns poisoning and is thus desirable.
+ *
+ * However given that embedded targets may or may not have different sources available
+ * with them to try generate random values, this logic tries to provides flexibility
+ * to the platform developer to decide, how they may want to handle this.
+ *
+ * If a given target doesnt support urandom nor realtime clock OR for some reason
+ * if the platform developer doesnt want to use random dns query id etal, then
+ * they can define __UCLIBC_DNSRAND_MODE_SIMPLECOUNTER__ so that a simple incrementing
+ * counter is used.
+ *
+ * However if the target has support for urandom or realtime clock, then the prngplus
+ * based random generation gives a good balance between randomness and performance,
+ * and inturn it is the default. The define is __UCLIBC_DNSRAND_MODE_PRNGPLUS__
+ *
+ * If urandom is available on the target and one wants to keep things simple and use
+ * it directly, then one can define __UCLIBC_DNSRAND_MODE_URANDOM__. However do note
+ * that this will be slightly slower compared to other options.
+ *
+ * If realtime clock is available on the target and enabled and inturn if one wants to
+ * keep things simple and use it directly, then define __UCLIBC_DNSRAND_MODE_CLOCK__.
+ * Do note that this may not be as random as urandom or prngplus, in some cases. This
+ * requires nanosec granularity wrt time info to give plausible randomness.
+ *
+ */
+int dnsrand_next(int urand_fd, int def_value) {
+	int val = def_value;
+#if defined __UCLIBC_DNSRAND_MODE_SIMPLECOUNTER__
+	return val;
+#elif defined __UCLIBC_DNSRAND_MODE_URANDOM__
+	if (_dnsrand_getrandom_urandom(urand_fd, &val) == 0) {
+		return val;
+	}
+	return def_value;
+#elif defined __UCLIBC_DNSRAND_MODE_CLOCK__
+	if (_dnsrand_getrandom_clock(&val) == 0) {
+		return val;
+	}
+	return def_value;
+#else
+	if(_dnsrand_getrandom_prng(urand_fd, &val) == 0) {
+		return val;
+	}
+	return def_value;
+#endif
+}
+
+int dnsrand_setup(int *urand_fd, int def_value) {
+#if !defined __UCLIBC_DNSRAND_MODE_SIMPLECOUNTER__ && !defined __UCLIBC_DNSRAND_MODE_CLOCK__
+	if (*urand_fd == -2) {
+		*urand_fd = open("/dev/urandom", O_RDONLY);
+	}
+#endif
+	return def_value;
+}
+
 /* On entry:
  *  a.buf(len) = auxiliary buffer for IP addresses after first one
  *  a.add_count = how many additional addresses are there already
@@ -1070,6 +1232,7 @@ int __dns_lookup(const char *name,
 	/* Protected by __resolv_lock: */
 	static int last_ns_num = 0;
 	static uint16_t last_id = 1;
+	static int urand_fd = -2; /* -2 used for 1st time open attempt, -1 indicates non available urandom */
 
 	int i, j, fd, rc;
 	int packet_len;
@@ -1149,7 +1312,7 @@ int __dns_lookup(const char *name,
 		}
 		/* first time? pick starting server etc */
 		if (local_ns_num < 0) {
-			local_id = last_id;
+			local_id = dnsrand_setup(&urand_fd, last_id);
 /*TODO: implement /etc/resolv.conf's "options rotate"
  (a.k.a. RES_ROTATE bit in _res.options)
 			local_ns_num = 0;
@@ -1159,7 +1322,7 @@ int __dns_lookup(const char *name,
 		}
 		if (local_ns_num >= __nameservers)
 			local_ns_num = 0;
-		local_id++;
+		local_id = dnsrand_next(urand_fd, ++local_id);
 		local_id &= 0xffff;
 		/* write new values back while still under lock */
 		last_id = local_id;
-- 
2.20.1

