← Back to CVE List
CVE-2026-82391NVD
Vulnerability Summary
The HTTP/2 front-end of the Zephyr HTTP server aliased connection receive state as a scratch output. In `subsys/net/lib/http/http_server_http2.c`, `handle_http2_static_fs_resource()` passed `&client->data_len` as the `file_size` out-parameter of `http_server_find_file()`. `client->data_len` is the count of unprocessed bytes remaining in the fixed-size per-connection receive buffer `client->buffer[CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE]` (256 bytes by default), an invariant every frame handler and the socket receive path rely on. Serving any static file therefore replaced that bounded value with the file's on-disk size, and nothing resets it afterwards. The defect is reached by an unauthenticated HTTP/2 `GET` for any file under the web root, in applications that register a static-filesystem resource (`HTTP_RESOURCE_TYPE_STATIC_FS`) with `CONFIG_FILE_SYSTEM=y`; `CONFIG_HTTP_SERVER_VERSION_2` is enabled by default and no TLS or credentials are required on this path. Once `client->data_len` exceeds the buffer size, the frame dispatch loop in `subsys/net/lib/http/http_server_core.c` keeps parsing past the end of the received data, and the peer controls the bytes at `client->cursor`, so it can choose which fabricated frame the state machine consumes next by appending bytes after its request. The inflated length defeats the only guards on the buffer. `parse_http_frame_header()` and the per-frame handlers read and skip up to a 24-bit frame length beyond the buffer (out-of-bounds read); the compaction `memmove(client->buffer, client->cursor, client->data_len)` at the end of request handling copies the file-size number of bytes into the 256-byte buffer, overflowing the rest of `struct http_client_ctx` — including the `current_detail`/`current_stream` pointers, the `parser_settings` function-pointer table and the `inactivity_timer` work handler — and the adjacent client contexts in static memory; and the subsequent `zsock_recv(client->fd, client->buffer + client->data_len, sizeof(client->buffer) - client->data_len, 0)` underflows its size argument, writing attacker-supplied data of attacker-chosen length out of bounds. The impact is remote memory corruption in the server's single address space: at minimum a crash or hang of the device, and plausibly control-data overwrite leading to code execution. The HTTP/1 and HTTP/3 static-filesystem handlers already used a local variable and are unaffected; the fix makes the HTTP/2 handler use a local `size_t file_size` as well. Affected components - `subsys/net/lib/http` Affected versions `>= 4.0.0, <= 4.4.2` Fix Projected fixed version: 4.5.0 (the fix is merged on `main` but not yet released; this forecast should be confirmed against the actual release). Introduced by: e55278a87be4e5534ba772b23e459882866bcd6d (net: lib: http_server: add static fs resource, 2024-07-31) Evidence - subsys/net/lib/http/http_server_http2.c:494-500 — post-fix call passes a local `file_size`; pre-fix it passed `&client->data_len`, overwriting the RX residue with the file size - subsys/net/lib/http/http_server_http2.c:535 — `remaining = file_size` (pre-fix `remaining = client->data_len`), the aliasing that made the corruption look intentional - include/zephyr/net/http/server.h:418-466 — `unsigned char buffer[HTTP_SERVER_CLIENT_BUFFER_SIZE]` plus `size_t data_len` documented as 'Data left to process in the buffer' - subsys/net/lib/http/Kconfig:152-157 — `CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE` default 256, so any larger served file makes `data_len` exceed the buffer - subsys/net/lib/http/http_server_core.c:872-881 — dispatch loop keeps running while `data_len > 0`, then `memmove(client->buffer, client->cursor, client->data_len)`: out-of-bounds write of file-size bytes into a 256-byte buffer - subsys/net/lib/http/http_server_core.c:1256-1257,1283 — `zsock_recv(client->fd, client->buffer + client->data_len, sizeof(client->buffer) - client->data_len, 0)` with only an equality guard `data_len == sizeof(buffer)`: size underflow and out-of-bounds destination - subsys/net/lib/http/http_server_http2.c:1893-1899 — `parse_http_frame_header()` only checks `buflen < HTTP2_FRAME_HEADER_SIZE`, which the inflated `data_len` defeats, so 9 bytes are read past the received data - subsys/net/lib/http/http_server_http2.c:1771-1777 — frame handlers gate on `client->data_len < frame->length` then advance `cursor` by up to a 24-bit length, walking far outside `client->buffer` - subsys/net/lib/http/http_server_http2.c:1442-1494 — `handle_incomplete_http_header()` returns `-EAGAIN` (reaching the core `memmove`) and itself does an unbounded `memmove` sized from the corrupted `data_len` - subsys/net/lib/http/http_server_http2.c:1645-1660 — reachability: `handle_http_frame_headers()` dispatches an unauthenticated HTTP/2 GET to the static-FS handler - subsys/net/lib/http/http_server_http1.c:695-698 — HTTP/1 twin already used a local `file_size`, confirming HTTP/2 was the only aliasing site (post-fix `grep -rn "&client->data_len" subsys/` is empty) - doc/security/vulnerabilities.rst:2327-2350 — CVE-2026-8023 in the same static-FS handler, establishing this deployment as remotely reachable and unauthenticated <!-- zepsec:original --> <details> <summary>Original report as submitted by the reporter</summary> Summary `handle_http2_static_fs_resource()` in `subsys/net/lib/http/http_server_http2.c` passes `&client->data_len` — the live HTTP/2 receive-length counter — as the `file_size` out-parameter of `http_server_find_file()`. That function unconditionally overwrites it with the on-disk file size (`file_size = dirent.size`, `http_server_core.c:972`). `client->data_len` is never restored. The HTTP/2 frame-state machine, `memmove`, and `zsock_recv` all use that corrupted counter, producing a deterministic out-of-bounds write reachable by any unauthenticated HTTP/2 client. `CONFIG_HTTP_SERVER_VERSION_2` is default y. Vulnerable code ```c // http_server_http2.c:496-499 ret = http_server_find_file(fname, sizeof(fname), &client->data_len, 0, NULL); // ^^^^^^^^^^^^^^^^ // live RX counter passed as file_size out-param ``` `http_server_find_file()` writes the file's on-disk size into whatever pointer it receives: ```c // http_server_core.c:972 *file_size = dirent.size; // overwrites client->data_len with file size ``` After the handler returns, `handle_http_request()` re-enters its parse loop because `client->data_len > 0` (it is now the file size, not bytes in the socket buffer): ```c // http_server_core.c:557 } while (ret >= 0 && client->data_len > 0); // http_server_core.c:563-565 — OOB read/overwrite if (client->data_len > 0) { memmove(client->buffer, client->cursor, client->data_len); } // http_server_core.c:704-705 — recv length underflows to huge size_t zsock_recv(client->fd, client->buffer + client->data_len, sizeof(client->buffer) - client->data_len, 0); ``` `client->buffer` is `unsigned char buffer[HTTP_SERVER_CLIENT_BUFFER_SIZE]` (default 256 bytes, `Kconfig:95-98`). For any served file larger than 256 bytes, `client->data_len` exceeds the buffer size, so: - `memmove` copies `file_size` bytes inside a 256-byte buffer → out-of-bounds read. - `zsock_recv` receives `sizeof(buffer) − file_size` which underflows to a huge `size_t`, writing attacker-supplied bytes far past the buffer end. Contrast with HTTP/1.1 The HTTP/1.1 handler in `http_server_http1.c` correctly uses a local variable and is not affected: ```c // http_server_http1.c:585 / 609 size_t file_size; ret = http_server_find_file(fname, sizeof(fname), &file_size, ...); ``` Proof of concept Required config: `CONFIG_HTTP_SERVER=y`, `CONFIG_HTTP_SERVER_VERSION_2=y` (default), `CONFIG_FILE_SYSTEM=y`, one `HTTP_RESOURCE_TYPE_STATIC_FS` resource containing a file larger than 256 bytes (e.g. a 1 KB `index.html`). 1. Open a TCP connection to the Zephyr HTTP server. 2. Send the HTTP/2 connection preface: `PRI HTTP/2.0\r\n\r\nSM\r\n\r\n` 3. Send an empty SETTINGS frame: `00 00 00 04 00 00 00 00 00` 4. Send a HEADERS frame for `GET /index.html` (`END_HEADERS | END_STREAM`). 5. After receiving the 200 response, send any pipelined HTTP/2 frame (e.g. a 9-byte PING). Result: `memmove(client->buffer, client->cursor, ~1000)` executes inside a 256-byte buffer; `zsock_recv` is then called with `SIZE_MAX - ~999` as its length, writing attacker bytes far past the buffer. With `CONFIG_ASSERT=n` (production default) no assertion halts this. Outcome: memory corruption and crash; on MMU-less targets, potential overwrite of adjacent kernel state. Fix Use a local variable in `handle_http2_static_fs_resource()`, matching the HTTP/1.1 handler: ```c / vulnerable / ret = http_server_find_file(fname, sizeof(fname), &client->data_len, 0, NULL); / fixed / size_t file_size; ret = http_server_find_file(fname, sizeof(fname), &file_size, 0, NULL); ``` Never pass `&client->data_len` to any function that writes a file size into it. Affected components - `subsys/net/lib/http/http_server_http2.c` — `handle_http2_static_fs_resource()` - `subsys/net/lib/http/http_server_core.c` — `http_server_find_file()`, `handle_http_request()` </details> <!-- zepsec:original --> <!-- zepsec:patch --> Patches | Branch | Pull request | Status | | --- | --- | --- | | `main` | #117267 — `4e0a5f38b14d` | merged | | `v4.4-branch` | #117603 | merged | | `v4.3-branch` | #117602 | merged | | `v3.7-branch` | — | not affected | <!-- zepsec:patch --> For more information If you have any questions or comments about this advisory: Open an issue in zephyr Email us at Zephyr-vulnerabilities embargo: 2026-11-10
CVSS v3.1 Base Metrics — Score 9.1
Attack VectorNetwork
Attack ComplexityLow
Privileges RequiredNone
User InteractionNone
ScopeUnchanged
ConfidentialityNone
IntegrityHigh
AvailabilityHigh
Affected & Patched Versions
- zephyr >= 4.0.0, <= 4.4.2
- zephyr 4.5.0