欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-07 12:52:42
...

好玩的东西又来咯!


我一向自诩自己是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

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

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

看一段代码是有益的:

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);
	...


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


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