Hello U-Boot maintainers,
I found an integer-overflow bounds-check issue in U-Boot's WGET receive path.
No CVE is assigned at report time.
Summary
-------
`net/wget.c` checks whether a received HTTP response block fits the configured
download buffer by evaluating:
wget_info->buffer_size < offset + len
The addition is performed before the bounds check. Since `offset` and `len` are
unsigned inputs to `store_block()`, a large offset can wrap `offset + len` to a
small value and bypass the intended `buffer_size` guard before `memcpy()`
writes response bytes to `image_load_addr + offset`.
Affected version
----------------
Observed in current master:
38dbe637c9dfcadbd1bc201bfbb27f96b2ad525a
Affected file:
net/wget.c
Details
-------
The vulnerable bounds check is in `store_block()`:
net/wget.c:48-55
static inline int store_block(uchar *src, unsigned int offset,
unsigned int len)
{
ulong store_addr = image_load_addr + offset;
...
if (wget_info->buffer_size &&
wget_info->buffer_size < offset + len)
return -1;
If `offset + len` wraps, the comparison can allow a write whose starting offset
is already outside the intended buffer.
The data is then copied to the computed load address:
net/wget.c:67-69
ptr = map_sysmem(store_addr, len);
memcpy(ptr, src, len);
unmap_sysmem(ptr);
The offset passed into `store_block()` is derived in the TCP receive callback:
net/wget.c:234-240
static int tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs,
void *buf, int len)
{
if ((max_rx_pos == (u32)(-1)) ||
(max_rx_pos < rx_offs + len - 1))
max_rx_pos = rx_offs + len - 1;
if (store_block(buf, rx_offs - http_hdr_size, len) < 0)
return -1;
The same callback also computes `rx_offs + len - 1` before checking for
overflow.
There is an additional adjacent issue while parsing headers:
net/wget.c:136-140
ptr = map_sysmem(image_load_addr, rx_bytes + 1);
saved = ptr[rx_bytes];
ptr[rx_bytes] = '\0';
pos = strstr((char *)ptr, http_eom);
ptr[rx_bytes] = saved;
This temporarily touches one byte past the received byte count in order to run
C-string parsing. If `rx_bytes` is at the configured buffer boundary, this
touches one byte beyond the caller's intended receive buffer.
Minimal host-side PoC
---------------------
The attached/referenced harness uses:
buffer_size = 64
offset = 0xfffffff0
len = 0x40
In unsigned 32-bit arithmetic:
offset + len == 48
The current check therefore sees:
buffer_size < wrapped_sum
64 < 48 // false
So the guard allows the write even though the starting offset is far outside
the 64-byte buffer.
Observed harness output:
BA2-T6-CAND-006: wrapped_sum=48 allows=1 terminator_one_past=1
PoC source to attach:
poc-wget-bounds.c
Build and run:
gcc -std=c11 -Wall -Wextra -O0 poc-wget-bounds.c -o poc-wget-bounds
./poc-wget-bounds
Observed output:
buffer_size=64
offset=0xfffffff0
len=64
wrapped_sum=48
current_check_allows=1
VULNERABLE: wrapped offset+len bypasses buffer_size check
Impact
------
This affects systems using U-Boot WGET/HTTP boot with a configured receive
buffer size. A malicious HTTP server on the boot network controls response
bytes reaching the WGET receive path. If the TCP stream layer presents a large
receive offset, the current arithmetic can bypass the buffer-size check and
copy attacker-controlled bytes outside the intended image buffer. The impact is
pre-OS denial of service or memory corruption in the bootloader context.
Expected invariant
------------------
The WGET receive path should reject any `(offset, len)` range that does not fit
inside `wget_info->buffer_size` without first computing `offset + len` in a
wrapping expression. The usual form is to reject when:
offset > buffer_size || len > buffer_size - offset
Similar non-wrapping checks should be used for `rx_offs + len - 1` and for the
temporary HTTP header terminator.
I can prepare a separate patch or sandbox test if that would be useful, but I
am sending this first as a vulnerability report so the security impact can be
triaged independently of the fix shape.
Regards,
Zhenyu Liu
#include <stdint.h>
#include <stdio.h>
int main(void)
{
const uint32_t buffer_size = 64u;
const uint32_t offset = 0xfffffff0u;
const uint32_t len = 0x40u;
const uint32_t wrapped_sum = offset + len;
const int current_check_allows = !(buffer_size < wrapped_sum);
/*
* This models net/wget.c:
*
* if (wget_info->buffer_size &&
* wget_info->buffer_size < offset + len)
* return -1;
*
* offset + len wraps to 48, so a 64-byte buffer does not reject it.
*/
printf("buffer_size=%u\n", buffer_size);
printf("offset=0x%08x\n", offset);
printf("len=%u\n", len);
printf("wrapped_sum=%u\n", wrapped_sum);
printf("current_check_allows=%d\n", current_check_allows);
if (offset > buffer_size && current_check_allows) {
puts("VULNERABLE: wrapped offset+len bypasses buffer_size check");
return 0;
}
puts("not reproduced");
return 1;
}
ÿþb u f f e r _ s i z e = 6 4
o f f s e t = 0 x f f f f f f f 0
l e n = 6 4
w r a p p e d _ s u m = 4 8
c u r r e n t _ c h e c k _ a l l o w s = 1
V U L N E R A B L E : w r a p p e d o f f s e t + l e n b y p a s s e s
b u f f e r _ s i z e c h e c k