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下proc文件的读写(部分转载)

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

proc源码解析(三)--proc初始化

在使用proc之前,我们必须首先初始化并挂载proc,并在内核内存中创建数据结构来描述文件系统。但是,不同的体系结构拥有不同的proc内容,所以,在初始化阶段并不完全创建子目录的内容,有些文件要等到系...

linux proc目录和常用操作

------------------------------------------------/proc-----------------------------------------------...

proc_create的使用方法

proc_create的使用方法 proc文件系统是个有用的东东。 创建一个proc虚拟文件,应用层通过读写该文件,即可实现与内核的交互。 proc虚拟文件是如何创建的呢? 先看看比较简单的,...
  • njuitjf
  • njuitjf
  • 2013年11月25日 10:57
  • 31202

ubuntu系统samba创建文件夹无权限

20170727 update: OS:CentOS 在centos服务器上配置了samba,security设置为user,而且也通过smbpasswd配置了密码,但是在windows上访问服务...

数据包的分类和调度-Linux TC的另一种解释

如果从分层递归的角度理解Linux的TC框架,很容易将队列分为有类队列和无类队列,这个角度上看,有类队列和无类队列的地位是平等的。但实际上,它们之间是有层次关系的。之所以将其分为有类队列和无类队列,完...
  • dog250
  • dog250
  • 2014年11月01日 16:47
  • 4171

/proc 文件分析

  • 2014年03月01日 20:17
  • 59KB
  • 下载

linux proc文件创建指南

linux proc文件创建

在linux内核中创建一个/proc/下的文件结点实例http://www.lslnet.com/linux/dosc1/34/linux-259508.htm

这个例子也是来自网上。现在能够编译,加载。 加载后通过echo将一段字符写入/proc/test。然后用cat看/proc/test。结果,在屏幕上不停的重复显示原来写入的字符串。不知道是为什么、?...

创建自己的/proc文件——processinfo

Cited from http://www.lupaworld.com/home-space-uid-401174-do-blog-id-149701.html 前面写过一个模块,创建内核进程之ke...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:proc 文件的创建流程及其安全分析
举报原因:
原因补充:

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