Go 移植版 pintheft - RDS zcopy 双重释放本地权限提升。属于 Dirty Frag 漏洞类别。
[!IMPORTANT] Arch Linux 是该漏洞最容易暴露的发行版之一,因为它是少数几个默认情况下通常可以加载 RDS 模块的发行版之一。
该漏洞位于 rds_message_zcopy_from_user() 函数中。该函数通过 FOLL_GET 逐个 GUP-钉住用户页面。如果后续页面发生页错误(例如 PROT_NONE 保护页),错误路径会对已钉住的页面调用 put_page(),然后 rds_message_purge() 再次对这些页面调用 __free_page(),因为 op_mmp_znotifier 已被置空,但 op_nents/sg 条目保持不变。当页面仍然有其他引用时,__free_page 会静默递减引用计数。每次失败的 sendmsg 恰好从第一个页面窃取一个引用。
CONFIG_INIT_ON_ALLOC_DEFAULT_ON通过 io_uring REGISTER_BUFFERS 钉住目标页面,这通过 FOLL_PIN 将 GUP_PIN_COUNTING_BIAS(1024)添加到引用计数。利用失败的 zcopy 发送窃取所有 1024 个钉引用。页面引用计数现在约为 1 (仅剩 PTE 映射)。munmap 走正常的 __folio_put 路径,该路径在释放前调用 mem_cgroup_uncharge(清除 memcg_data)。不会触发 bad_page 检查。
io_uring 在 bvec 数组中保留原始 struct page*,不进行存活检查。当页面被回收作为 suid 二进制文件的页缓存后,IORING_OP_READ_FIXED 通过该悬垂指针将我们的载荷写入其中。
IORING_REGISTER_CLONE_BUFFERS 递增 imu->refs。分叉的守护进程子进程保持克隆环 fd 打开 - io_buffer_unmap 看到 refs > 1 并跳过 unpin_user_folio,从而防止已释放页面上的引用计数损坏。
钉住到 CPU 0,在释放前清空过时的 PCP 条目 - 当页缓存分配器获取页面时,我们的页面会位于顶部。
REGISTER_BUFFERS(+1024) -> CLONE_BUFFERS(imu->refs=2) ->
daemon holds clone -> steal 1024 refs -> evict page cache ->
drain PCP -> munmap(free) -> pread(reclaim) ->
READ_FIXED(overwrite) -> verify -> exec -> root
go build -o pintheft-go .
./pintheft-go
成功后会通过 PTY 进入 root shell。磁盘上的二进制文件不会被修改。
sudo cp /tmp/.backup_pintheft_<pid> /usr/bin/su && sudo chmod u+s /usr/bin/su
CONFIG_RDS=m,CONFIG_RDS_TCP=m(通过 SO_RDS_TRANSPORT=2 自动加载)CONFIG_IO_URING=y 且 io_uring_disabled=0顺便说一句,我爱 Arch Linux :)