基于eBPF的Linux沙盒文件系统SandFS

好玩的东西又来咯!


我一向自诩自己是Netfilter的骨灰级业余玩家,但对于文件系统,我终究连入门都没有曾经。

如果把Netfilter嫁接在VFS上会怎样?

我没有亲自去做,因为我对VFS的API真的不熟。

但是,由于始于Unix的文件系统模型在ACL方面确实是没有太多的建树,以至于除了chroot,isolated mount namespace这种粗暴的控制手段之外,没有更好的精细化ACL方案。

这个时候,我总是能想到iptables…

如果有一个下面的命令该多好:

fstables -A READ --uid 1 --target_dir /home/skinshoe --target_file /etc/rc.local,/etc/inittab -j DENY

显而易见的,为了支持这种类似iptables的配置,需要对vfs的代码进行稍许的增改,在OPEN,READ,WRITE,MMAP,CLOSE这些HOOK点增加类似Netfilter的FF_HOOK调用即可,照猫画虎,就能很容易站在Netfilter的肩膀上实现Fsfilter,进而实现fstables…

这个想法基于以下的事实:

  • 独立的主机有两个途径与外部联系:网络和文件系统。

如果把 “外部” 看作是不可信任的,那么所谓的防火墙就理所当然地应该作用于这些途径上,类似网络协议栈会有那5个经典的HOOK点,文件系统也应该在关键的路径上设置HOOK点。


时间过得很快,好几年过去了,我依然不会编程,但是身外却发生了巨大的变化,如今,基于eBPF的bpfilter大有取代Netfilter制造通用防火墙之势。

作为一种并不仅限于网络协议栈处理的通用方案,eBPF可以作用于每一个子系统,包括文件系统!

在OPEN,READ,WRITE,MMAP,CLOSE等关键的HOOK点挂载eBPF虚拟机执行 编制外的 eBPF Program,必要时再JIT编译成本地代码,这样要比直接调用HOOK Function更加优雅。

直接上eBPF就是了,用通用的方式将eBPF字节码灌入特定的点,而不是类似iptables那样编写netlink和Netfilter通信。

这件事,已经有人做了,这就是sandfs。

我觉得,下面的两篇文档就够了:
https://static.sched.com/hosted_files/osseu19/20/OSSEUSandFS.pdf
https://lwn.net/Articles/803890/

如果你觉得这些比较cheap,那么the code是:
https://github.com/sandfs


你可以用C语言编写一个eBPF程序,然后load进文件系统的eBPF虚拟机,ACL保存在该eBPF虚拟机的map中,用户态进程设置ACL策略,内核遵照执行。

很棒的工作!

嗯,是的,我的意思是,它可以工作,它可以满足我们的精细化访问控制的需要,但是…

  1. 直到Linux kernel 5.3,它依然没有进入mainline。
  2. 我还是希望实现FSFilter。

eBPF正在快速吞噬内核,我相信 BPF_PROG_TYPE_SANDFS 终究会被合入。

说说第二点,我虽然不会编程,但是这部分工作已经由sandfs做了,于是我fork了这个代码,注释掉了eBPF的部门,只留了个框架:
https://github.com/marywangran/sandfs_with_no_ebpf

我这个fork版仅仅是一个sandfs,和eBPF无关,它的目的在于展示OPEN,READ,WRITE,MMAP,CLOSE这些操作是可以被HOOK的。

正常情况下,我们应该按照以下的步骤使用沙盒文件系统:

  1. chroot或者unshare -m提供一个新的mount namespace给用户作为沙盒基准。
  2. 针对第1步的沙盒基准使用eBPF或者我说的fstables设置ACL策略。

关于namespace,unshare这种,参见下面的中文链接:
https://www.ibm.com/developerworks/cn/linux/l-mount-namespaces.html

但是为了演示方便,这里仅仅是打印一下信息。

在这里插入图片描述

看一段代码是有益的:

static ssize_t sandfs_write(struct file *file, const char __user *buf,
			    size_t count, loff_t *ppos)
{
	...
	// 所谓的sandfs本身只是一个附加ACL检查的透明代理!它并不真的进行IO操作!
	lower_file = sandfs_lower_file(file);

	path = file_path(lower_file, (char *)path_buf, PATH_MAX);
	if (!path) {
		pr_err("[%s:%d] Failed to get path\n", __func__, __LINE__);
		err = -EIO;
		goto out;
	}

#ifdef BPF_SANDFS
	args.args[0].size = strlen(path);
	args.args[0].value = (void *)path;
	args.args[1].size = sizeof(loff_t);
	args.args[1].value = (void *)ppos;
	args.args[2].size = sizeof(size_t);
	args.args[2].value = (void *)&count;

	args.num_args = 3;
	args.op = SANDFS_WRITE;
	err = sandfs_request_bpf_op(SANDFS_SB(file_inode(file)->i_sb)->priv, &args);
	if (err < 0)
		goto out;
#endif
	printk("##### write file:%s\n", path);
	...

修改这些代码,在printk处增加你自己的判断Policy,方可实现精细的访问控制:

  • xx进程及其子孙不能访问某个目录…
  • xx用户不能访问某个目录…
  • xx时间,yy文件不能读写…

这些Policy rules会被用户态程序通过Netlink更新。

周三晚上,动卧去深圳办事,周四晚上二等座归,这期间总是要找个场面不那么宏大的东西来玩,就找到了sanfs这个。


浙江温州皮鞋湿,下雨进水不会胖。

发布了1550 篇原创文章 · 获赞 4786 · 访问量 1065万+

猜你喜欢

转载自blog.csdn.net/dog250/article/details/104028841