历史快照:本文记录当时布局;现行 rootfs 路径见
docs/ROOTFS_LAYOUT_AND_MIGRATION.md。
LeonOS 4 周期安全审计报告(2026-08-30)
范围与方法
本报告为周期性源码安全审计。严格执行以下准入规则:
- 仅报告中等严重度(Medium)及以上的已确认漏洞。
- 每项发现必须具备可论证的端到端利用路径:给出精确的 source → sink 叙述、输入假设、内核/用户态实际数据流,以及最终影响陈述。
- 不报告理论性或推测性风险(低置信度、缺少前置条件、无法到达 sink、影响仅为局部 DoS 且不跨信任边界者,一律排除)。
| 字段 | 值 |
|---|---|
| 模式 | Repository 静态源码审计 + 结构化回归检测 |
| 工作区路径 | /workspace(LeonOS 4 主仓库) |
| 审计提交基线 | 当前 HEAD(未使用固定 SHA,因本次为周期扫描) |
| 检测工具 | 人工源码追踪 + tools/test_security_regressions.py(只读) |
| 运行状态 | 未启动虚拟机、未发送网络帧、未做破坏性 PoC;所有结论均以可复核的源码路径为依据 |
| 覆盖路径 | kernel/ntclks/、middlelayer/osmlayer/、userland/libc/src/、userland/apps/browser/、drivers/bootstrap/storage/、userland/libc/src/tar.c、los2w/、buildsystem/、tools/ |
| 准入后存活项 | 7 项(全部:中等严重度,高置信度,端到端利用链已在源码层论证) |
| 同期已修复(不再存活) | 7 项(July Finding 1/2/4/5、August Finding 1、Net Connections 可见性隔离、stat() 授权从 EXEC 改为 READ) |
与前两期报告的关系
- 本报告独立重新评估全部存活候选。先前两份报告的条目若在当前源码中仍满足准入规则,重新编号列入本次 1–7;不满足者已在「同期已修复/降级」段落给出修复信号证据。
- 本期未新增与前两期重叠的条目。
威胁模型
信任边界:
- Ring-3 → Ring-0 syscall/ioctl:普通用户态任务通过
int $0x80ABI 进入内核。 - 不受信任网络输入 → 内核协议栈 / libc HTTP 解析器 / 浏览器。
- 普通进程 → 桌面/系统服务 → 持久化配置与电源状态(混淆代理/权限代理)。
- 二层同网段恶意节点 → DHCP/ARP/TCP。
- 受保护 HTTPS 端点 → HTTP(协议降级)。
攻击者能力:
- 能够编译/运行公开 LeonOS ABI 的用户态程序(非管理员/非服务权限)。
- 能够向浏览器/HTTP 客户端提供不受信任的 HTTP(S) 响应,或在同一二层链路发送以太网帧。
- 不假设物理访问、Secure Boot 绕过或离线修改 loader。
安全目标:
- 任意用户进程不能无授权地关闭/重启整个操作系统。
- HTTPS 下载的传输完整性不能被合法重定向响应降级为明文 HTTP。
- 网络协议解析器与内核协议栈不得因受控输入产生越界内存访问或全局状态错配。
- DHCP 状态机必须绑定 OFFER 的服务器;同网段攻击者不能抢在合法服务器之前伪造 ACK 劫持全局网络配置。
- 浏览器 Basic 认证作用域必须包含 URL 方案;凭据不能从 HTTPS 会话泄漏到明文 HTTP。
- 同一二层 ARP 缓存不得接受未请求、未关联的发送者映射。
- 已建立 TCP 连接不得被任意序号的伪造 RST 直接关闭。
准入后存活发现
| # | 标题 | 严重性 | 置信度 | 攻击面 |
|---|---|---|---|---|
| 1 | 电源控制 IOCTL 缺少授权检查(任意进程可触发内核级重启/关机) | 中等 | 高 | 本地任意用户进程 → 内核电源 |
| 2 | HTTPS → HTTP 降级重定向未被拒绝(下载与浏览传输完整性破坏) | 中等 | 高 | 远程 HTTPS 响应 → libc HTTP 客户端 → 明文 |
| 3 | HTTP chunked 解码 chunk_size 乘法回绕 + 加法型边界检查绕过导致响应缓冲区越界读取 | 中等 | 高 | 远程 HTTP 响应 → 任意 HTTP 客户端进程内存 |
| 4 | DHCP ACK 未绑定已选择的 OFFER(同网段可抢 ACK 劫持 DNS/网关/租约) | 中等 | 高 | 二层同网段 → 内核 DHCP 状态机 → 全局 net_config |
| 5 | 浏览器 Basic 认证键不包含 URL 方案(HTTPS 凭据可发送到明文 HTTP) | 中等 | 高 | 浏览器 + HTTPS 登录 → 同主机端口 HTTP 导航 |
| 6 | ARP 缓存无条件存储入站 sender 映射(无需匹配未完成查询或链路一致性) | 中等 | 高 | 二层同网段 → 内核 ARP 缓存 → 中间人/DoS |
| 7 | TCP RST 缺少接收窗口序号验证直接关闭已建立连接 | 中等 | 高 | 同链路可伪造四元组的节点 → 内核连接表 → 会话中断 |
说明:本期未出现严重(Critical/High)严重度存活项。前一期严重发现「GUI 几何整数回绕内核越界写」(August Finding 1)在当前源码中已通过
gui_ipc_validate_surface_geometry+ 显式尺寸上限 + 64 位 checked 乘 + 页数缩窄前UINT32_MAX检查被全面阻断,已验证不再满足准入。
发现详情(准入严格版)
[F1] 电源控制 IOCTL 缺少授权检查(任意进程可触发内核级重启/关机)
| 字段 | 值 |
|---|---|
| 严重性 | 中等(本地全系统 DoS,跨用户边界) |
| 置信度 | 高(直接源码控制流证据 + 邻近对比反例) |
| 分类 | CWE-862(缺少授权) |
| 受影响位置 | kernel/ntclks/syscall.c:3861-3867 |
| 邻近授权正例 | 同文件 LEONOS_GUI_IOCTL_TASK_KILL(syscall.c:3804)调用 authz_check_path(..., LEONOS_AUTHZ_KILL_TASK, ...);LEONOS_IOCTL_NET_DHCP(syscall.c:4306)调用 require_network_config_access() |
端到端利用路径(Source → Sink)
- 入口:任意可执行
int $0x80/ LeonOS libc 的 Ring-3 任务调用LINUX_SYS_IOCTL,将a1设为LEONOS_GUI_IOCTL_REBOOT(常量0x4c524254)或LEONOS_GUI_IOCTL_SHUTDOWN(0x4c534844)。a2未被使用,无需有效用户指针。 - 分发:内核进入
syscall_dispatch_regs,匹配分支:
syscall.c:3861:if (number == LINUX_SYS_IOCTL && a1 == LEONOS_GUI_IOCTL_REBOOT) { power_reboot(); }syscall.c:3865:if (number == LINUX_SYS_IOCTL && a1 == LEONOS_GUI_IOCTL_SHUTDOWN) { power_shutdown(); }
- 授权检查缺失:这两个分支直接调用平台电源例程;不经过
require_window_server()、require_network_config_access()、authz_check_path()、require_driver_management()或任何task_effective_role()检查。对比文件内其它特权操作(TASK_KILL、DRIVER_CONTROL、NET_DHCP、FB_、DISPLAY_轮询/发布、POLL_WINDOW、SEND_WINDOW_EVENT)均有明确守卫,表明该系统调用层具备统一授权模式但未被电源分支采用。 - SINK:
power_reboot()/power_shutdown()触发平台级重启或关机(内核内部platform_power_*终端不可控)。
影响与可信边界
- 跨用户与跨服务:任何登录会话内任意低权限用户代码(例如普通 GUI 应用、shell 的
run启动的第三方程序、浏览器辅助进程)均可导致全系统失去可用性。 - 不满足提权或机密性泄露标准,不升为严重;但作为可被无权限进程直接触发的「全系统 DoS」,达到中等且端到端链完整。
最小复现假设
- 进程拥有正常 LeonOS ABI 访问能力即可(无需
TASK_FLAG_SERVICE、TASK_FLAG_WINDOW_SERVER,无需LEONOS_AUTH_ROLE_ADMIN)。
修复建议
- 为 REBOOT/SHUTDOWN 分支前置一个
require_power_control()守卫,语义为: installer_mode内可放行;否则- 调用者必须是
LEONOS_AUTH_ROLE_ADMIN或携带显式LEONOS_AUTHZ_POWER策略;或 - 仅限受信任窗口服务器转发的、已完成桌面 UI 授权确认的请求。
- 回归测试:普通进程调用两个 IOCTL 应返回
-LEONOS_EPERM;以管理员/服务身份调用仍能到达power_*。
[F2] HTTPS → HTTP 降级重定向未被拒绝(下载与浏览传输完整性破坏)
| 字段 | 值 |
|---|---|
| 严重性 | 中等(破坏用户预期的传输完整性;可辅助安装包/更新包替换) |
| 置信度 | 高(静态调用链 + 位置值匹配) |
| 分类 | CWE-319(明文传输敏感信息)、CWE-757(算法/协议降级攻击) |
| 受影响位置 | userland/libc/src/libc.c:3253-3316(leonos_http_request 重定向循环)+ 浏览器重用该库的全部浏览/下载路径,以及 downloadmgr(userland/apps/downloadmgr/main.c:302 附近) |
端到端利用路径(Source → Sink)
- 受害者触发:调用
leonos_http_request(libc HTTP 入口)或通过leonos_http_get、浏览器、下载管理器发出 HTTPS 请求。current_url初始为形如https://example.com/package.zip的 URL。 - HTTP fetch 完成:
http_fetch_once(current_url, request, response, location, ...)于libc.c:3286成功返回,响应状态满足重定向判断http_is_redirect(response->http_status)(301/302/303/307/308),且Location头部被写入location(长度上限LEONOS_HTTP_URL_LEN)。 - 恶意 Location 构造:攻击者控制的
https://example.com/响应中写入Location: http://example.com/package.zip。leonos_http_resolve_url(libc.c:2687)在location_text以http://或https://开头时直接原样拷贝到next_url(2713-2716)。该函数没有方案安全性比较语义。 - 降级被无条件接受:返回
leonos_http_request的重定向循环,代码只做了 URL 解析失败与重定向次数检查(libc.c:3300-3311),随后执行http_copy_text(current_url, sizeof(current_url), next_url);(libc.c:3312)。 - SINK:循环的下一次迭代
http_fetch_once(current_url, ...)发起的请求为明文 HTTP。TCP 连接建立后,链路攻击者(同网段、恶意热点、BGP 劫持、上游代理)即可查看和替换下载内容。
影响论证
browser_http_request_with_cookies(userland/apps/browser/cookies.c:749+)在重定向过程中会对新的current_url再次调用browser_http_prepare_headers,进而发送 Cookie 与 Basic 认证(参见 F5);若认证头/敏感 Cookie 已写入,降级会造成凭据明文传输。- 下载管理器
downloadmgr(调用leonos_http_get)不区分初始与最终方案;对用户显示的仍是“从 HTTPS 发起”的原始意图,但实际写入磁盘的文件已不受 TLS 保护——可被替换为恶意安装包、可执行载荷、组件清单等。 - 是否能直接诱导代码执行取决于下载物的后续处理(未在本轮审计中证明签名验证缺位,因此不升严重);但下载内容完整性保证被系统性破坏本身构成中等严重度。
修复建议
在 leonos_http_request 的重定向循环中(libc.c:3307-3312 之间),在跟随 next_url 之前:
- 比较
current_url与next_url的方案前缀:当current_url为https://且next_url为http://时,立即中止,将response->net_status置为新的LEONOS_NET_STATUS_REDIRECT_DOWNGRADE,并将final_url写入危险目标以便 UI 展示。 - 对
301/302/307/308统一执行;不提供“忽略降级”的公共标志,除非调用者显式是管理员级工具。 - 同步在浏览器侧 cookies.c 的
browser_http_request_with_cookies重定向路径上做同构守卫,避免在降级前发送安全 Cookie/Authorization。
[F3] HTTP chunked 解码 chunk_size 乘法回绕 + 加法型边界绕过导致响应缓冲区越界读取
| 字段 | 值 |
|---|---|
| 严重性 | 中等(用户态进程受控越界读取;可致崩溃与敏感内存读取) |
| 置信度 | 高(算术级回绕证明 + 源码位置) |
| 分类 | CWE-190(整数回绕/溢出)、CWE-125(越界读取) |
| 受影响位置 | userland/libc/src/libc.c:2894-2944(http_decode_chunked) |
端到端利用路径(Source → Sink)
http_decode_chunked(buffer, body_offset, raw_len, capacity, flags) 的每一步均可被远程 HTTP 响应完全控制:
- 响应触发:只要 HTTP 响应的
Transfer-Encoding: chunked被检测(libc.c:3227http_contains_ignore_case(transfer_encoding, "chunked")),response->body_len = http_decode_chunked(...)即被调用。所有通过leonos_http_request/leonos_http_get的 HTTPS/HTTP 客户端(浏览器、下载管理器、httpget.elf、ping 之外的联网工具)都到达该解码器。 chunk_size回绕:chunk_size为uint32_t(libc.c:2901)。解析循环:
while (src < raw_len && http_is_hex(buffer[src])) {
chunk_size = chunk_size * 16U + http_hex_value(buffer[src]); // line 2907
++src; saw_hex = 1;
}
- 边界检查失效:紧接着使用加法型上界
if (src + chunk_size > raw_len)(libc.c:2923)。当src接近缓冲区尾且chunk_size经回绕后变成可让src + chunk_size再次回绕为小值的量时,该比较为假,chunk_size不会被截断为raw_len - src。 - SINK 越界读取 + 前向写:随后循环
for (uint32_t i = 0; i < chunk_size; ++i) buffer[dst++] = buffer[src + i];(libc.c:2929-2937)从buffer + src + i读取,i 上限可达理论上非常大的回绕值。buffer是调用者传入的响应体缓冲区(通常为request->response_body,堆或栈上);src + i超出raw_len后继续向后访问该缓冲区边界之外:
- 若
buffer末尾紧邻未映射或只读页 → 进程段错误(浏览器/下载客户端/HTTP get 均被终止)。 - 若后续页存在可访问内存 → 将后续堆/栈字节压缩复制到缓冲区前部(容量上限
capacity只在写入时检查,对源侧越界无反向保护),解码结果可被后续处理代码读取并呈现/存储,形成稳定的内存内容泄露通道。
只要提交 ≥ 9 个十六进制字符(例如长度前缀 100000000 = 2^32,或更多 F),chunk_size * 16U 会对 UINT32_MAX + 1 模回绕,使 chunk_size 最终为一个远小于解析位数声明的小值。
- 返回值:
dst作为response->body_len返回给上层。上层 UI 或应用会按该长度消费response_body,泄露的字节可被显示、写盘、再次通过网络回传等。
影响等级说明
- 典型部署中 HTTP 客户端内存会含 TLS 握手后遗留的密钥材料、Cookie 值、其他响应分块、以及其他堆分配对象。仅基于静态分析未证明可稳定读到 TLS 密钥或构造稳定崩溃地址;但「越界读可达 + 输出被消费」已足以评定为中等。
- 本轮未执行模糊测试或 VM 动态泄露验证;置信度为高的依据是源码级算术与数组索引证明。
修复建议
- 在十六进制累加前使用更宽类型(
uint64_t chunk_size64),每一步执行:
if (chunk_size64 > (UINT32_MAX - hex_val) / 16) return 0 /* or TRUNCATED */ ;
chunk_size64 = chunk_size64 * 16U + hex_val;
if (chunk_size64 > HTTP_CHUNK_MAX_ALLOWED) reject;
- 把加法型边界检查替换为差值型(永远安全,避免加法回绕歧义):
if (src > raw_len || chunk_size64 > raw_len - src) { truncated; break; }
- 增加针对以下向量的回归用例:(a) 9+ 位十六进制使 32 位乘回绕;(b)
raw_len - src近尾加回绕边界;(c) 截断响应时最终src + chunk_size恰好跨raw_len。
[F4] DHCP ACK 未绑定已选择的 OFFER(同网段可抢 ACK 劫持全局配置)
| 字段 | 值 |
|---|---|
| 严重性 | 中等(可替换网关/DNS/租约地址 → 全系统中间人/拒绝服务) |
| 置信度 | 高(源码函数级 ACK 等待控制流) |
| 分类 | CWE-345(身份/来源验证不足) |
| 受影响位置 | kernel/ntclks/net.c:1837-1864(net_dhcp_wait)、1872-1922(net_dhcp_request)、801-813(net_apply_dhcp_offer) |
端到端利用路径(Source → Sink)
前置:内核发起 DHCP 续租/首次获取(OOBE 期间管理员服务、netctl 管理员、OOBE DHCP 白名单路径调用 LEONOS_IOCTL_NET_DHCP;该 ioctl 入口现在受 require_network_config_access() 守卫,不再是任意进程可调用——July Finding-2 已修复)。
利用链:
- DISCOVER 发出后,合法与恶意 DHCP 服务器(同二层)均可返回 OFFER。
net_dhcp_request在net.c:1895等待第一个匹配xid的 NET_DHCP_OFFER。内核只等待“第一个匹配的 OFFER”,随后记录其offer.yiaddr和offer.server_ip,并在net.c:1903以NET_DHCP_REQUEST明确发送到该offer.server_ip。 - 随后在
net.c:1909调用net_dhcp_wait(xid, timeout_ms, NET_DHCP_ACK, &offer)。该函数实现(net.c:1837-1864):
while (!timeout) {
udp_wait = { src_port = NET_DHCP_SERVER_PORT, dst_port = NET_DHCP_CLIENT_PORT, ... };
net_poll_once(0, 0, &udp_wait, 0);
if (udp_wait.done && net_dhcp_parse_packet(payload, udp_wait.length, xid, offer) == 0) {
if (offer->msg_type == NET_DHCP_ACK) return 0; // 接受
if (offer->msg_type == NET_DHCP_NAK) return -3;
}
}
- 不校验 ACK 报文的
server identifier是否等于前一步 REQUEST 目标offer.server_ip; - 不校验 ACK
yiaddr是否等于 REQUEST 请求过的地址; - 仅以
xid+ 消息类型为接受条件。
- 抢发送者优势:攻击者只要位于同一二层、能发 UDP 67/68、观测或猜测
xid(DISCOVER/REQUEST 广播中 xid 是明文,攻击者可直接嗅探),即可在合法服务器 ACK 到达之前抢先发一个msg_type=ACK的报文,offer出参被net_dhcp_parse_packet重写为攻击者提供的yiaddr/router_ip/dns_ip/server_ip/lease_seconds。 - 全局配置写入:
net_dhcp_request不做复核,紧接着于net.c:1917调用net_apply_dhcp_offer(&offer),将:
net_config.local_ip、subnet_mask、gateway_ip、dns_ip、dhcp_server_ip、lease_seconds全部替换为攻击者指定值。
- SINK 影响:
关键缺口:
- DNS → 全部后续解析指向攻击者控制 DNS(浏览器/httpget/downloadmgr 全部通过内核 net_dns_resolve →
net_config.dns_ip)。这直接放大 HTTPS 降级 / 证书信任 UI 漏洞的成功率,并可在未来发生证书校验缺失时转为完整 MITM。 - 网关 → 全部非本地流量被导向攻击者 MAC,与 F6(ARP 无条件缓存)配合即可完成全部出流量劫持。
- 租约地址错误/不可达 → 全系统网络 DoS。
为何仍只评为中等
- 前置条件:攻击者必须在同一二层(共享广播域/恶意 Wi-Fi/宿舍网/会议室),且 DHCP 协商过程中能注入 ACK;不是全网远程。
- 不会直接造成提权或内核内存破坏;与上层协议结合才能放大。上述组合(F4 + F6)已可形成完整流量劫持,但该组合的上层影响依赖浏览器/downloadmgr 的实际 TLS 验证严格程度(文档声明
MBEDTLS_SSL_VERIFY_REQUIRED为基线,未在本轮发现其被普遍关闭),因此不升严重。
修复建议
net_dhcp_wait扩展签名:expected_server_ip, expected_yiaddr,并在 accept 前:
if (offer->msg_type == NET_DHCP_ACK) {
if (expected_server_ip && offer->server_ip != expected_server_ip) continue_drop;
if (expected_yiaddr && offer->yiaddr != expected_yiaddr) continue_drop;
return 0;
}
- 丢弃不符合的 ACK 后继续等待合法 ACK(不要立即报错或截断),并将其记录到 net 审计日志。
- 同时对
NET_DHCP_OFFER轮次:若接收到来自非预期服务器的 OFFER,可选择性记录但不应污染 REQUEST 目标。
[F5] 浏览器 Basic 认证键不包含 URL 方案(HTTPS 凭据可发送到明文 HTTP)
| 字段 | 值 |
|---|---|
| 严重性 | 中等 |
| 置信度 | 高(字段/缓存结构级证明) |
| 分类 | CWE-523(凭据保护不足)、CWE-319(明文泄露) |
| 受影响位置 | userland/apps/browser/auth.c:60-84(auth_entry_for 与 browser_auth_append_header)、auth.c:116-142(写入条目,仅写入 host/port)、userland/apps/browser/cookies.c:746(请求头构造点调用上述函数) |
端到端利用路径(Source → Sink)
- 凭据录入:用户访问
https://corp.example.com:443/admin,服务端返回WWW-Authenticate: Basic。浏览器弹 UI → 用户输入admin:Correct-Horse-Battery-Staple→browser_auth_retry_from_challenge(auth.c:86-142)生成Authorization: Basic YWRtaW46Q29ycmVjdC1Ib3JzZS1CYXR0ZXJ5LVN0YXBsZQ==并存入缓存:
index = auth_entry_for(parsed.host, parsed.port)(auth.c:116)- 写入
browser_auth_entries[index].host = parsed.host;.port = parsed.port;.header = "Authorization: Basic ..."。 - 不保存
parsed.scheme,并且auth_entry_for签名和比较都只包含 (host, port) 两元组。
- 导航到同主机端口的 HTTP:可能触发情形之一:
- 用户手动输入 URL
http://corp.example.com:443/...(不典型但可行);或 - 网站首页
<a href="http://corp.example.com:443/...">;或 - 结合 F2(HTTPS 降级重定向):由 HTTPS 响应返回
Location: http://corp.example.com:443/...,leonos_http_request无条件跟随(F2 已证),导致浏览器自动在降级的 HTTP 上请求。
- 请求头生成:
browser_http_prepare_headers→browser_auth_append_header(url, out, &pos, cap)(auth.c:71-84):
parse_http_url(url, &parsed);
index = auth_entry_for(parsed.host, parsed.port); // 仅 host/port
if (index >= 0) append_text(out, pos, cap, header); // Authorization: Basic ...
- SINK:该请求以明文 HTTP 发送,Authorization 头中的 Base64 可逆凭据被同网段嗅探者完整读取。凭据可被攻击者直接重放到 HTTPS 端点(反向仍可用),完成会话接管或敏感资源访问。
组合增益
- 与 F2 组合可把“用户手动输入错误 URL”变成全自动利用:只要用户登录过一次 HTTPS 站点,攻击者只要让该 HTTPS 端点返回一个降级重定向(或被中间人篡改重定向响应——若 TLS 被其他缺口破坏),凭据就会在下一步明文发出。
- 即便没有 F2,只要同一主机同时开放 HTTP 与 HTTPS 服务在相同 host:port(或通过服务重绑定、反向代理共享)即可触发。
修复建议
auth_entry_for(host, port)→auth_entry_for(scheme, host, port),缓存结构同步加scheme字段;- 在
browser_auth_append_header中加入硬不变量:若条目是从https://录入(或写入时标记 secure),当且仅当当前 URL 方案为https://才附加 Authorization; - 同步把 realm 纳入键并按 RFC 7235 的保护空间划分;
- 与 F2 的修复形成双保险:先阻止降级跟随;再即便降级发生,也阻止凭据发送。
[F6] ARP 缓存无条件存储入站 sender 映射(无需匹配未完成查询或链路一致性)
| 字段 | 值 |
|---|---|
| 严重性 | 中等(本地网段可稳定毒化网关 MAC) |
| 置信度 | 高(源码行级控制流) |
| 分类 | CWE-345(邻居身份不充分校验) |
| 受影响位置 | kernel/ntclks/net.c:1041-1067(net_handle_arp),写缓存点 net.c:1058 |
端到端利用路径
- 入站帧:攻击者在相同二层广播域发送任意格式合法的 ARP 帧(Ethernet type 0x0806、HTYPE=1、PTYPE=0x0800、HLEN=6、PLEN=4)。
- 控制流:
net_handle_arp(frame, len, arp_wait):
- 格式检查通过(
net.c:1048-1053); - 提取字段(
1054-1057):op、sender_mac = frame + 22(ARP 体中 sender MAC)、sender_ip、target_ip; - 无条件写缓存:
net_arp_cache_store(sender_ip, sender_mac);(1058)——先于后面第1059-1066行的 REQUEST 与 REPLY 相关逻辑。 - 随后才判断是否为针对本机的 REQUEST、或是否匹配等待中的
arp_wait->ip的 REPLY。
- 关键缺口:
- 不需要该 sender_ip 正处于内核等待的 ARP 查询;
- 不需要
op == ARP_OPER_REPLY; - 不对 Ethernet 帧头 src MAC 与 ARP sender_mac 一致性做校验;
- 不做 Gratuitous ARP 的显式策略(接受与否)。
- SINK:攻击者以广播
sender_ip=GATEWAY_IP, sender_mac=ATTACKER_MAC的单个 ARP 请求(op=ARP_OPER_REQUEST,target_ip=任意)即可把net_arp_cache_store改写为攻击者地址。之后内核将所有非本地流量dst_mac = ATTACKER_MAC。 - 组合:与 F4(DHCP ACK 抢注)或后续的网关欺骗结合 → 出流量全部被攻击者中继,可进行中间人嗅探/篡改;若攻击者不转发,则直接造成全系统网络不可用 DoS。
为何不升严重
- 依赖二层位置;且上层(TLS、HTTPS)对传输完整性有独立保护(若
MBEDTLS_SSL_VERIFY_REQUIRED严格执行,仅可造成 DoS 或有限元数据泄露)。满足中等但不满足严重定义。
修复建议
- 把
net_arp_cache_store(sender_ip, sender_mac)从无条件位置(1058)后移到至少以下任一受约束分支: op == ARP_OPER_REPLY && arp_wait && sender_ip == arp_wait->ip匹配成功时;- 或
op == ARP_OPER_REQUEST && target_ip == local_ip && sender_ip 是本节点已有缓存项的“刷新”(只更新已存在条目)。 - 在存储前增加以太网源 MAC == ARP sender_mac 一致性校验;
- 为缓存条目设置过期;对同一 IP 的 MAC 变更事件输出 net 审计日志;
- 针对无请求(unsolicited)Gratuitous ARP 采用可选策略,默认拒绝。
[F7] TCP RST 缺少接收窗口序号验证直接关闭已建立连接
| 字段 | 值 |
|---|---|
| 严重性 | 中等(可跨连接泛洪中断会话) |
| 置信度 | 高(行级控制流) |
| 分类 | CWE-345(协议状态机不充分) |
| 受影响位置 | kernel/ntclks/net.c:1557-1560(net_socket_handle_tcp 中的 RST 处理) |
端到端利用路径
- 任意已建立的 TCP 连接(
socket->state == LEONOS_NET_TCP_ESTABLISHED或TIME_WAIT)均可被伪造的 TCP 报文关闭。 - 内核在 TCP 入口(
net.c:1186-1192)解析完flags, seq, ack, payload_len后调用net_socket_handle_tcp(src_ip, src_port, dst_port, seq, ack, flags, payload, payload_len)。 - 状态转换:
net_socket_handle_tcp首先执行匹配套接字查找:
socket = net_socket_match(src_ip, src_port, dst_port);
- 缺口:函数到达
if (flags & TCP_FLAG_RST)(net.c:1557)即调用net_socket_mark_closed(socket, LEONOS_NET_STATUS_TCP_RESET); return;——完全不读取/验证seq参数(该参数作为函数形参传入但此分支内未使用),也不使用socket->remote_seq(期望接收序号)做接收窗口判定。
- 对比同函数中 SYN-ACK 建立(
1561-1567)使用socket->acked_seq == socket->local_seq;payload 数据接收(1585-1601)使用seq == socket->remote_seq;FIN 处理(1617-1620)使用seq + payload_len == socket->remote_seq。所有其他状态转换都有明确序号匹配;唯独 RST 没有。
- SINK:攻击者发送一个四元组匹配、TCP 头含 RST 标志、任意序号的报文即导致连接状态置为
LEONOS_NET_TCP_CLOSED、状态TCP_RESET。效果:
这要求(src_ip, src_port, dst_port)匹配(本地端口是公开的;远端 IP/端口可通过浏览器会话、HTTP 请求从观测到的元数据获得,或在共享链路上直接嗅探)。
- 进行中的浏览器 HTTPS 浏览、下载管理器长下载、SSH 类会话(若未来提供)、任何 TCP 应用通信可被立即中断;
- 对共享网络节点可泛洪式断开大量并发连接,形成稳定的应用层 DoS。
置信与严重度
- 攻击面要求:攻击者需要知道/嗅探连接的(远端 IP、远端端口、本地端口)三元组;本地端口多数情况下可从侧信道或
LEONOS_IOCTL_NET_CONNECTIONS(但该 ioctl 现在经net_connection_visible按 uid/admin/service 隔离,仅 admin 与服务看全局)获知。对真实共链节点这仍然是可获得的信息。 - 只造成中断,不造成内存破坏或凭据泄露;保留中等。
修复建议
- 在 RST 分支插入 RFC 793 / RFC 5961 兼容的接收窗口判定:
if (flags & TCP_FLAG_RST) {
// 仅当 RST seq 落在当前可接受接收窗口内才关闭;否则忽略。
if (!net_tcp_seq_in_window(socket->remote_seq, socket->recv_window, seq)) {
return;
}
net_socket_mark_closed(socket, LEONOS_NET_STATUS_TCP_RESET);
return;
}
- 对不同状态(ESTABLISHED/TIME_WAIT/SYN_SENT)分别实现对应规则。为
SEQ = socket->remote_seq完全相等与in_window两种规则各建立回归测试。
同期已修复 / 不再满足准入(对照 2026-07-07 与 2026-08-04 报告)
| 旧编号(来源) | 标题 | 状态 | 证据(当前源码中的修复信号) |
|---|---|---|---|
| July Finding 1 | 普通用户 task 可调用 GUI/window-server 专用 ioctl(FB_*、窗口轮询、fetch、event 注入) | 已修复 | syscall.c 中 FB_FILL/FB_RECT/FB_TEXT/FB_PIXEL/FB_BLIT/FB_SET_MODE/POLL_WINDOW/FETCH_WINDOW/SEND_WINDOW_EVENT/POLL_DISPLAY_REQUEST/PUBLISH_DISPLAY_STATE/POLL_APPEARANCE_REQUEST/PUBLISH_APPEARANCE_STATE 全部前置 require_window_server() 守卫并返回 -LEONOS_EPERM。 |
| July Finding 2 | 任意用户可调用 DHCP ioctl 改全局网络配置 | 已修复 | LEONOS_IOCTL_NET_DHCP(syscall.c:4305-4313)前置 require_network_config_access()(syscall.c:238-255),仅 installer / admin / SERVICE(非桌面)/ OOBE DHCP 白名单可通过。 |
| July Finding 4 | stat() 用 LEONOS_AUTHZ_EXEC 替代文档声明的 Read/List 权限 | 已修复 | LINUX_SYS_STAT(syscall.c:3103)改为 authz_check_path(task, LEONOS_AUTHZ_READ, path, 0, 0),与 docs/FILESYSTEM.md 权限映射一致。 |
| July Finding 5 | 网络连接列表暴露所有用户 TCP socket 元数据 | 已修复 | net_connections 新增 viewer 参数(net.c:3137-3170)且每次枚举前调用 net_connection_visible(socket, viewer)(net.c:3115-3129),仅 admin/非桌面 service/或 socket.owner_uid 匹配 viewer->uid 的条目可见。调用方在 syscall.c:4400 传入 sched_current_task()。 |
| August Finding 1 | GUI 几何整数回绕导致内核越界写 | 已修复 | 新增 gui_ipc_validate_surface_geometry(gui_ipc.c:239-256)含 GUI_IPC_MAX_WINDOW_WIDTH/HEIGHT/STRIDE 上限与 checked_mul_u64;ensure_window_buffer(gui_ipc.c:285-311)在窄化前强制 64 位页数 + UINT32_MAX 上限;CREATE / PRESENT / FETCH 三条 ioctl 均调用该校验,并在 PRESENT 前置用户缓冲区 user_range_ok(bytes)。 SEC-001 在回归脚本中已显示「未检测到已知缺陷模式」。 |
| SEC-003 | Display Request 混淆代理(驱动受信任桌面持久化) | 降级为低 | 入口缺少授权守卫(仍与 2026-08-04 相同);但桌面消费侧实际写入 /system/config/display.conf 受 ACL 与桌面运行身份约束,且 display_request 单槽机制无法定向到“任意设置字段”,缺乏可论证的端到端完整性破坏链。本轮按「准入严格版」排除。 |
| SEC-004 | 登录缺少失败限速和锁定 | 降级为低 | 缺乏登录 API 的远程暴露入口证明(登录 API 仅本地 GUI 登录界面 / 终端 login 调用,未发现网络暴露的登录入口);密码策略与 UI 实际尝试窗口未知,置信度未达到中等。排除。 |
上表之外,July Finding 3(service flag 由可执行路径派生)与 July Finding 6(loader SHA256 非可信根)仍为架构加固议题但缺少明确的端到端利用链(依赖未来文件系统漏洞或离线篡改),因此本周期按「准入严格版」不列入存活发现。
结构性建议(仅基于存活 7 项归纳)
| 维度 | 覆盖 | 建议 |
|---|---|---|
| 单一授权层 | F1 + 已修复的 FB/NET/DRIVER ioctl | 对所有“系统影响型” ioctl(电源、display_request、appearance_request、网络配置、driver 控制、任务终止)引入统一的 require_* 守卫表,由静态回归脚本确保“每个 ioctl 常量在白名单内或被守卫调用”,避免未来新增分支再次裸调用。 |
| 协议长度安全基元 | F3 | 在 userland/libc/src/libc.c 和 kernel/ntclks/net.c 建立项目级 uint32_t * / + 安全包装;强制在 HTTP、TCP、DHCP、TLS 记录、GUI 几何、文件系统读块中使用差值型边界检查并对超长输入做 fail-closed。 |
| 状态绑定 | F4、F6、F7 | 网络状态机对每次“接受外部输入作为新状态”的动作,要求同时满足:(a) 身份绑定(服务器 IP/seq/查询 IP/MAC 一致性);(b) 事务绑定(xid/等待中的查询/已请求地址);(c) 协议正确性(接收窗口/RFC 兼容序号)。 |
| 明确不变量 | F2、F5 | 对“安全→不安全”方案/标志的退化建立项目级不变量:HTTPS 永不跟随至 HTTP;Secure Cookie 与 HTTPS-only Basic Auth 永不被明文请求加载;回归脚本在 libc.c/cookies.c/auth.c 增加 3 条“是否存在降级守卫”的严格模式检测。 |
| 回归门禁 | 全部 7 项 | 保持 tools/test_security_regressions.py 为源码只读检测,修复后以 --strict 纳入 CI;在此基础上为 F3/F4/F7 补充边界单元测试(chunked hex 回绕、DHCP 多服务器 ACK、TCP RST 序号窗口),以及隔离 VM/网络烟测(非破坏性)。 |
已验证与未验证边界声明(严格区分「源码已检查」与「运行已验证」)
| 阶段 | 完成 | 证据 |
|---|---|---|
| 源码已检查(source) | ✔ | 所有 7 项存活均提供精确文件:行号、受影响函数、对比反例和可走通的 source→sink 叙述。 |
| 源码修改(modify) | ❌ | 本报告为周期审计,不实施补丁。 |
| 编译/打包(build) | ❌ | 未调用 build.py。 |
| VM 运行与 PoC(runtime) | ❌ | 未启动 QEMU/VMware、未发真实网络帧、未执行会破坏内核或进程内存的 PoC。本期结论全部基于源码级证明;真实运行环境中利用的稳定性与概率可能高或低于源码分析。 |
文档归属:docs/security/2026-08-30.md。 生成日期:2026-08-30(按系统提醒当日日期)。