proc 文件的创建流程及其安全分析

原创 2016年05月31日 23:52:11

1 概述

随着对安全的逐渐深入的学习,Linux系统是无法绕过的坎,接下来就渐渐的学习Linux的各种文件系统安全性,同时结合源码进行跟读学习。

在查看本文时,需要了解如何创建一个proc文件,风险的存在点,仍然是read和write函数,这两个点是否存在风险需要分析,由于syscall的之前的部分前面的文章已经有部分分析,这里不再涉及

2 参考文章

Linux源码:

http://lxr.free-electrons.com/ 

参考博客:

http://blog.chinaunix.net/uid-20543672-id-3235254.html

http://linux.die.net/lkmpg/x861.html

3 profs的变迁史

这里整理了一下,profs的发展过程,可以用下图来表示



从存在VFS开始,profs都是采用的是file_operation作为标准接口的,在linux2.6之后,为了解决缓存问题,引入了seq_operations这个公共接口,

4 read流程分析



4 文件创建流程

4.1第一种实现方式(3.9版本之前)

实现案例代码:http://linux.die.net/lkmpg/x769.html

代码路径:/fs/proc/generic.c

struct proc_dir_entry *create_proc_entry(const char *name, umode_t mode,
struct proc_dir_entry *parent)
{
    struct proc_dir_entry *ent;
    nlink_t nlink;

    if (S_ISDIR(mode)) {
        if ((mode & S_IALLUGO) == 0)
            mode |= S_IRUGO | S_IXUGO;
        nlink = 2;
    } else {
        if ((mode & S_IFMT) == 0)
            mode |= S_IFREG;
        if ((mode & S_IALLUGO) == 0)
            mode |= S_IRUGO;
        nlink = 1;
    }

    ent = __proc_create(&parent, name, mode, nlink);
    if (ent) {
        if (proc_register(parent, ent) < 0) {//这个注册的过程,是将fops赋值的过程
            kfree(ent);
            ent = NULL;
        }
    }
    return ent;
}
返回值是proc_dir_entry,这个定义在proc_fs.h中

 struct proc_dir_entry {
         unsigned int low_ino;
         umode_t mode;
         nlink_t nlink;
         kuid_t uid;
         kgid_t gid;
         loff_t size;
         const struct inode_operations *proc_iops;
         /*
          * NULL ->proc_fops means "PDE is going away RSN" or
          * "PDE is just created". In either case, e.g. ->read_proc won't be
          * called because it's too late or too early, respectively.
          *
          * If you're allocating ->proc_fops dynamically, save a pointer
          * somewhere.
          */
         const struct file_operations *proc_fops;//标准的file_ops
         struct proc_dir_entry *next, *parent, *subdir;
         void *data;
         read_proc_t *read_proc;//这两个就是在上面的链接案例中实现procfs引用的指针
         write_proc_t *write_proc;
         atomic_t count;         /* use count */
         int pde_users;  /* number of callers into module in progress */
         struct completion *pde_unload_completion;
         struct list_head pde_openers;   /* who did ->open, but not ->release */
         spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
         u8 namelen;
         char name[];
 };
4.2 文件创建流程-第二种实现方式(3.10版本及其之后)

proc_dir_entry这个结构体,在3.10之后,发生了较大的变化,定义到了fs/proc/internal.h中

struct proc_dir_entry {
	unsigned int low_ino;
	umode_t mode;
	nlink_t nlink;
	kuid_t uid;
	kgid_t gid;
	loff_t size;
	const struct inode_operations *proc_iops;
	const struct file_operations *proc_fops;//保留
	struct proc_dir_entry *parent;
	struct rb_root subdir;
	struct rb_node subdir_node;
	void *data;
	atomic_t count;		/* use count */
	atomic_t in_use;	/* number of callers into module in progress; */
			/* negative -> it's going away RSN */
	struct completion *pde_unload_completion;
	struct list_head pde_openers;	/* who did ->open, but not ->release */
	spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
	u8 namelen;
	char name[];
};
通过上面的代码可以看到,仅仅保留了file_ops,之前存在的read和write方法已经失效了

创建的procfs的过程可以看看:fs/proc/kmsg.c,源码的实现是很好的学习模仿对象

static const struct file_operations proc_kmsg_operations = {
	.read		= kmsg_read,
	.poll		= kmsg_poll,
	.open		= kmsg_open,
	.release	= kmsg_release,
	.llseek		= generic_file_llseek,
};

static int __init proc_kmsg_init(void)
{
	proc_create("kmsg", S_IRUSR, NULL, &proc_kmsg_operations);
	return 0;
}

