Re: [SECURITY] NFS symlink chain buffer overflow → remote code execution (net/nfs-common.c)

manizzle alexandria manizzle.msf at gmail.com
Tue Apr 7 23:33:04 CEST 2026


Done!

Thanks!

On Mon, Apr 6, 2026 at 8:49 AM Tom Rini <trini at konsulko.com> wrote:

> On Fri, Apr 03, 2026 at 09:05:40PM -0700, manizzle alexandria wrote:
>
> > Hi
> >
> > Attached is the .patch file in the format requested. Thanks!
>
> Please send the patch directly (git send-email is good for this), and CC
> the appropriate maintainers as well, in order to start discussion.
> Thanks again!
>
> >
> >
> > On Fri, Apr 3, 2026 at 1:44 PM manizzle alexandria <
> manizzle.msf at gmail.com>
> > wrote:
> >
> > > Will clean up the PoC and send a proper patch following the link.
> > >
> > > Thanks for the quick review.
> > >
> > > On Fri, Apr 3, 2026, 12:49 PM Tom Rini <trini at konsulko.com> wrote:
> > >
> > >> On Fri, Apr 03, 2026 at 11:44:49AM -0700, manizzle alexandria wrote:
> > >>
> > >> >   Hi Tom,
> > >> >
> > >> >   I'm reporting a remotely exploitable buffer overflow in U-Boot's
> NFS
> > >> >   client that I've confirmed leads to arbitrary code execution.
> > >> >
> > >> >   SUMMARY
> > >> >
> > >> >   A rogue NFS server can chain two READLINK (symlink) responses to
> > >> >   overflow the global nfs_path_buff[2048] in net/nfs-common.c by
> ~141
> > >> >   bytes. This corrupts the nfs_path pointer and adjacent globals,
> > >> >   allowing the attacker to hijack the NFS state machine and deliver
> > >> >   shellcode to a known memory address. I have a working end-to-end
> > >> >   exploit with code execution confirmed on QEMU ARM (Cortex-A15).
> > >> >
> > >> >   No authentication is required. The overflow triggers during normal
> > >> >   NFS boot before any OS is loaded.
> > >> >
> > >> >   AFFECTED
> > >> >
> > >> >   - All U-Boot versions with CONFIG_CMD_NFS=y (NFS boot support)
> > >> >   - Tested on: 2026.04-rc5, commit c704af3c8b0
> > >> >   - File: net/nfs-common.c, function nfs_readlink_reply(), lines
> 667-686
> > >> >
> > >> >   ROOT CAUSE
> > >> >
> > >> >   nfs_readlink_reply() reads the symlink target length (rlen) from
> > >> >   the RPC reply and validates it against the packet size, but NOT
> > >> >   against the destination buffer (nfs_path_buff[2048]):
> > >> >
> > >> >     rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);
> > >> >
> > >> >     // Checks rlen fits in packet — CORRECT
> > >> >     if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt +
> rlen) >
> > >> > len)
> > >> >         return -NFS_RPC_DROP;
> > >> >
> > >> >     // Copies rlen bytes into nfs_path — NO CHECK against buffer
> size
> > >> >     memcpy(nfs_path + pathlen, ..., rlen);
> > >> >
> > >> >   A single response can't overflow because rlen maxes at ~1128
> (packet
> > >> >   size limit). But two chained relative symlinks accumulate path
> length:
> > >> >
> > >> >     1st READLINK: 1100-byte relative target → nfs_path grows to
> ~1060B
> > >> >     2nd READLINK: 1128-byte relative target → writes to offset 2189
> > >> >                   → overflows nfs_path_buff[2048] by ~141 bytes
> > >> >
> > >> >   EXPLOIT CHAIN
> > >> >
> > >> >     1. Portmap lookup  → attacker returns fake mountd/nfsd ports
> > >> >     2. Mount           → attacker returns fake file handle
> > >> >     3. Lookup          → attacker returns file handle
> > >> >     4. Read            → attacker returns NFSERR_ISDIR (triggers
> > >> symlink)
> > >> >     5. Readlink #1     → 1100-byte relative symlink (grows path)
> > >> >     6. Readlink #2     → OVERFLOW — overwrites nfs_path pointer to
> > >> >                          attacker-planted "/x" string, state machine
> > >> > survives
> > >> >     7. Mount/Lookup    → re-enters NFS flow with controlled path
> > >> >     8. Read            → attacker serves ARM shellcode as file
> content
> > >> >                          → written to image_load_addr (0x42000000)
> > >> >     9. Code execution  → shellcode runs, writes "PWNED!" to UART
> > >> >
> > >> >   PROOF
> > >> >
> > >> >   GDB output after exploit:
> > >> >
> > >> >     === SHELLCODE AT 0x42000000 ===
> > >> >        0x42000000:  mov   r0, #9
> > >> >        0x42000004:  lsl   r0, r0, #24      @ r0 = 0x09000000 (UART)
> > >> >        0x42000008:  mov   r1, #0x50        @ 'P'
> > >> >        0x4200000c:  str   r1, [r0]         @ write to UART
> > >> >        ...
> > >> >
> > >> >     === AFTER EXECUTION ===
> > >> >        pc = 0x42000040   (completed, infinite loop)
> > >> >        r0 = 0x09000000   (UART base)
> > >> >        r1 = 0x0a         (newline)
> > >> >
> > >> >     === QEMU UART ===
> > >> >        Loading: *T #PWNED!
> > >> >
> > >> >   SUGGESTED FIX
> > >> >
> > >> >   Add a bounds check before the memcpy in nfs_readlink_reply():
> > >> >
> > >> >     --- a/net/nfs-common.c
> > >> >     +++ b/net/nfs-common.c
> > >> >     @@ -670,6 +670,10 @@
> > >> >          if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt
> +
> > >> rlen)
> > >> > > len)
> > >> >              return -NFS_RPC_DROP;
> > >> >
> > >> >     +    int current_len = strlen(nfs_path) + 1;
> > >> >     +    if (current_len + rlen >= sizeof(nfs_path_buff))
> > >> >     +        return -NFS_RPC_ERR;
> > >> >     +
> > >> >          if (*((char *)&rpc_pkt.u.reply.data[2 +
> nfsv3_data_offset]) !=
> > >> > '/') {
> > >> >
> > >> >   I have a full advisory document and working PoC (Python script,
> ~300
> > >> >   lines) attached. The PoC runs as a rogue NFS server and was tested
> > >> >   against QEMU ARM virt with qemu_arm_defconfig.
> > >> >
> > >> >   I'm requesting a CVE for this issue. I plan to follow standard
> 90-day
> > >> >   coordinated disclosure. Please let me know if you need anything
> else
> > >> >   or would like to discuss the fix.
> > >> >
> > >> >   Thanks,
> > >> >   Murtaza Munaim
> > >>
> > >> > # Security Advisory: U-Boot NFS Symlink Chain Remote Code Execution
> > >> >
> > >> > ## Summary
> > >> >
> > >> > A buffer overflow vulnerability in U-Boot's NFS client allows a
> rogue
> > >> NFS
> > >> > server to achieve remote code execution on the target device during
> > >> network
> > >> > boot. No authentication is required. The attacker serves crafted NFS
> > >> > symlink responses that overflow the global `nfs_path_buff[2048]`
> buffer,
> > >> > corrupting adjacent pointers and hijacking the NFS state machine to
> > >> deliver
> > >> > and execute arbitrary shellcode.
> > >> >
> > >> > ## Affected Software
> > >> >
> > >> > - **Product:** Das U-Boot
> > >> > - **Component:** `net/nfs-common.c`, function `nfs_readlink_reply()`
> > >> > - **Affected versions:** All versions with NFS boot support (at
> least
> > >> since NFSv3 support was added; tested on 2026.04-rc5, commit
> `c704af3c8b0`)
> > >> > - **Configurations:** Any build with `CONFIG_CMD_NFS=y` (NFS boot
> > >> enabled)
> > >> >
> > >> > ## CVSS Score
> > >> >
> > >> > **8.1 (High)** — CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
> > >> >
> > >> > - **Attack Vector:** Network (rogue NFS server)
> > >> > - **Attack Complexity:** High (target must be performing NFS boot)
> > >> > - **Privileges Required:** None
> > >> > - **User Interaction:** None (beyond initiating NFS boot, which is
> > >> typically automatic)
> > >> > - **Impact:** Complete compromise of bootloader execution context
> > >> >
> > >> > ## Vulnerability Details
> > >> >
> > >> > ### Root Cause
> > >> >
> > >> > In `net/nfs-common.c`, the function `nfs_readlink_reply()`
> processes NFS
> > >> > READLINK responses (symlink target resolution). At line 667, it
> reads
> > >> the
> > >> > symlink target length (`rlen`) from the RPC reply:
> > >> >
> > >> > ```c
> > >> > rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);
> > >> > ```
> > >> >
> > >> > The bounds check at line 669 validates that `rlen` fits within the
> **RPC
> > >> > packet buffer** (1152 bytes):
> > >> >
> > >> > ```c
> > >> > if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) >
> > >> len)
> > >> >     return -NFS_RPC_DROP;
> > >> > ```
> > >> >
> > >> > However, it does **not** validate that `rlen` fits within the
> > >> **destination
> > >> > buffer** `nfs_path_buff[2048]`. When processing a relative symlink
> (line
> > >> > 672-680), the target is appended to the existing path:
> > >> >
> > >> > ```c
> > >> > strcat(nfs_path, "/");
> > >> > pathlen = strlen(nfs_path);
> > >> > memcpy(nfs_path + pathlen,
> > >> >        (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset],
> > >> >        rlen);
> > >> > nfs_path[pathlen + rlen] = 0;
> > >> > ```
> > >> >
> > >> > ### Exploitation via Symlink Chaining
> > >> >
> > >> > A single READLINK response cannot overflow the buffer because
> `rlen` is
> > >> > capped at ~1128 bytes (by the packet size check) and `nfs_path`
> starts
> > >> > short (~10 bytes). However, by chaining **two** symlink resolutions:
> > >> >
> > >> > 1. **First READLINK:** Server returns a ~1100-byte relative symlink
> > >> target
> > >> >    with directory separators (`a/b/c/.../x`). After `nfs_dirname()`,
> > >> >    `nfs_path` retains ~1060 bytes.
> > >> >
> > >> > 2. **Second READLINK:** Server returns a ~1128-byte relative symlink
> > >> >    target. The `memcpy` writes to offset `1061 + 1128 = 2189`,
> exceeding
> > >> >    `nfs_path_buff[2048]` by approximately **141 bytes**.
> > >> >
> > >> > ### What Gets Overwritten
> > >> >
> > >> > The overflow corrupts global variables adjacent to `nfs_path_buff`
> in
> > >> BSS
> > >> > (verified via `nm` on QEMU ARM build):
> > >> >
> > >> > | Offset from buffer end | Variable | Type | Impact |
> > >> > |------------------------|----------|------|--------|
> > >> > | +0 | `nfs_path` | `char *` | Pointer to current NFS path —
> controls
> > >> future memory writes |
> > >> > | +4 | `nfs_filename` | `char *` | Pointer to filename component |
> > >> > | +8 | `nfs_download_state` | `enum` | Controls boot
> success/failure |
> > >> > | +12 | `filefh3_length` | `uint` | NFS file handle length |
> > >> > | +16 | `filefh` | `char[64]` | NFS file handle — controls which
> file
> > >> is read |
> > >> >
> > >> > ### Code Execution Chain
> > >> >
> > >> > By overwriting `nfs_path` to point to a short valid path string
> (e.g.,
> > >> > `"/x"`) planted within the overflow payload itself, the attacker
> keeps
> > >> the
> > >> > NFS state machine alive. The state machine then:
> > >> >
> > >> > 1. Re-mounts the filesystem (MOUNT `/`)
> > >> > 2. Looks up the file (LOOKUP `x`)
> > >> > 3. Reads file content (READ) — the server serves **arbitrary
> shellcode**
> > >> > 4. `store_block()` writes the shellcode to `image_load_addr` (e.g.,
> > >> `0x42000000`)
> > >> >
> > >> > The shellcode is now in memory at a known address. In typical
> embedded
> > >> boot
> > >> > configurations, `bootm` or `go` is called on the load address after
> NFS
> > >> > download, executing the attacker's code.
> > >> >
> > >> > ## Proof of Concept
> > >> >
> > >> > A complete working exploit (`nfs_rce.py`) is provided. It was tested
> > >> > against U-Boot 2026.04-rc5 running on QEMU ARM
> (`qemu_arm_defconfig`,
> > >> > Cortex-A15, 256MB RAM).
> > >> >
> > >> > ### Tested Exploit Chain
> > >> >
> > >> > ```
> > >> > [1] PORTMAP      → returned fake mountd/nfsd ports
> > >> > [2] MOUNT        → returned fake file handle
> > >> > [3] LOOKUP       → returned success with file handle
> > >> > [4] READ         → returned NFSERR_ISDIR → triggered symlink
> > >> > [5] READLINK #1  → 1100-byte relative symlink (grew nfs_path to
> ~1060B)
> > >> > [6] READLINK #2  → 1128-byte overflow payload
> > >> >                     Overwrote nfs_path → valid "/x" string
> > >> >                     State machine survived and continued
> > >> > [7] MOUNT/LOOKUP → re-entered NFS flow with attacker-controlled path
> > >> > [8] READ         → served 68-byte ARM shellcode → written to
> 0x42000000
> > >> > [9] EXECUTION    → shellcode wrote "PWNED!" to PL011 UART
> > >> > ```
> > >> >
> > >> > ### GDB Verification
> > >> >
> > >> > ```
> > >> > === SHELLCODE AT 0x42000000 ===
> > >> >    0x42000000:  mov   r0, #9
> > >> >    0x42000004:  lsl   r0, r0, #24      @ r0 = 0x09000000 (UART base)
> > >> >    0x42000008:  mov   r1, #0x50        @ 'P'
> > >> >    0x4200000c:  str   r1, [r0]         @ write to UART
> > >> >
> > >> > === AFTER EXECUTION ===
> > >> >    pc = 0x42000040   (completed, hit infinite loop)
> > >> >    r0 = 0x09000000   (UART base)
> > >> >    r1 = 0x0a         (newline — last char written)
> > >> >
> > >> > === QEMU UART OUTPUT ===
> > >> >    Loading: *T #PWNED!
> > >> >    >>> CODE EXECUTION CONFIRMED <<<
> > >> > ```
> > >> >
> > >> > ### Reproduction Steps
> > >> >
> > >> > ```bash
> > >> > # 1. Build U-Boot for QEMU ARM with NFS enabled
> > >> > cd u-boot
> > >> > make CROSS_COMPILE=arm-none-eabi- qemu_arm_defconfig
> > >> > # Enable CONFIG_CMD_NFS=y in .config
> > >> > make CROSS_COMPILE=arm-none-eabi- -j$(nproc)
> > >> >
> > >> > # 2. Start the exploit server
> > >> > python3 nfs_rce.py
> > >> >
> > >> > # 3. Start QEMU
> > >> > qemu-system-arm -machine virt -cpu cortex-a15 -m 256M -nographic \
> > >> >     -bios u-boot.bin \
> > >> >     -netdev user,id=net0,net=10.0.2.0/24,dhcpstart=10.0.2.15 \
> > >> >     -device virtio-net-device,netdev=net0
> > >> >
> > >> > # 4. In U-Boot console:
> > >> > setenv ipaddr 10.0.2.15
> > >> > setenv serverip 10.0.2.2
> > >> > nfs 0x42000000 10.0.2.2:/boot/uImage
> > >> >
> > >> > # 5. Observe "PWNED!" in UART output (or verify via GDB at
> 0x42000000)
> > >> > ```
> > >> >
> > >> > ## Suggested Fix
> > >> >
> > >> > Add a bounds check against `nfs_path_buff` size before the `memcpy`
> in
> > >> > `nfs_readlink_reply()`:
> > >> >
> > >> > ```c
> > >> > --- a/net/nfs-common.c
> > >> > +++ b/net/nfs-common.c
> > >> > @@ -670,6 +670,11 @@ static int nfs_readlink_reply(uchar *pkt,
> unsigned
> > >> int len)
> > >> >       if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt +
> > >> rlen) > len)
> > >> >               return -NFS_RPC_DROP;
> > >> >
> > >> > +     /* Validate symlink target fits in nfs_path_buff */
> > >> > +     int current_len = strlen(nfs_path) + 1; /* +1 for '/' */
> > >> > +     if (current_len + rlen >= sizeof(nfs_path_buff))
> > >> > +             return -NFS_RPC_ERR;
> > >> > +
> > >> >       if (*((char *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset]) !=
> > >> '/') {
> > >> >               int pathlen;
> > >> > ```
> > >> >
> > >> > Additionally, consider replacing `strcat`/`memcpy` with
> bounds-checked
> > >> > alternatives throughout the NFS path handling code.
> > >>
> > >> Thanks for the report, please see
> > >> https://docs.u-boot.org/en/latest/develop/sending_patches.html for
> how
> > >> to properly submit a patch, thanks!
> > >>
> > >> --
> > >> Tom
> > >>
> > >
>
>
>
> --
> Tom
>


More information about the U-Boot mailing list