实现一个Linux文件系统ACL防火墙-FS_HOOK简介

近日,我将一个基于eBPF实现的SandFS中的eBPF部分摘除了,形成了一个单纯的SandFS框架:
https://github.com/marywangran/sandfs_with_no_ebpf

纳尼?eBPF大红大紫之时,我竟然反其道而行之,这不犯了形而上学的错误了吗?

没错,事实上我摘除它的原因是因为这个原生的eBPF-based-SandFS在我的两个开发环境中均没有编译通过:

  • CentOS 7-3.10内核:llvm 8.0.1未安装成功,因为glibc版本太低。
  • Ubuntu 19.10-5.3内核,SandFS-Kernel编译失败,没时间折腾。

然而我又非常喜欢SandFS这个框架:

  • 没有修改VFS本身的代码,而是通过实现一个wrap fs来嫁接eBPF字节码。
    我想让它运行起来,我在去深圳的火车上就想让它运行起来。

同时,我又特别特别喜欢Netfilter,于是我就想让为SandFS嫁接一个类似的Filter,来完成我无能为力的eBPF Program的功能,就这样。


完成代码后,暂时没有时间写类似iptables的用户态程序用来灌入策略,那就写了几个内核态的测试case,完成下面的小功能:

  1. 用户zhaoya的uid为1000,以zhaoya登录一次不能读取超过10字节的文件内容,一次只能读取最多10字节。
  2. 不能往任何文件里写skinshoe这个单词。
  3. 不能读取根目录下名称为key的文件。

效果如下:
在这里插入图片描述
其中testread的代码如下:

// gcc testread.c -o testread
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	int fd;
	int len, ret;
	char buf[1024] = {0};

	len = atoi(argv[2]);

	fd = open(argv[1], O_RDONLY);

	ret = read(fd, buf, len);
	perror("read");
	printf("%s\n", buf);

	return 0;
}

我的三个测试case代码如下:

#define TEST
#ifdef TEST

static int test_read_func(unsigned int hook, struct sandfs_args *args, void *priv, int *handled)
{
	int ret = FS_ACCEPT;
	struct sandfs_args *largs = args;
	kuid_t id;
	size_t count;
	struct cred *cred;

	cred = (struct cred *)largs->args[SANDFS_IDX_CRED].value;
	id = cred->uid;
	count = *((size_t *)largs->args[SANDFS_IDX_COUNT].value);
	if (id.val == 1000 && count > 10) {
		printk("#### test read deny request\n");
		ret = FS_DROP;
	}
	*handled = 1;
	return ret;
}

static int test_write_func(unsigned int hook, struct sandfs_args *args, void *priv, int *handled)
{
	int ret = FS_ACCEPT;
	struct sandfs_args *largs = args;
	size_t count;
	char *buf;

	count = largs->args[SANDFS_IDX_BUF].size;
	buf = (char *)largs->args[SANDFS_IDX_BUF].value;
	if (strnstr(buf, "skinshoe", count)) {
		printk("#### test write deny request\n");
		ret = FS_DROP;
	}
	*handled = 1;
	return ret;
}

static int test_lookup_func(unsigned int hook, struct sandfs_args *args, void *priv, int *handled)
{
	int ret = FS_ACCEPT;
	struct sandfs_args *largs = args;
	size_t len;
	char *path;

	len = largs->args[SANDFS_IDX_PATH].size;
	path = (char *)largs->args[SANDFS_IDX_PATH].value;
	if (!strncmp(path, "/key", len)) {
		printk("#### test lookup deny request\n");
		ret = FS_DROP;
	}
	*handled = 1;
	return ret;
}

static struct vfs_rule test_read_rule = {
	.name = "test read",
	.func = test_read_func,
	.hooknum = SANDFS_READ,
};

static struct vfs_rule test_write_rule = {
	.name = "test write",
	.func = test_write_func,
	.hooknum = SANDFS_WRITE,
};

static struct vfs_rule test_lookup_rule = {
	.name = "test lookup",
	.func = test_lookup_func,
	.hooknum = SANDFS_LOOKUP,
};
#endif

完成的代码在sandfs_with_no_ebpf的devel分支,等待实现了用户态配置程序之后再进行一次merge:
https://github.com/marywangran/sandfs_with_no_ebpf

当然,很多人会觉得这个东西没有意义,没有技术含量,太简单,这些都无所谓,它的意义是特定于我自己的:

  • 可以精细化特定mount namespace中的文件系统访问控制。
  • 过年回家前治愈无聊缓解乏力。
  • 有时间还能写写用户态配置程序,学学怎么编程。

对于不会编程的我来讲,这就是小惊喜,小收获了。

PS:我依然喜欢用####来做打印前缀,因为它一目了然,容易在日志文件中快速匹配。可能是我不懂更正规的做法吧,希望有人能教我。


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

发布了1560 篇原创文章 · 获赞 4877 · 访问量 1091万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览