4.3文件创建流程-第三种种实现方式(2.6版本及其之后)

参考这篇文章:http://blog.chinaunix.net/uid-20543672-id-3235254.html

在linux2.6之后,引入seq作为处理缓冲区问题的机制,

其中典型的实现代码是:proc/devices.c

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int devinfo_show(struct seq_file *f, void *v)
{
	int i = *(loff_t *) v;

	if (i < CHRDEV_MAJOR_HASH_SIZE) {
		if (i == 0)
			seq_puts(f, "Character devices:\n");
		chrdev_show(f, i);
	}
#ifdef CONFIG_BLOCK
	else {
		i -= CHRDEV_MAJOR_HASH_SIZE;
		if (i == 0)
			seq_puts(f, "\nBlock devices:\n");
		blkdev_show(f, i);
	}
#endif
	return 0;
}

static void *devinfo_start(struct seq_file *f, loff_t *pos)
{
	if (*pos < (BLKDEV_MAJOR_HASH_SIZE + CHRDEV_MAJOR_HASH_SIZE))
		return pos;
	return NULL;
}

static void *devinfo_next(struct seq_file *f, void *v, loff_t *pos)
{
	(*pos)++;
	if (*pos >= (BLKDEV_MAJOR_HASH_SIZE + CHRDEV_MAJOR_HASH_SIZE))
		return NULL;
	return pos;
}

static void devinfo_stop(struct seq_file *f, void *v)
{
	/* Nothing to do */
}

static const struct seq_operations devinfo_ops = {
	.start = devinfo_start,
	.next  = devinfo_next,
	.stop  = devinfo_stop,
	.show  = devinfo_show
};

static int devinfo_open(struct inode *inode, struct file *filp)
{
	return seq_open(filp, &devinfo_ops);
}

static const struct file_operations proc_devinfo_operations = {
	.open		= devinfo_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= seq_release,
};

static int __init proc_devices_init(void)
{
	proc_create("devices", 0, NULL, &proc_devinfo_operations);
	return 0;
}
fs_initcall(proc_devices_init);


在seq中是没有实现write操作的,因此,如果根据需要添加write操作,需要再file_ops中单独添加

static const struct file_operations proc_xxx_operations = {
	.open		= xxx_open,
	.read		= seq_read,
        .write          = xxx_write,
	.llseek		= seq_lseek,
	.release	= seq_release,
};

5, read安全性分析

所谓安全性分析,主要分析的是:read流程应该是内核态数据写入用户态的过程中,如果不进行是否是内核态数据校验是否会出现问题,正常的数据copy,应该使用copytouser,但是很多时候使用scnprintf也没啥风险的原因是什么

通过上面创建文件几种方式,可以看到主要存在两种方式

 fops->seq_read->(seq-next,start,stop,show)
 fops->xxx_read(自定义)
第一种方式中:seq_show(seq_file.c)

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	struct seq_file *m = file->private_data;
	size_t copied = 0;
	loff_t pos;
	size_t n;
	void *p;
	int err = 0;

	mutex_lock(&m->lock);

	/*
	 * seq_file->op->..m_start/m_stop/m_next may do special actions
	 * or optimisations based on the file->f_version, so we want to
	 * pass the file->f_version to those methods.
	 *
	 * seq_file->version is just copy of f_version, and seq_file
	 * methods can treat it simply as file version.
	 * It is copied in first and copied out after all operations.
	 * It is convenient to have it as  part of structure to avoid the
	 * need of passing another argument to all the seq_file methods.
	 */
	m->version = file->f_version;

	/* Don't assume *ppos is where we left it */
	if (unlikely(*ppos != m->read_pos)) {
		while ((err = traverse(m, *ppos)) == -EAGAIN)
			;
		if (err) {
			/* With prejudice... */
			m->read_pos = 0;
			m->version = 0;
			m->index = 0;
			m->count = 0;
			goto Done;
		} else {
			m->read_pos = *ppos;
		}
	}

	/* grab buffer if we didn't have one */
	if (!m->buf) {
		m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
		if (!m->buf)
			goto Enomem;
	}
	/* if not empty - flush it first */
	if (m->count) {
		n = min(m->count, size);
		err = copy_to_user(buf, m->buf + m->from, n);
		if (err)
			goto Efault;
		m->count -= n;
		m->from += n;
		size -= n;
		buf += n;
		copied += n;
		if (!m->count)
			m->index++;
		if (!size)
			goto Done;
	}
	/* we need at least one record in buffer */
	pos = m->index;
	p = m->op->start(m, &pos);
	while (1) {
		err = PTR_ERR(p);
		if (!p || IS_ERR(p))
			break;
		err = m->op->show(m, p);
		if (err < 0)
			break;
		if (unlikely(err))
			m->count = 0;
		if (unlikely(!m->count)) {
			p = m->op->next(m, p, &pos);
			m->index = pos;
			continue;
		}
		if (m->count < m->size)
			goto Fill;
		m->op->stop(m, p);
		kvfree(m->buf);
		m->count = 0;
		m->buf = seq_buf_alloc(m->size <<= 1);
		if (!m->buf)
			goto Enomem;
		m->version = 0;
		pos = m->index;
		p = m->op->start(m, &pos);
	}
	m->op->stop(m, p);
	m->count = 0;
	goto Done;
