do_dns() keeps its dns_cb_arg on the stack and registers its address as
the dns_gethostbyname() callback argument. lwIP has no way to cancel a
pending lookup, so if the command is interrupted or times out while the
name is still resolving, dns_cb() fires later and writes the result into
the stack frame that has since been reused.
Allocate dns_cb_arg on the heap and add an 'abandoned' flag. If the
lookup did not complete, hand ownership to dns_cb(), which frees the
context when it eventually fires; otherwise free it before returning.
Fixes: aedcfec9ed78 ("net: lwip: add dns command")
Signed-off-by: Shahriyar Jalayeri <[email protected]>
---
net/lwip/dns.c | 38 +++++++++++++++++++++++++++++++-------
1 file changed, 31 insertions(+), 7 deletions(-)
diff --git a/net/lwip/dns.c b/net/lwip/dns.c
index b620b0611d6..e93720963b8 100644
--- a/net/lwip/dns.c
+++ b/net/lwip/dns.c
@@ -4,6 +4,7 @@
#include <command.h>
#include <console.h>
#include <env.h>
+#include <malloc.h>
#include <lwip/dns.h>
#include <lwip/timeouts.h>
#include <net.h>
@@ -15,6 +16,7 @@
struct dns_cb_arg {
ip_addr_t host_ipaddr;
bool done;
+ bool abandoned;
};
static void do_dns_tmr(void *arg)
@@ -26,6 +28,12 @@ static void dns_cb(const char *name, const ip_addr_t
*ipaddr, void *arg)
{
struct dns_cb_arg *dns_cb_arg = arg;
+ /* Late callback for an abandoned lookup: reclaim and stop. */
+ if (dns_cb_arg->abandoned) {
+ free(dns_cb_arg);
+ return;
+ }
+
dns_cb_arg->done = true;
if (!ipaddr)
@@ -36,7 +44,7 @@ static void dns_cb(const char *name, const ip_addr_t *ipaddr,
void *arg)
static int dns_loop(struct udevice *udev, const char *name, const char *var)
{
- struct dns_cb_arg dns_cb_arg = { };
+ struct dns_cb_arg *dns_cb_arg;
struct netif *netif;
const char *ipstr;
ip_addr_t ipaddr;
@@ -52,18 +60,22 @@ static int dns_loop(struct udevice *udev, const char *name,
const char *var)
return CMD_RET_FAILURE;
}
- dns_cb_arg.done = false;
+ dns_cb_arg = calloc(1, sizeof(*dns_cb_arg));
+ if (!dns_cb_arg) {
+ net_lwip_remove_netif(netif);
+ return CMD_RET_FAILURE;
+ }
- ret = dns_gethostbyname(name, &ipaddr, dns_cb, &dns_cb_arg);
+ ret = dns_gethostbyname(name, &ipaddr, dns_cb, dns_cb_arg);
if (ret == ERR_OK) {
- dns_cb(name, &ipaddr, &dns_cb_arg);
+ dns_cb(name, &ipaddr, dns_cb_arg);
} else if (ret == ERR_INPROGRESS) {
start = get_timer(0);
sys_timeout(DNS_RESEND_MS, do_dns_tmr, NULL);
do {
net_lwip_rx(udev, netif);
- if (dns_cb_arg.done)
+ if (dns_cb_arg->done)
break;
if (ctrlc()) {
printf("\nAbort\n");
@@ -75,15 +87,27 @@ static int dns_loop(struct udevice *udev, const char *name,
const char *var)
net_lwip_remove_netif(netif);
- if (dns_cb_arg.done && !ip_addr_isany(&dns_cb_arg.host_ipaddr)) {
- ipstr = ipaddr_ntoa(&dns_cb_arg.host_ipaddr);
+ /*
+ * A started lookup that never completed may still be pending in lwIP
+ * and reference dns_cb_arg. Hand ownership to dns_cb, which frees it
+ * when it eventually fires, instead of freeing it here.
+ */
+ if (ret == ERR_INPROGRESS && !dns_cb_arg->done) {
+ dns_cb_arg->abandoned = true;
+ return CMD_RET_FAILURE;
+ }
+
+ if (dns_cb_arg->done && !ip_addr_isany(&dns_cb_arg->host_ipaddr)) {
+ ipstr = ipaddr_ntoa(&dns_cb_arg->host_ipaddr);
if (var)
env_set(var, ipstr);
else
printf("%s\n", ipstr);
+ free(dns_cb_arg);
return CMD_RET_SUCCESS;
}
+ free(dns_cb_arg);
return CMD_RET_FAILURE;
}
--
2.43.0