Check & Init

首先会对prog_flags做一些校验,检查flags是否合法

后续是一些权限的判断,判断是否有权限使用bpf_prog_load这个函数

static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
{
	enum bpf_prog_type type = attr->prog_type;
	struct bpf_prog *prog, *dst_prog = NULL;
	struct btf *attach_btf = NULL;
	int err;
	char license[128];

	if (CHECK_ATTR(BPF_PROG_LOAD)) // helper macro to check that unused fields 'union bpf_attr' are zero
		return -EINVAL;

	if (attr->prog_flags & ~(BPF_F_STRICT_ALIGNMENT | // 严格对齐
				 BPF_F_ANY_ALIGNMENT | // 任意对齐
				 BPF_F_TEST_STATE_FREQ |
				 BPF_F_SLEEPABLE |
				 BPF_F_TEST_RND_HI32 |
				 BPF_F_XDP_HAS_FRAGS |
				 BPF_F_XDP_DEV_BOUND_ONLY)) // only these flags could be used
		return -EINVAL;

	if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) &&
	    (attr->prog_flags & BPF_F_ANY_ALIGNMENT) && // 这个任意对齐是需要对应CAP的
	    !bpf_capable()) // check privilege
		return -EPERM;
		/* Intent here is for unprivileged_bpf_disabled to block BPF program
	 * creation for unprivileged users; other actions depend
	 * on fd availability and access to bpffs, so are dependent on
	 * object creation success. Even with unprivileged BPF disabled,
	 * capability checks are still carried out for these
	 * and other operations.
	 */
	if (sysctl_unprivileged_bpf_disabled && !bpf_capable()) // check privilege 前面的sysctl_unprivileged_bpf_disabled就是开关
		return -EPERM;

这里的sysctl_unprivileged_bpf_disabled是之前所说的开关,决定普通权限用户是否能够载入bpf程序

后续还有对指令数量的check,普通用户最多支持4096指令数的bpf程序,特权用户支持1000000条

	if (attr->insn_cnt == 0 ||
	    attr->insn_cnt > (bpf_capable() ? BPF_COMPLEXITY_LIMIT_INSNS : BPF_MAXINSNS))
		return -E2BIG; // if bpf_capable -> 1000000, else -> 4096

然后是对bpf程序类型的check,普通用户只能使用BPF_PROG_TYPE_SOCKET_FILTERBPF_PROG_TYPE_CGROUP_SKB 这两种bpf程序类型

	if (type != BPF_PROG_TYPE_SOCKET_FILTER &&
	    type != BPF_PROG_TYPE_CGROUP_SKB &&
	    !bpf_capable()) // is unprivileged, could only using these two type
		return -EPERM;

对一些特殊类型的bpf程序还会做更多的权限校验

可以看到除了BPF_PROG_TYPE_CGROUP_SKBBPF_PROG_TYPE_SK_REUSEPORT 以外都需要CAP_NET_ADMIN 或者CAP_SYS_ADMIN

而如果是perfmon 类型的prog,则需要CAP_PERFMONCAP_SYS_ADMIN

这里不同的bpf程序类型可以调用的内核接口/权限等不一样

	if (is_net_admin_prog_type(type) && !capable(CAP_NET_ADMIN) && !capable(CAP_SYS_ADMIN))
		return -EPERM;
	if (is_perfmon_prog_type(type) && !perfmon_capable())
		return -EPERM;
		
	static bool is_net_admin_prog_type(enum bpf_prog_type prog_type)
{
	switch (prog_type) {
	case BPF_PROG_TYPE_SCHED_CLS:
	case BPF_PROG_TYPE_SCHED_ACT:
	case BPF_PROG_TYPE_XDP:
	case BPF_PROG_TYPE_LWT_IN:
	case BPF_PROG_TYPE_LWT_OUT:
	case BPF_PROG_TYPE_LWT_XMIT:
	case BPF_PROG_TYPE_LWT_SEG6LOCAL:
	case BPF_PROG_TYPE_SK_SKB:
	case BPF_PROG_TYPE_SK_MSG:
	case BPF_PROG_TYPE_FLOW_DISSECTOR:
	case BPF_PROG_TYPE_CGROUP_DEVICE:
	case BPF_PROG_TYPE_CGROUP_SOCK:
	case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
	case BPF_PROG_TYPE_CGROUP_SOCKOPT:
	case BPF_PROG_TYPE_CGROUP_SYSCTL:
	case BPF_PROG_TYPE_SOCK_OPS:
	case BPF_PROG_TYPE_EXT: /* extends any prog */
	case BPF_PROG_TYPE_NETFILTER:
		return true;
	case BPF_PROG_TYPE_CGROUP_SKB:
		/* always unpriv */
	case BPF_PROG_TYPE_SK_REUSEPORT:
		/* equivalent to SOCKET_FILTER. need CAP_BPF only */
	default:
		return false;
	}
}

static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)
{
	switch (prog_type) {
	case BPF_PROG_TYPE_KPROBE:
	case BPF_PROG_TYPE_TRACEPOINT:
	case BPF_PROG_TYPE_PERF_EVENT:
	case BPF_PROG_TYPE_RAW_TRACEPOINT:
	case BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE:
	case BPF_PROG_TYPE_TRACING:
	case BPF_PROG_TYPE_LSM:
	case BPF_PROG_TYPE_STRUCT_OPS: /* has access to struct sock */
	case BPF_PROG_TYPE_EXT: /* extends any prog */
		return true;
	default:
		return false;
	}
}

随后会去检查attr中的attach_prog_id 字段,这个字段指定了要附加的内核已有BPF程序id或内核的BTF信息,这主要关系到BPF的两个功能

附加内核已有的BPF程序为了实现程序链式调用(tail call机制),或者让一个程序在另一个程序的基础上扩展额外的处理逻辑。这样可以在不修改原程序的前提下添加新的功能,或者实现对现有行为的拦截和增强

利用BTF可以对类型进行安全检查,以及获取CO-RE支持。附加内核 BTF 信息使 BPF 程序能够获取内核数据结构的元数据,从而在加载时或运行时验证数据访问是否安全。这对 CO-RE(Compile Once – Run Everywhere,编译一次,到处运行)非常重要,因为程序可以利用内核提供的类型信息适应不同内核版本的变化,从而减少因结构偏移不同而产生的错误

当然这些都需要内核BTF支持

	/* attach_prog_fd/attach_btf_obj_fd can specify fd of either bpf_prog
	 * or btf, we need to check which one it is
	 */
	if (attr->attach_prog_fd) { // if attach prog fd
		dst_prog = bpf_prog_get(attr->attach_prog_fd);
		if (IS_ERR(dst_prog)) {
			dst_prog = NULL;
			attach_btf = btf_get_by_fd(attr->attach_btf_obj_fd);
			if (IS_ERR(attach_btf))
				return -EINVAL;
			if (!btf_is_kernel(attach_btf)) {
				/* attaching through specifying bpf_prog's BTF
				 * objects directly might be supported eventually
				 */
				btf_put(attach_btf);
				return -ENOTSUPP;
			}
		}
	} else if (attr->attach_btf_id) { // if attach btf id
		/* fall back to vmlinux BTF, if BTF type ID is specified */
		attach_btf = bpf_get_btf_vmlinux();
		if (IS_ERR(attach_btf))
			return PTR_ERR(attach_btf);
		if (!attach_btf)
			return -EINVAL;
		btf_get(attach_btf);
	}