U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Jerome Forissier <jerome.forissier@arm.com>
To: Shahriyar Jalayeri <shahriyar@byteray.co.uk>,
	u-boot@lists.u-boot-project.org
Cc: Tom Rini <trini@konsulko.com>,
	David Lechner <dlechner@baylibre.com>,
	Argus <argus@byteray.co.uk>, nd <nd@arm.com>
Subject: Re: [PATCH 2/3] net: lwip: wget: free the transfer context after an aborted request
Date: Wed, 12 Aug 2026 15:38:59 +0200	[thread overview]
Message-ID: <d8c2edf5-e0c1-481f-a2e5-7ae2cb869a63@arm.com> (raw)
In-Reply-To: <20260811-lwip-httpc-uaf-v1-2-9f0ba7331dea@byteray.co.uk>

Hi Shahriar,

On 11/08/2026 20:48, Shahriyar Jalayeri wrote:
> wget_do_request() keeps the transfer context on the stack and passes its
> address to the lwIP httpc callbacks. If the user interrupts the transfer
> with Ctrl-C while the host name is still resolving, wget_handle_request()
> leaves its receive loop without tearing the connection down. The pending
> DNS lookup (which lwIP cannot cancel) later resolves, the connection is
> established, and httpc_recv_cb() runs against a stack frame that no longer
> exists. It writes attacker-controlled TCP data through store_block() to
> map_sysmem(ctx->daddr), with ctx read from reused stack.
> 
> Allocate the context on the heap and add an 'abandoned' flag. On Ctrl-C
> with a request still in flight, mark it abandoned and hand ownership to
> the lwIP callback, which frees the context when the connection finally
> tears down; the receive, headers-done and result callbacks return early
> so a stale callback neither stores data nor touches wget_info.
> 
> Fixes: 3c656c928bd7 ("net: lwip: add wget command")
> Signed-off-by: Shahriyar Jalayeri <shahriyar@byteray.co.uk>
> ---
>  net/lwip/wget.c | 71 ++++++++++++++++++++++++++++++++++++++++++++-------------
>  1 file changed, 55 insertions(+), 16 deletions(-)
> 
> diff --git a/net/lwip/wget.c b/net/lwip/wget.c
> index 9e93765926d..b81509dff3f 100644
> --- a/net/lwip/wget.c
> +++ b/net/lwip/wget.c
> @@ -12,6 +12,7 @@
>  #include <lwip/errno.h>
>  #include <lwip/timeouts.h>
>  #include <rng.h>
> +#include <malloc.h>
>  #include <mapmem.h>
>  #include <net.h>
>  #include <time.h>
> @@ -39,6 +40,7 @@ struct wget_ctx {
>  	ulong content_len;
>  	ulong hash_count;
>  	enum done_state done;
> +	bool abandoned;
>  };
>  
>  static void wget_lwip_fill_info(struct pbuf *hdr, u16_t hdr_len, u32_t hdr_cont_len)
> @@ -200,6 +202,13 @@ static err_t httpc_recv_cb(void *arg, struct altcp_pcb *pcb, struct pbuf *pbuf,
>  	if (!pbuf)
>  		return ERR_BUF;
>  
> +	/* The caller gave up on this request; drop the connection. */
> +	if (ctx->abandoned) {
> +		altcp_abort(pcb);
> +		pbuf_free(pbuf);
> +		return ERR_ABRT;
> +	}
> +
>  	if (!ctx->start_time)
>  		ctx->start_time = get_timer(0);
>  
> @@ -227,6 +236,12 @@ static void httpc_result_cb(void *arg, httpc_result_t httpc_result,
>  	struct wget_ctx *ctx = arg;
>  	ulong elapsed;
>  
> +	/* Last callback for an abandoned request: reclaim ctx and stop. */
> +	if (ctx->abandoned) {
> +		free(ctx);
> +		return;
> +	}
> +
>  	wget_info->status_code = (u32)srv_res;
>  
>  	if (err == ERR_BUF) {
> @@ -282,6 +297,9 @@ static err_t httpc_headers_done_cb(httpc_state_t *connection, void *arg, struct
>  {
>  	struct wget_ctx *ctx = arg;
>  
> +	if (ctx->abandoned)
> +		return ERR_BUF;
> +
>  	wget_lwip_fill_info(hdr, hdr_len, content_len);
>  
>  	if (wget_info->check_buffer_size && (ulong)content_len > wget_info->buffer_size)
> @@ -376,8 +394,17 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https,
>  
>  	while (!ctx->done) {
>  		net_lwip_rx(udev, netif);
> -		if (ctrlc())
> +		if (ctrlc()) {
> +			/*
> +			 * A request may still be in flight (e.g. the name is
> +			 * still resolving). Hand ctx to the lwIP callback, which
> +			 * frees it once the connection tears down, instead of
> +			 * freeing it here under a live callback.
> +			 */
> +			if (!ctx->done)
> +				ctx->abandoned = true;

One more object seems to have the same lifetime issue here: httpc_connection_t conn is
stack-allocated in wget_handle_request(), but httpc_get_file_dns() keeps a pointer to it
in the HTTP client state and later dereferences it for the headers/result callbacks.

So if Ctrl-C makes wget_handle_request() return while DNS/connect is still pending, ctx
remains valid but conn does not. Could conn be moved into struct wget_ctx (or otherwise
made to live as long as the request) as well?

>  			break;
> +		}
>  	}
>  
>  	if (ctx->done == SUCCESS)
> @@ -392,27 +419,26 @@ static int wget_handle_request(struct wget_ctx *ctx, bool is_https,
>  int wget_do_request(ulong dst_addr, char *uri)
>  {
>  	struct udevice *udev;
> -	struct wget_ctx ctx;
> +	struct wget_ctx *ctx;
>  	struct netif *netif;
> -	bool is_https;
> +	bool is_https, abandoned;
>  	int ret;
>  
> -	ctx.daddr = dst_addr;
> -	ctx.saved_daddr = dst_addr;
> -	ctx.done = NOT_DONE;
> -	ctx.size = 0;
> -	ctx.prevsize = 0;
> -	ctx.start_time = 0;
> -	ctx.content_len = 0;
> -	ctx.hash_count = 0;
> +	ctx = calloc(1, sizeof(*ctx));
> +	if (!ctx)
> +		return -ENOMEM;
> +
> +	ctx->daddr = dst_addr;
> +	ctx->saved_daddr = dst_addr;
> +	ctx->done = NOT_DONE;
>  
> -	ret = parse_url(uri, ctx.server_name, &ctx.port, &ctx.path, &is_https);
> +	ret = parse_url(uri, ctx->server_name, &ctx->port, &ctx->path, &is_https);
>  	if (ret)
> -		return ret;
> +		goto out;
>  
>  	ret = net_lwip_eth_start();
>  	if (ret)
> -		return ret;
> +		goto out;
>  
>  	if (!wget_info)
>  		wget_info = &default_wget_info;
> @@ -422,14 +448,27 @@ int wget_do_request(ulong dst_addr, char *uri)
>  	netif = net_lwip_new_netif(udev);
>  	if (!netif) {
>  		net_lwip_eth_stop();
> -		return -ENODEV;
> +		ret = -ENODEV;
> +		goto out;
>  	}
>  
> -	ret = wget_handle_request(&ctx, is_https, udev, netif);
> +	ret = wget_handle_request(ctx, is_https, udev, netif);
> +
> +	/*
> +	 * If the request was abandoned the lwIP callback still owns ctx and
> +	 * frees it when the connection tears down; do not free it here.
> +	 */
> +	abandoned = ctx->abandoned;
>  
>  	net_lwip_remove_netif(netif);
>  	net_lwip_eth_stop();
>  
> +	if (!abandoned)
> +		free(ctx);
> +
> +	return ret;
> +out:
> +	free(ctx);
>  	return ret;
>  }
Thanks,
-- 
Jerome

  reply	other threads:[~2026-08-12 13:39 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-11 18:48 [PATCH 0/3] net: lwip: fix use-after-free bugs in the wget and dns commands Shahriyar Jalayeri
2026-08-11 18:48 ` [PATCH 1/3] net: lwip: wget: return ERR_ABRT after aborting the connection Shahriyar Jalayeri
2026-08-11 18:48 ` [PATCH 2/3] net: lwip: wget: free the transfer context after an aborted request Shahriyar Jalayeri
2026-08-12 13:38   ` Jerome Forissier [this message]
2026-08-11 18:48 ` [PATCH 3/3] net: lwip: dns: free the callback context after an aborted lookup Shahriyar Jalayeri

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d8c2edf5-e0c1-481f-a2e5-7ae2cb869a63@arm.com \
    --to=jerome.forissier@arm.com \
    --cc=argus@byteray.co.uk \
    --cc=dlechner@baylibre.com \
    --cc=nd@arm.com \
    --cc=shahriyar@byteray.co.uk \
    --cc=trini@konsulko.com \
    --cc=u-boot@lists.u-boot-project.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox