<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>阿德日志 &#187; vmsplice</title>
	<atom:link href="http://blog.delai.me/tag/vmsplice/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.delai.me</link>
	<description>我的自留地</description>
	<lastBuildDate>Thu, 29 Dec 2011 03:33:58 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>linux使普通用户获得root权限的vmsplice系统调用漏洞分析</title>
		<link>http://blog.delai.me/2008/12/linux-vmsplice.html</link>
		<comments>http://blog.delai.me/2008/12/linux-vmsplice.html#comments</comments>
		<pubDate>Mon, 29 Dec 2008 15:54:43 +0000</pubDate>
		<dc:creator>阿德</dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[Linux/GNU]]></category>
		<category><![CDATA[vmsplice]]></category>
		<category><![CDATA[漏洞]]></category>

		<guid isPermaLink="false">http://www.soulward.cn/?p=688</guid>
		<description><![CDATA[vmsplice系统调用是linux内核2.6.17第一次引入的，随后被发现存在能让普通用户提升到root权限的漏洞。该漏洞影响的版本网络上笼统的说法是：2.6.17-2.6.24.1，实际上更确切的说是：2.6.17- 2.6.22.17，2... ]]></description>
			<content:encoded><![CDATA[<a href='http://blog.delai.me/2008/12/linux-vmsplice.html' target="_blank" rel="nofollow"><img src="http://blog.shidelai.cn/wp-content/gallery/icons/linux-logo.jpg" style="border:0; float:left; margin: 0 1em .5em 0;" alt="linux使普通用户获得root权限的vmsplice系统调用漏洞分析" title="linux使普通用户获得root权限的vmsplice系统调用漏洞分析"/></a>
<h3><span style="color: #008000;"><strong>2008期末课程小论文</strong> 之一 </span></h3>
<h3><span style="color: #008000;">《linux使普通用户获得root权限的vmsplice系统调用漏洞分析》</span></h3>
<p><span style="color: #000000;">vmsplice系统调用是linux内核2.6.17第一次引入的，随后被发现存在能让普通用户提升到root权限的漏洞。</span><span style="color: #000000;">该漏洞影响的版本网络上笼统的说法是：2.6.17-2.6.24.1，实际上更确切的说是：2.6.17-</span> 2.6.22.17，2.6.23-2.6.23.15 和 2.6.24-2.6.24.1.</p>
<p><span style="color: #000000;">关于这个漏洞，国内很少有人写过什么原创性的文章进行介绍，因为上研究生操作系统课的课程报告就是做这个，所以把它放上来。漏洞虽然已经补上，但学习其机理，还是比较有好处，有意思的。<br />
</span></p>
<h2>一、预备知识</h2>
<h3>1. 本文的一些约定</h3>
<p style="padding-left: 30px;"><span style="color: #0000ff;">这个颜色的代码来自攻击程序</span><br />
<span style="color: #808000;">这个颜色的代码来自内核</span><br />
<span style="color: #ff0000;">这个颜色表示重要的地方，或者安装程序逻辑，下一步要进入的函数</span><br />
所提到的攻击代码是本文附带的exp.c</p>
<h3>2. vmsplice()介绍</h3>
<p>原型：long vmsplice(int fd, const struct iovec *iov, unsigned long nr_segs, unsigned int flags);<br />
其中：</p>
<blockquote><p><span style="color: #808000;">struct iovec<br />
{<br />
void __user *iov_base;<br />
__kernel_size_t iov_len;<br />
};</span></p></blockquote>
<p>这个系统调用将用户空间的内存映射到内核空间，从而避免了实际的内存写操作，提高了系统效率。这个功能的是主要是通过fs/splice.c的do_vmsplice()来实现。</p>
<h3>3. 有关Page的常量</h3>
<blockquote><p><span style="color: #808000;">#define PAGE_SHIFT	12<br />
#define PAGE_SIZE	(1UL &lt;&lt; PAGE_SHIFT)<br />
/*1UL：32位的unsigned int 1，左移12位，PAGE_SIZE=0&#215;1000*/<br />
#define PAGE_MASK	(~(PAGE_SIZE-1))<br />
/*PAGE_MASK=0&#215;000*/</span></p></blockquote>
<h2>二、Splice系统调用漏洞考古</h2>
<p><strong>2006 Jun 18</strong><br />
在发布的Linux kernel 2.6.17中引入vmsplice()，用于提高性能。<br />
没有人知道，对应的fs/splice.c中的get_iovec_page_array()函数存在漏洞。<br />
<!--inline-more--><br />
<strong>2007 Oct 09</strong><br />
在发布的Linux kernel 2.6.23 中<br />
加入了vmsplice_to_user()函数和copy_from_user_mmap_sem()函数,也存在同样的漏洞。</p>
<p><strong>2007 Dec 03</strong><br />
ID为CVE-2008-0009和CVE-2008-0010的漏洞报告被提交到CVE。<br />
分别指出vmsplice_to_user()和copy_from_user_mmap_sem()存在这个漏洞。<br />
但这两份报告有个错误：这两个漏洞存在于2.6.23-2.6.24而不是报告里说的2.6.22-2.6.24,因为这两个函数是在2.6.23里正式被加进来的。</p>
<p><strong>2008 Feb 05</strong><br />
ID为CVE-2008-0600的漏洞报告被提交到CVE<br />
指出:</p>
<p style="padding-left: 30px;">The vmsplice_to_pipe function in Linux kernel 2.6.17 through 2.6.24.1 does not validate a certain userspace pointer before dereference, which allows local users to gain root privileges via crafted arguments in a vmsplice system call, a different vulnerability than CVE-2008-0009 and CVE-2008-0010.</p>
<p>事实上，问题出在get_iovec_page_array()函数。但因为get_iovec_page_array()函数只被vmsplice_to_pipe()调用，数据参数都来自vmsplice_to_pipe()，所以，get_iovec_page_array()没有验证用户数据的合法性也可以被认为vmsplice_to_pipe()没有验证用户数据的合法性。</p>
<p><strong>2008 Feb 08</strong><br />
在发布的Linux kernel 2.6.24.1和2.6.23.15补丁中补上了CVE-2008-0009和CVE-2008-0010提到这个这两个漏洞。</p>
<p><strong>2008 Feb 09</strong><br />
ID为qaaz的Geek在milw0rm上公布了两个对应的POC代码，第一个是针对vmsplice_to_user()函数和copy_from_user_mmap_sem()函数的，另外一个是针对get_iovec_page_array()函数，</p>
<p><img class="aligncenter" title="linux" src="http://lh5.ggpht.com/_8kd-QOlHLBY/SVJc4Uc57JI/AAAAAAAAC-I/r4v1p0DpzZU/s800/qaaz.jpg" alt="" width="689" height="54" /></p>
<p><strong>2008 Feb 11</strong><br />
在发布的Linux kernel 2.6.24.2 、2.6.23.16和 2.6.22.18补丁中补上了get_iovec_page_array()函数的漏洞。</p>
<h2>三、漏洞攻击效果</h2>
<p style="text-align: center;"><img class="aligncenter" title="linux" src="http://lh4.ggpht.com/_8kd-QOlHLBY/SVJdjnqnWmI/AAAAAAAAC-U/rSw40MqDeoE/s800/2.6.19.1.jpg" alt="" width="654" height="359" />攻击成功</p>
<p style="text-align: center;"><img class="aligncenter" title="linux" src="http://lh6.ggpht.com/_8kd-QOlHLBY/SVJdmAVTIdI/AAAAAAAAC-c/qj5og1SNZds/s800/2.6.24.2.jpg" alt="" width="590" height="375" />打了补丁的2.6.24.2就攻击不成功了</p>
<h2>四、漏洞攻击真实案例</h2>
<p>This bug is being actively exploited in the wild &#8212; our server was just broken in to by an attacker using it. (They got a user&#8217;s password by previously compromising a machine somewhere else where that user had an account, and installed a modified ssh binary on it to record user names and passwords. Then they logged in to our site as that user, exploited CVE-2008-0010, and became root).</p>
<h2>五、the vmsplice() exploit story</h2>
<h3>1. 整个过程可以概括为下图：</h3>
<p>Online view: http://share.xmind.net/shidelai/vmsplice-loopholes-in-the-success-of-the-attack-1/</p>
<h3>2. 攻击程序做了如下六件事情</h3>
<p><strong>1)</strong>定义 kernel_code()<br />
这个函数会修改当前用户的udi和gid，所有者一切的目的就是让内核执行这个函数。</p>
<p><strong>2)</strong>定义了pages[5]，其中：</p>
<blockquote><p><span style="color: #0000ff;">proc_pages[0]-&gt;flags    = 1 &lt;&lt; PG_compound;<br />
proc_pages[0]-&gt;private  = (unsigned long) proc_pages[0];<br />
proc_pages[0]-&gt;count    = 1;</span></p></blockquote>
<p>这是为了把这个proc_page伪装成compound page，保证内核执行到/mm/Swap.c的</p>
<blockquote><p><span style="color: #808000;">void put_page(struct page *page)<br />
{<br />
if (unlikely(PageCompound(page)))<br />
put_compound_page(page);<br />
else if (put_page_testzero(page))<br />
__page_cache_release(page);<br />
}</span></p></blockquote>
<p>时候，内核进入put_compound_page(page);</p>
<blockquote><p><span style="color: #0000ff;">proc_pages[1]-&gt;lru.next = (long) kernel_code;</span></p></blockquote>
<p>内核执行到/mm/Swap.c的</p>
<blockquote><p><span style="color: #808000;">static void put_compound_page(struct page *page)<br />
{<br />
&#8230;.<br />
dtor = get_compound_page_dtor(page);<br />
(*dtor)(page);<br />
}</span></p></blockquote>
<p>的时候，get_compound_page_dtor会返回proc_pages[1]-&gt;lru.next。这样，kernel_code()就被内核执行了。</p>
<p><strong>3)</strong>将proc_pages[0] mmap到0&#215;0 address<br />
到时候内核被骗执行0地址第二个page的lru.next指向的函数时候，实际上执行的就是这里定义的proc_pages[1]-&gt;lru.next所指向的kernel_code()。</p>
<p><strong>4)</strong>close(pi[0]); close pipe_read，当内核执行到/fs/Splice.c的splice_to_pipe()</p>
<blockquote><p><span style="color: #808000;">for (;;) {<br />
if (!pipe-&gt;readers) {<br />
send_sig(SIGPIPE, current, 0);<br />
if (!ret)<br />
ret = -EPIPE;<br />
break;<br />
}<br />
&#8230;<br />
}<br />
&#8230;<br />
while (page_nr &lt; spd_pages)<br />
page_cache_release(spd-&gt;pages[page_nr++]);</span></p></blockquote>
<p>时候，if判断会返回真，于是跳出for循环，于是导致page_cache_release(spd-&gt;pages[page_nr++]);被执行。</p>
<p><strong>5)</strong>iov.iov_len  = ULONG_MAX;<br />
iov.iov_len等于32个1。这个是关键，它保证了内核中数值溢出，结果将内核骗到攻击程序设定的page上来。</p>
<p><strong>6)</strong>_vmsplice(pi[1], &amp;iov, 1, 0);<br />
执行vmsplice系统调用。</p>
<h3>3. 内核代码执行细节</h3>
<p><strong>1)</strong>执行_vmsplice(pi[1], &amp;iov, 1, 0);后，/fs/Splice.c中的</p>
<blockquote><p><span style="color: #808000;">long <strong><span style="color: #ff0000;">sys_vmsplice</span></strong>(int fd, const struct iovec __user *<span style="color: #00ff00;">iov</span>, unsigned long nr_segs, unsigned int flags)<br />
{<br />
&#8230;<br />
if (file-&gt;f_mode &amp; FMODE_WRITE)//往管道写<br />
error = <strong><span style="color: #ff0000;">vmsplice_to_pipe</span></strong>(file,<span style="color: #00ff00;"> iov</span>, nr_segs, flags);<br />
&#8230;<br />
}</span></p></blockquote>
<p>被调用。<br />
2)接着内核进入/fs/Splice.c中的</p>
<blockquote><p><span style="color: #808000;">static long <span style="color: #ff0000;"><strong>vmsplice_to_pipe</strong></span>(struct file *file, const struct iovec __user *<span style="color: #00ff00;">iov</span>,<br />
unsigned long nr_segs, unsigned int flags)<br />
{<br />
struct pipe_inode_info *pipe;<br />
<span style="color: #ff0000;">struct page *pages[PIPE_BUFFERS];   //请注意这里<br />
struct partial_page partial[PIPE_BUFFERS]; //#define PIPE_BUFFERS (16) //请注意这里<br />
struct splice_pipe_desc spd = {//请注意这里</span><br />
.pages = pages,<br />
.partial = partial,<br />
.flags = flags,<br />
.ops = &amp;user_page_pipe_buf_ops,<br />
};<br />
&#8230;<br />
spd.nr_pages = <span style="color: #ff0000;"><strong>get_iovec_page_array</strong></span><span style="color: #00ff00;">(iov</span>, nr_segs, pages, partial,<br />
flags &amp; SPLICE_F_GIFT);<br />
&#8230;<br />
return splice_to_pipe(pipe, &amp;spd);<br />
}</span></p></blockquote>
<p>3)接着内核进入/fs/Splice.c中的</p>
<blockquote><p><span style="color: #808000;">static int <span style="color: #ff0000;"><strong>get_iovec_page_array</strong></span>(const struct iovec __user *<span style="color: #00ff00;">iov</span>,<br />
unsigned int nr_vecs, struct page **pages,<br />
struct partial_page *partial, int aligned)<br />
{<br />
&#8230;&#8230;</span></p>
<p><span style="color: #808000;">if (copy_from_user_mmap_sem(&amp;entry, <span style="color: #00ff00;">iov</span>, sizeof(entry))) //entry = iov<br />
break;<br />
base = entry.iov_base;//内核缓冲区基地址<br />
len = entry.iov_len;//长度<br />
&#8230;&#8230;<br />
npages = (off + len + PAGE_SIZE &#8211; 1) &gt;&gt; PAGE_SHIFT;<br />
// npages = （0+ 32个1 + 1后面12个0 &#8211; 1 ）&gt;&gt; PAGE_SHIFT = （32个1 + 1 -1 + 1后面12个0 &#8211; 1）&gt;&gt; PAGE_SHIFT =0<br />
if (npages &gt; PIPE_BUFFERS &#8211; buffers)<br />
npages = PIPE_BUFFERS &#8211; buffers;<br />
error = <strong><span style="color: #ff0000;">get_user_pages</span></strong>(current, current-&gt;mm,<br />
(unsigned long) base, npages, 0, 0,<br />
&amp;pages[buffers], NULL);<br />
&#8230;&#8230;<br />
}</span></p></blockquote>
<p>4) 于是内核进入/mm/Memory.c中的</p>
<blockquote><p><span style="color: #808000;">int <span style="color: #ff0000;"><strong>get_user_pages</strong></span>(struct task_struct *tsk, struct mm_struct *mm,<br />
unsigned long start, int len, int write, int force,<br />
struct page **pages, struct vm_area_struct **vmas)<br />
{<br />
&#8230;&#8230;<br />
do {<br />
&#8230;&#8230;<br />
i++;<br />
start += PAGE_SIZE;<br />
len&#8211;;<br />
continue;<br />
}<br />
do {<br />
&#8230;&#8230;<br />
i++;<br />
start += PAGE_SIZE;<br />
len&#8211;;<br />
} while (len &amp;&amp; start &lt; vma-&gt;vm_end);<br />
} while (len);<br />
return i;<br />
}</span></p></blockquote>
<p><span style="color: #000000;">len这里已经是int型了，而不是ulong int，len从0被减到-32768(0xf0000000)再减1变成+32767(0x7fffffff)，最终返回i=46。</span></p>
<p>5) 然后i返回给/fs/Splice.c中的</p>
<blockquote><p><span style="color: #808000;">static int <span style="color: #ff0000;"><strong>get_iovec_page_array</strong></span>(const struct iovec __user *iov,<br />
unsigned int nr_vecs, struct page **pages,<br />
struct partial_page *partial, int aligned)<br />
{<br />
&#8230;&#8230;<br />
error = get_user_pages(current, current-&gt;mm,<br />
(unsigned long) base, npages, 0, 0,<br />
&amp;pages[buffers], NULL);<br />
//error = 46<br />
&#8230;&#8230;<br />
for (i = 0; i &lt; error; i++) {<br />
const int plen = min_t(size_t, len, PAGE_SIZE &#8211; off);<br />
partial[buffers].offset = off;<br />
partial[buffers].len = plen;<br />
off = 0;<br />
len -= plen;<br />
buffers++;<br />
}<br />
&#8230;&#8230;<br />
}</span></p></blockquote>
<p>在这个for循环中，partial[]数组大小是16，而循环确循环了46次，所以，溢出了。比partial先定义的pages[]指针数组被0覆盖。</p>
<p>6) 接着，函数返回到/fs/Splice.c的</p>
<blockquote><p><span style="color: #808000;">static long <span style="color: #ff0000;"><strong>vmsplice_to_pipe</strong></span>(struct file *file, const struct iovec __user *iov,<br />
unsigned long nr_segs, unsigned int flags)<br />
{<br />
&#8230;&#8230;<br />
spd.nr_pages = get_iovec_page_array(iov, nr_segs, pages, partial,<br />
flags &amp; SPLICE_F_GIFT);<br />
&#8230;&#8230;<br />
return <span style="color: #ff0000;"><strong>splice_to_pipe</strong></span>(pipe, &amp;spd);<br />
}</span></p></blockquote>
<p>7) 进入/fs/Splice.c的</p>
<blockquote><p><span style="color: #808000;">ssize_t <span style="color: #ff0000;"><strong>splice_to_pipe</strong></span>(struct pipe_inode_info *pipe,<br />
struct splice_pipe_desc *spd)<br />
{<br />
&#8230;&#8230;<br />
for (;;) {<br />
if (!pipe-&gt;readers) {<br />
send_sig(SIGPIPE, current, 0);<br />
if (!ret)<br />
ret = -EPIPE;<br />
break;<br />
}<br />
&#8230;&#8230;.<br />
}<br />
&#8230;&#8230;<br />
while (page_nr &lt; spd_pages)<br />
<span style="color: #ff0000;"><strong>page_cache_release</strong></span>(spd-&gt;pages[page_nr++]);<br />
return ret;<br />
}</span></p></blockquote>
<p>因为之前提到的pipe的读已经被关掉了，所以从for循环跳出。内核执行进入page_cache_release(spd-&gt;pages[page_nr++]);<br />
请注意：这里的spd-&gt;pages就是之前被溢出被0覆盖的pages[]指针数组。</p>
<p>8)由于</p>
<blockquote><p><span style="color: #808000;">#define page_cache_release(page)<strong><span style="color: #ff0000;"> put_page(page)</span></strong></span></p></blockquote>
<p>9) 于是进入了/mm/Swap.c中的</p>
<blockquote><p><span style="color: #808000;">void <span style="color: #ff0000;"><strong>put_page</strong></span>(struct page *page)<br />
{<br />
if (unlikely(PageCompound(page)))<br />
<span style="color: #ff0000;"><strong>put_compound_page</strong></span>(page);<br />
else if (put_page_testzero(page))<br />
__page_cache_release(page);<br />
}<br />
</span></p></blockquote>
<p>由于之前提到的，攻击程序已经将其已经被mmap到0地址空间的proc_pages[]伪装成compound page，所以，这里if判断后</p>
<p>10)内核进入同在/mm/Swap.c 的</p>
<blockquote><p><span style="color: #808000;">static void <span style="color: #ff0000;"><strong>put_compound_page</strong></span>(struct page *page)<br />
{<br />
page = compound_head(page);<br />
if (put_page_testzero(page)) {<br />
compound_page_dtor *dtor;<br />
dtor = <span style="color: #ff0000;"><strong>get_compound_page_dtor</strong></span>(page);<br />
(*dtor)(page);<br />
}<br />
}</span></p></blockquote>
<p>11)再进入/include/linux/Mm.h中的</p>
<blockquote><p><span style="color: #808000;">static inline compound_page_dtor *get_compound_page_dtor(struct page *page)<br />
{<br />
<strong>return (compound_page_dtor *)page[1].lru.next;</strong><br />
}</span></p></blockquote>
<p>OK，到此，proc_pages[1].lru.next所指向的函数指针也即kernel_code()函数指针返回给dtor，随后(*dtor)(page);执行了这个函数，于是，攻击成功。</p>
<hr /><h2>Comments</h2><ul><li><a href="http://blog.delai.me/2008/12/linux-vmsplice.html">2009-01-2</a>, <a href='http://www.olihe.com' rel='external nofollow' class='url'>盒子</a> writes: 嘿嘿～ :mrgreen: 啊德这总能找到好东西</li></ul><hr /><h2>Related posts:</h2><ul><li><a href="http://blog.delai.me/2010/11/qq-vs-360.html" rel="bookmark" title="Permanent Link: 腾讯那篇《致广大QQ用户的一封信》挺扯淡的">腾讯那篇《致广大QQ用户的一封信》挺扯淡的</a></li><li><a href="http://blog.delai.me/2008/02/linux-basic.html" rel="bookmark" title="Permanent Link: 最基础的Ubuntu文件移动|复制|打包|解包|挂载iso总结">最基础的Ubuntu文件移动|复制|打包|解包|挂载iso总结</a></li><li><a href="http://blog.delai.me/2009/04/crazy-linux.html" rel="bookmark" title="Permanent Link: Linux可以完美运行影音风暴、PPlive、搜狗输入法哦">Linux可以完美运行影音风暴、PPlive、搜狗输入法哦</a></li><li><a href="http://blog.delai.me/2008/02/linux-configuration-service-files.html" rel="bookmark" title="Permanent Link: linux常见配置文件&#038;常见服务对照表">linux常见配置文件&#038;常见服务对照表</a></li><li><a href="http://blog.delai.me/likedislike" rel="bookmark" title="Permanent Link: 喜不喜欢">喜不喜欢</a></li></ul><hr /><strong>[原文链接：<a href="http://blog.delai.me/2008/12/linux-vmsplice.html">http://blog.delai.me/2008/12/linux-vmsplice.html</a>] </strong>]]></content:encoded>
			<wfw:commentRss>http://blog.delai.me/2008/12/linux-vmsplice.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

