简单分析CVE-2015-1805漏洞
关于CVE-2015-1805漏洞的前世今生大家可以自行百度,总之就是一个牛逼的漏洞虽然发现的早但是玩脱的故事吾爱关于这个漏洞的分析好像没有,我就先现个丑,简单分析一下。
参考文章http://bobao.360.cn/learning/detail/2810.html
漏洞介绍:在linux 内核3.16版本之前的fs/pipe.c当中,由于pipe_read和pipe_write没有考虑到拷贝过程中数据没有同步的一些临界情况,造成了堆数组拷贝越界的问题,因此有可能导致系统crash以及系统权限提升,这种漏洞又称之为” I/O vector array overrun”
出问题的函数为pipe_read
static ssize_t
pipe_read(struct kiocb *iocb, const struct iovec *_iov,
unsigned long nr_segs, loff_t pos)
{
struct file *filp = iocb->ki_filp;
struct inode *inode = filp->f_path.dentry->d_inode;
struct pipe_inode_info *pipe;
int do_wakeup;
ssize_t ret;
struct iovec *iov = (struct iovec *)_iov;
size_t total_len;
total_len = iov_length(iov, nr_segs);//这是拷贝数据的总长度,正是由于这个值没有能够即时更新所导致的漏洞
/* Null read succeeds. */
if (unlikely(total_len == 0))
return 0;
do_wakeup = 0;
ret = 0;
mutex_lock(&inode->i_mutex);
pipe = inode->i_pipe;
for (;;) {
int bufs = pipe->nrbufs;
if (bufs) {
int curbuf = pipe->curbuf;
struct pipe_buffer *buf = pipe->bufs + curbuf;
const struct pipe_buf_operations *ops = buf->ops;
void *addr;
size_t chars = buf->len;
int error, atomic;
if (chars > total_len)
chars = total_len;
error = ops->confirm(pipe, buf);
if (error) {
if (!ret)
ret = error;
break;
}
atomic = !iov_fault_in_pages_write(iov, chars);//在此处判断iov->len是否大于0,且iov->base指向的地址是否可写且处于用户态,之后返回
redo:
addr = ops->map(pipe, buf, atomic);
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);//进行拷贝的关键函数
ops->unmap(pipe, buf, addr);
if (unlikely(error)) {
/*
* Just retry with the slow path if we failed.
*/
if (atomic) {//当atomic为1,且拷贝中途失败时,进入该分支
atomic = 0;
goto redo;
}
if (!ret)
ret = error;
break;
}
ret += chars;
buf->offset += chars;
buf->len -= chars;
if (!buf->len) {
buf->ops = NULL;
ops->release(pipe, buf);
curbuf = (curbuf + 1) & (pipe->buffers - 1);
pipe->curbuf = curbuf;
pipe->nrbufs = --bufs;
do_wakeup = 1;
}
total_len -= chars;//这里更新total_len的值
if (!total_len)
break;/* common path: read succeeded */
}
if (bufs) /* More to do? */
continue;
if (!pipe->writers)
break;
if (!pipe->waiting_writers) {
/* syscall merging: Usually we must not sleep
* if O_NONBLOCK is set, or if we got some data.
* But if a writer sleeps in kernel space, then
* we can wait for that data without violating POSIX.
*/
if (ret)
break;
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
break;
}
}
if (signal_pending(current)) {
if (!ret)
ret = -ERESTARTSYS;
break;
}
if (do_wakeup) {
wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);
kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
}
pipe_wait(pipe);
}
mutex_unlock(&inode->i_mutex);
/* Signal writers asynchronously that there is more room. */
if (do_wakeup) {
wake_up_interruptible_sync_poll(&pipe->wait, POLLOUT | POLLWRNORM);
kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
}
if (ret > 0)
file_accessed(filp);
return ret;
}
可以看到在if(bufs){...}
分支内进行拷贝,而且在分支内当出错后可以以goto的方式跳转。而total_len的更新在分支之外。这就有可能导致一种情况:拷贝中途发生错误,goto跳转,但是由于total_len值并未更新,所以在重新开始拷贝的时候,虽然已经拷贝了大小为n的内容(指针已经向前前进了n),还是会从当前位置向后拷贝total_len大小的数据,导致数组越界,多拷贝了大小为n的内容。
static int
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,
int atomic)
{
unsigned long copy;
while (len > 0) {
while (!iov->iov_len)
iov++;
copy = min_t(unsigned long, len, iov->iov_len);
if (atomic) {
if (__copy_to_user_inatomic(iov->iov_base, from, copy))
return -EFAULT;
} else {
if (copy_to_user(iov->iov_base, from, copy))
return -EFAULT;
}
from += copy;
len -= copy;
iov->iov_base += copy;//指针向前前进已拷贝的大小
iov->iov_len -= copy;//将长度减去已拷贝的大小
}
return 0;
}
落实到具体代码,如果atomic=1,则pipe_iov_copy_to_user -> __copy_to_user_inatomic;如果atomic=0,则pipe_iov_copy_to_user -> copy_to_user 。成功拷贝,就会将当前iov数组内元素的ivo_base和ivo_len进行相应的变化。这个变化在函数内,也就是前文所说的分支内进行,与total_len的更新不同步。
下面来构造一个例子首先构造一个struct iovec iov的数组。如下赋值ivo->ivo_base = 0x00004000ivo->ivo_len = 0x100ivo->ivo_base = 0x00004200ivo->ivo_len = 0x100ivo->ivo_base = 0x00004400ivo->ivo_len = 0x100ivo->ivo_base = 0x00004600ivo->ivo_len = 0x100ivo->ivo_base = 0x00004800ivo->ivo_len = 0x100total_len = 0x500chars假设为0x200先将ivo的ivo_base设置为不可访问。以atomic=1状态进入拷贝
首先ivo拷贝成功,ivo->ivo_len = 0x0然后ivo拷贝失败进入分支if (atomic)goto跳转重新开始拷贝再将ivo->ivo_base的地址设为可访问,每次成功拷贝0x200,两次分别将ivo、ivo和ivo、ivo的数据拷走之后 total_len -= chars这里更新total_len的值 total_len两次0x200,最后会多出0x100,就是因为第一次的错误导致ivo的长度没有减去由于第一次的错误导致此处的total_len不为0再次进行拷贝,此次拷贝的内容超过数组范围,产生越界。只要通过喷堆或者ROP之类的方式,控制越界的内容为我们想要的代码,就可以利用漏洞,进行提权。
支持楼主。 @Ericky 世事繁华皆成空 发表于 2016-5-29 13:57
@Ericky
没代码么。。 世事繁华皆成空 发表于 2016-5-29 13:57
@Ericky
http://bobao.360.cn/learning/detail/2810.html
http://bbs.pediy.com/showthread.php?t=210503 世事繁华皆成空 发表于 2016-5-29 13:57
@Ericky
pediy 硬编码的exp都有啦,这个简单分析就先不加了。楼主可以分析研究个没人研究的,例如CVE-2016-1503等
页:
[1]