
CVE-2022-0847 (Dirty Pipe) Linux कर्नेल विशेषाधिकार वृद्धि भेद्यता का प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट और विस्तृत विश्लेषण, जिसमें परीक्षण और डीबगिंग के लिए Docker वातावरण शामिल है।
[toc]
यह लेख पहली बार Huawei सुरक्षा सार्वजनिक खाते में प्रकाशित हुआ था, यह ब्लॉग संस्करण है (अधिक पूर्ण)
पहला लिंक: https://mp.weixin.qq.com/s/6VhWBOzJ7uu80nzFxe5jpg
भेद्यता संख्या: CVE-2022-0847 (उपनाम: गंदा पाइप)
भेद्यता उत्पाद: Linux कर्नेल - splice syscall
प्रभावित संस्करण: Linux 5.8 पैच f6dd975583bd द्वारा शुरू किया गया ~ 5.16.11, 5.15.25, 5.10.102 द्वारा ठीक किया गया
भेद्यता का प्रभाव: किसी भी पठनीय फ़ाइल पर एक पृष्ठ से अधिक नहीं (पर्याप्त) सामग्री लिखना, स्थानीय विशेषाधिकार वृद्धि संभव।
भेद्यता विश्लेषण Docker: chenaotian/cve-2022-0847 (यदि अभी भी पहुँच योग्य नहीं है तो मैंने इसे अभी तक अपलोड नहीं किया है)
प्रदान करता है:
प्रारंभ:
cd ~/cve-2022-0847
gcc exp.c -o exp --static && cp exp ./rootfs && cd rootfs
find . | cpio -o --format=newc > ../rootfs.img
cd ../
./boot.sh
डिबगिंग:
gdb ./vmlinux
target remote :10086
directory /root/linux-5.13
b do_splice
b copy_page_to_iter_pipe
b pipe_write
ignore 3 15
...
p *(struct pipe_inode_info *) pipe
p (struct pipe_buffer)pipe->bufs[0]
भेद्यता का संक्षिप्त सिद्धांत यह है कि
spliceफ़ंक्शन को कॉल करके फ़ाइल को "ज़ीरो-कॉपी" रूप मेंpipeपर भेजा जा सकता है। कोड स्तर पर ज़ीरो-कॉपी का अर्थ है कि फ़ाइल कैश पेज (page cache) को सीधेpipeकेbufपेज के रूप में उपयोग किया जाता है। लेकिन यहाँ एक चर प्रारंभ न करने की भेद्यता शुरू की गई है, जिससे फ़ाइल कैश पेज बाद मेंpipeचैनल में सामान्यpipeकैश पेज के रूप में माना जाता है और "आगे लिखा" जा सकता है और इस प्रकार छेड़छाड़ की जा सकती है। हालाँकि, इस स्थिति में, कर्नेल इस कैश पेज को "गंदा पेज" नहीं मानेगा, और थोड़े समय (अगले रिबूट आदि तक) के लिए डिस्क पर फ्लश नहीं होगा। इस अवधि के दौरान, उस फ़ाइल तक पहुँचने वाले सभी परिदृश्य छेड़छाड़ किए गए फ़ाइल कैश पेज का उपयोग करेंगे, जिससे "थोड़े समय में किसी भी पठनीय फ़ाइल में मनमाना लेखन" का संचालन प्राप्त होता है। इससे स्थानीय विशेषाधिकार वृद्धि पूरी की जा सकती है।
पैच के अनुसार, भेद्यता बिंदु copy_page_to_iter_pipe फ़ंक्शन में स्थित है, जहाँ buf->flags के प्रारंभिकीकरण को जोड़ा गया है, इसलिए यह एक चर प्रारंभ न करने की भेद्यता है।

copy_page_to_iter_pipe का कॉल बिंदु splice सिस्टम कॉल में दिखाई देता है। splice फ़ंक्शन (सिस्टम कॉल) "ज़ीरो-कॉपी" विधि द्वारा फ़ाइल सामग्री को पाइप में स्थानांतरित करता है। पारंपरिक रूप से फ़ाइल सामग्री को सीधे पाइप में डालने की तुलना में यह बेहतर प्रदर्शन करता है। विस्तार से नीचे वर्णित है।
सबसे पहले, भेद्यता उपनाम गंदा पाइप है, आइए पहले पाइप (pipe) को समझें। pipe कर्नेल द्वारा प्रदान किया गया एक संचार पाइप है, जिसे pipe/pipe2 फ़ंक्शन द्वारा बनाया जाता है, जो दो फ़ाइल डिस्क्रिप्टर लौटाता है, एक डेटा भेजने के लिए और दूसरा डेटा प्राप्त करने के लिए, पाइप के दो सिरों के समान, विशिष्ट उपयोग के बारे में अधिक नहीं बोलेंगे।

