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
#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;
}
ÿþconfigured_server=0xc0a80101

packet_source=0xc0a80166

accepted_by_visible_gate=1

VULNERABLE: first TFTP response from 
unexpected source IP passes the visible 
receive gate

Reply via email to