Fill:
	/* they want more? let's try to get some more */
	while (m->count < size) {
		size_t offs = m->count;
		loff_t next = pos;
		p = m->op->next(m, p, &next);
		if (!p || IS_ERR(p)) {
			err = PTR_ERR(p);
			break;
		}
		err = m->op->show(m, p);
		if (seq_has_overflowed(m) || err) {
			m->count = offs;
			if (likely(err <= 0))
				break;
		}
		pos = next;
	}
	m->op->stop(m, p);
	n = min(m->count, size);
	err = copy_to_user(buf, m->buf, n);
	if (err)
		goto Efault;
	copied += n;
	m->count -= n;
	if (m->count)
		m->from = n;
	else
		pos++;
	m->index = pos;
Done:
	if (!copied)
		copied = err;
	else {
		*ppos += copied;
		m->read_pos += copied;
	}
	file->f_version = m->version;
	mutex_unlock(&m->lock);
	return copied;
Enomem:
	err = -ENOMEM;
	goto Done;
Efault:
	err = -EFAULT;
	goto Done;
}
通过上面的代码可以看到,用户态的数据传入之后,linux内核是使用的copytouser安全函数完成赋值的,procfs文件如果创建的源码中使用的seq_read是没有风险的

针对第二种方式的实现,和dev文件的风险是完全相同的,存在风险

同理对于write操作,由于,本身不存在seq的方式,如果程序在编写过程中,存在使用函数不当,一定会存在内核态任意写的漏洞的(本来应该使用copyfromuser,结果却使用了memcpy等,这种漏洞在驱动开发时候,经常会出现,需要注意)

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

linux下获取按键响应事件

linux下获取按键响应事件   1、问题 通过一个死循环将读取键盘对应的设备文件将触发键盘事件在屏幕上打印出来,按esc退出程序 代码是在unbuntu10.04编译执行通过的 2、inp...

ThreadPoolExecutor使用介绍

一、简介   线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:   ThreadPoolExecutor(int corePoolS...

linux 中/proc 详解

Linux-proc      proc 文件系统   在Linux中有额外的机制可以为内核和内核模块将信息发送给进程-- /proc 文件系统。最初设计的目的是允许更方便的对进程...

linux设备驱动学习笔记--内核调试方法之proc

/proc 文件系统是 GNU/Linux 特有的。它是一个虚拟的文件系统,因此在该目录中的所有文件都不会消耗磁盘空间。通过它能够非常简便地了解系统信息,尤其是其中的大部分文件是人类可阅读的(不过还是...

Linux内核通信之---proc文件系统(详解)

使用 /proc 文件系统来访问 Linux 内核的内容,这个虚拟文件系统在内核空间和用户空间之间打开了一个通信窗口: /proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Li...

ubuntu/linux mint 创建proc文件的三种方法(三)

在做内核驱动开发的时候,可以使用/proc下的文件,获取相应的信息,以便调试。 大多数/proc下的文件是只读的,但为了示例的完整性,都提供了写方法。 方法一:使用create_proc_entry...

Linux下/proc目录简介

1. /proc目录 Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它...

linux下proc文件的读写(部分转载)

in Linux, proc文件系统是一个虚拟文件系统,用户和应用程序可以通过proc文件系统得到当前的一些系统信息,并可以改变一些内核的参数。/proc下的文件是一种特殊文件,不能够像一般文件一...

/proc 文件分析

  • 2014-03-01 20:17
  • 58KB
  • 下载

linux内核——创建自己的/proc文件——processinfo

利用proc_mkdir()创建一个mydir,再利用create_proc_read_entry()函数创建一个processinfo文件。我们从模块里面获取的信息都将写入到processinfo文...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)