कर्नेल में कार्यान्वयन के बारे में संक्षेप में, आमतौर पर पाइप कैश स्थान की कुल लंबाई 65536 बाइट्स को पृष्ठों के रूप में प्रबंधित किया जाता है, कुल 16 पृष्ठ (एक पृष्ठ 4096 बाइट्स), पृष्ठ निरंतर नहीं होते हैं, बल्कि एक सरणी के माध्यम से प्रबंधित होते हैं, जो एक रिंग लिंक्ड लिस्ट बनाते हैं। दो लिंक्ड लिस्ट पॉइंटर बनाए रखे जाते हैं, एक लिखने के लिए (pipe->head), और एक पढ़ने के लिए (pipe->tail), यहाँ मुख्य रूप से pipe_write फ़ंक्शन का विश्लेषण किया गया है:
linux-5.13\fs\pipe.c : 400 : pipe_write
static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
struct file *filp = iocb->ki_filp;
struct pipe_inode_info *pipe = filp->private_data;
unsigned int head;
ssize_t ret = 0;
size_t total_len = iov_iter_count(from);
ssize_t chars;
bool was_empty = false;
bool wake_next_writer = false;
··· ···
··· ···
head = pipe->head;
was_empty = pipe_empty(head, pipe->tail);
chars = total_len & (PAGE_SIZE-1);
if (chars && !was_empty) {
//[1] पाइप कैश खाली नहीं है, तो वर्तमान अंतिम पृष्ठ से "आगे" लिखने का प्रयास करें
unsigned int mask = pipe->ring_size - 1;
struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];
int offset = buf->offset + buf->len;
if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
offset + chars <= PAGE_SIZE) {
/*[2] महत्वपूर्ण, यदि PIPE_BUF_FLAG_CAN_MERGE ध्वज मौजूद है, तो इस पृष्ठ को आगे लिखने की अनुमति है
* यदि लिखने की लंबाई पृष्ठ सीमा को पार नहीं करती है, तो आगे लिखें, अन्यथा नया पृष्ठ शुरू करें */
ret = pipe_buf_confirm(pipe, buf);
···
ret = copy_page_from_iter(buf->page, offset, chars, from);
···
}
buf->len += ret;
···
}
}
for (;;) {//[3] यदि पिछले पृष्ठ पर आगे नहीं लिख सकते, तो नया पृष्ठ शुरू करें
··· ···
head = pipe->head;
if (!pipe_full(head, pipe->tail, pipe->max_usage)) {
unsigned int mask = pipe->ring_size - 1;
struct pipe_buffer *buf = &pipe->bufs[head & mask];
struct page *page = pipe->tmp_page;
int copied;
if (!page) {//[4] एक नया पृष्ठ आवंटित करें
page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
if (unlikely(!page)) {
ret = ret ? : -ENOMEM;
break;
}
pipe->tmp_page = page;
}
spin_lock_irq(&pipe->rd_wait.lock);
head = pipe->head;
··· ···
pipe->head = head + 1;
spin_unlock_irq(&pipe->rd_wait.lock);
/* इसे बफर सरणी में डालें */
buf = &pipe->bufs[head & mask];
buf->page = page;//[5] नए आवंटित पृष्ठ को पृष्ठ सरणी में रखें
buf->ops = &anon_pipe_buf_ops;
buf->offset = 0;
buf->len = 0;
if (is_packetized(filp))
buf->flags = PIPE_BUF_FLAG_PACKET;
else
buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
//[6] ध्वज सेट करें, डिफ़ॉल्ट PIPE_BUF_FLAG_CAN_MERGE
pipe->tmp_page = NULL;
copied = copy_page_from_iter(page, 0, PAGE_SIZE, from);
//[7] कॉपी ऑपरेशन
··· ···
ret += copied;
buf->offset = 0;
buf->len = copied;
··· ···
}
··· ···
}
··· ···
return ret;
}