[SECURITY] net: tftp: first OACK/DATA can be accepted from an unexpected source IP
liu zhenyu
lzy20000419 at outlook.com
Fri May 22 06:01:59 CEST 2026
Hello U-Boot maintainers,
I found a TFTP client-side source verification issue in U-Boot's pre-boot network stack.
No CVE is assigned at report time.
Summary
-------
In client mode, U-Boot records the expected TFTP server IP in `tftp_remote_ip`, but the IPv4 TFTP receive handler accepts the first OACK or DATA packet without checking that the packet source IP (`sip`) matches that configured server IP.
A network-adjacent attacker who can observe or race traffic on the boot LAN can send an OACK or first DATA packet to the client's local TFTP port before the legitimate server response. If accepted, U-Boot binds `tftp_remote_port` from the attacker packet and can store attacker-controlled DATA into the boot image buffer.
Affected version
----------------
Observed in current master:
38dbe637c9dfcadbd1bc201bfbb27f96b2ad525a
Affected file:
net/tftp.c
Details
-------
At transfer setup, U-Boot stores the expected TFTP server IP:
net/tftp.c:817-820
tftp_remote_ip6 = net_server_ip6;
tftp_remote_ip = net_server_ip;
The IPv4 receive handler checks the destination port and some source-port/state conditions:
net/tftp.c:458-470
static void tftp_handler(uchar *pkt, unsigned dest, struct in_addr sip,
unsigned src, unsigned len)
...
if (dest != tftp_our_port)
return;
if (tftp_state != STATE_SEND_RRQ && src != tftp_remote_port &&
tftp_state != STATE_RECV_WRQ && tftp_state != STATE_SEND_WRQ)
return;
There is no equivalent check that `sip` matches `tftp_remote_ip` for client-side traffic from the expected server.
For OACK, the handler accepts the packet and binds the remote transfer port from the packet:
net/tftp.c:530-540
case TFTP_OACK:
...
tftp_state = STATE_OACK;
tftp_remote_port = src;
For first DATA in the initial client state, the handler also binds `tftp_remote_port` from the packet and proceeds to store the received block:
net/tftp.c:636-640
if (tftp_state == STATE_SEND_RRQ || tftp_state == STATE_OACK ||
tftp_state == STATE_RECV_WRQ) {
tftp_state = STATE_DATA;
tftp_remote_port = src;
net/tftp.c:663
if (store_block(tftp_cur_block, pkt + 2, len)) {
Attack scenario
---------------
1. A device starts a TFTP boot from configured server `192.168.1.1`.
2. The attacker observes the RRQ and learns the client's local UDP port.
3. The attacker sends an OACK or DATA block from `192.168.1.102` to that client port before the legitimate server response.
4. The visible handler checks accept the packet because the destination port and state match.
5. U-Boot binds the transfer port from the attacker packet and can write attacker-controlled DATA to the boot buffer.
Minimal host-side PoC
---------------------
The attached/referenced harness includes this reduced check:
configured_server = 0xc0a80101 // 192.168.1.1
packet_source = 0xc0a80166 // 192.168.1.102
state = STATE_SEND_RRQ
dest = tftp_our_port
It shows that the visible accept conditions are true even when the packet source IP does not match the configured server:
BA2-T6-CAND-002: configured=0xc0a80101 packet=0xc0a80166 accepted=1
PoC source to attach:
poc-tftp-source-ip.c
Build and run:
gcc -std=c11 -Wall -Wextra -O0 poc-tftp-source-ip.c -o poc-tftp-source-ip
./poc-tftp-source-ip
Observed output:
configured_server=0xc0a80101
packet_source=0xc0a80166
accepted_by_visible_gate=1
VULNERABLE: first TFTP response from unexpected source IP passes the visible receive gate
Impact
------
This affects systems using U-Boot TFTP boot on a network where an attacker can race or spoof UDP packets on the boot LAN. The impact is boot-data injection or denial of service in the pre-OS boot path. Existing destination-port and later transfer-port checks reduce the race window, but they do not verify the server IP before accepting the first OACK/DATA packet.
Expected invariant
------------------
For TFTP client transfers, before accepting OACK/DATA and before binding the transfer port, U-Boot should reject packets whose source IP does not match the configured TFTP server IP. The TFTP server mode initial WRQ path can remain separate, since it intentionally accepts an incoming client and then records its address.
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
-------------- next part --------------
#include <stdint.h>
#include <stdio.h>
#define STATE_SEND_RRQ 1u
int main(void)
{
const uint32_t configured_server = 0xc0a80101u; /* 192.168.1.1 */
const uint32_t attacker_source = 0xc0a80166u; /* 192.168.1.102 */
const unsigned tftp_our_port = 1234u;
const unsigned packet_dest = 1234u;
const unsigned tftp_state = STATE_SEND_RRQ;
/*
* This models the visible accept gate in net/tftp.c:
*
* if (dest != tftp_our_port)
* return;
* if (tftp_state != STATE_SEND_RRQ && src != tftp_remote_port &&
* tftp_state != STATE_RECV_WRQ && tftp_state != STATE_SEND_WRQ)
* return;
*
* There is no sip != tftp_remote_ip check in this gate.
*/
const int accepted_by_visible_gate =
(packet_dest == tftp_our_port) && (tftp_state == STATE_SEND_RRQ);
printf("configured_server=0x%08x\n", configured_server);
printf("packet_source=0x%08x\n", attacker_source);
printf("accepted_by_visible_gate=%d\n", accepted_by_visible_gate);
if (attacker_source != configured_server && accepted_by_visible_gate) {
puts("VULNERABLE: first TFTP response from unexpected source IP passes the visible receive gate");
return 0;
}
puts("not reproduced");
return 1;
}
-------------- next part --------------
ÿþc o n f i g u r e d _ s e r v e r = 0 x c 0 a 8 0 1 0 1
p a c k e t _ s o u r c e = 0 x c 0 a 8 0 1 6 6
a c c e p t e d _ b y _ v i s i b l e _ g a t e = 1
V U L N E R A B L E : f i r s t T F T P r e s p o n s e f r o m u n e x p e c t e d s o u r c e I P p a s s e s t h e v i s i b l e r e c e i v e g a t e
More information about the U-Boot
mailing list