463 lines · 11.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */#include <linux/bpf.h>#include <linux/bpf_verifier.h>#include <linux/filter.h>#include <linux/btf.h> #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args) static int check_abnormal_return(struct bpf_verifier_env *env){	int i; 	for (i = 1; i < env->subprog_cnt; i++) {		if (env->subprog_info[i].has_ld_abs) {			verbose(env, "LD_ABS is not allowed in subprogs without BTF\n");			return -EINVAL;		}		if (env->subprog_info[i].has_tail_call) {			verbose(env, "tail_call is not allowed in subprogs without BTF\n");			return -EINVAL;		}	}	return 0;} /* The minimum supported BTF func info size */#define MIN_BPF_FUNCINFO_SIZE	8#define MAX_FUNCINFO_REC_SIZE	252 static int prepare_btf_func(struct bpf_verifier_env *env,			    const union bpf_attr *attr,			    bpfptr_t uattr){	u32 krec_size = sizeof(struct bpf_func_info);	const struct btf_type *type, *func_proto;	u32 i, nfuncs, urec_size, min_size;	struct bpf_func_info *krecord;	struct bpf_prog *prog;	const struct btf *btf;	u32 prev_offset = 0;	bpfptr_t urecord;	int ret = -ENOMEM; 	nfuncs = attr->func_info_cnt;	if (!nfuncs) {		if (check_abnormal_return(env))			return -EINVAL;		return 0;	} 	urec_size = attr->func_info_rec_size;	if (urec_size < MIN_BPF_FUNCINFO_SIZE ||	    urec_size > MAX_FUNCINFO_REC_SIZE ||	    urec_size % sizeof(u32)) {		verbose(env, "invalid func info rec size %u\n", urec_size);		return -EINVAL;	} 	prog = env->prog;	btf = prog->aux->btf; 	urecord = make_bpfptr(attr->func_info, uattr.is_kernel);	min_size = min_t(u32, krec_size, urec_size); 	krecord = kvcalloc(nfuncs, krec_size, GFP_KERNEL_ACCOUNT | __GFP_NOWARN);	if (!krecord)		return -ENOMEM; 	for (i = 0; i < nfuncs; i++) {		ret = bpf_check_uarg_tail_zero(urecord, krec_size, urec_size);		if (ret) {			if (ret == -E2BIG) {				verbose(env, "nonzero tailing record in func info");				/* set the size kernel expects so loader can zero				 * out the rest of the record.				 */				if (copy_to_bpfptr_offset(uattr,							  offsetof(union bpf_attr, func_info_rec_size),							  &min_size, sizeof(min_size)))					ret = -EFAULT;			}			goto err_free;		} 		if (copy_from_bpfptr(&krecord[i], urecord, min_size)) {			ret = -EFAULT;			goto err_free;		} 		/* check insn_off */		ret = -EINVAL;		if (i == 0) {			if (krecord[i].insn_off) {				verbose(env,					"nonzero insn_off %u for the first func info record",					krecord[i].insn_off);				goto err_free;			}		} else if (krecord[i].insn_off <= prev_offset) {			verbose(env,				"same or smaller insn offset (%u) than previous func info record (%u)",				krecord[i].insn_off, prev_offset);			goto err_free;		} 		/* check type_id */		type = btf_type_by_id(btf, krecord[i].type_id);		if (!type || !btf_type_is_func(type)) {			verbose(env, "invalid type id %d in func info",				krecord[i].type_id);			goto err_free;		} 		func_proto = btf_type_by_id(btf, type->type);		if (unlikely(!func_proto || !btf_type_is_func_proto(func_proto)))			/* btf_func_check() already verified it during BTF load */			goto err_free; 		prev_offset = krecord[i].insn_off;		bpfptr_add(&urecord, urec_size);	} 	prog->aux->func_info = krecord;	prog->aux->func_info_cnt = nfuncs;	return 0; err_free:	kvfree(krecord);	return ret;} static int check_btf_func(struct bpf_verifier_env *env,			  const union bpf_attr *attr,			  bpfptr_t uattr){	const struct btf_type *type, *func_proto, *ret_type;	u32 i, nfuncs, urec_size;	struct bpf_func_info *krecord;	struct bpf_func_info_aux *info_aux = NULL;	struct bpf_prog *prog;	const struct btf *btf;	bpfptr_t urecord;	bool scalar_return;	int ret = -ENOMEM; 	nfuncs = attr->func_info_cnt;	if (!nfuncs) {		if (check_abnormal_return(env))			return -EINVAL;		return 0;	}	if (nfuncs != env->subprog_cnt) {		verbose(env, "number of funcs in func_info doesn't match number of subprogs\n");		return -EINVAL;	} 	urec_size = attr->func_info_rec_size; 	prog = env->prog;	btf = prog->aux->btf; 	urecord = make_bpfptr(attr->func_info, uattr.is_kernel); 	krecord = prog->aux->func_info;	info_aux = kzalloc_objs(*info_aux, nfuncs,				GFP_KERNEL_ACCOUNT | __GFP_NOWARN);	if (!info_aux)		return -ENOMEM; 	for (i = 0; i < nfuncs; i++) {		/* check insn_off */		ret = -EINVAL; 		if (env->subprog_info[i].start != krecord[i].insn_off) {			verbose(env, "func_info BTF section doesn't match subprog layout in BPF program\n");			goto err_free;		} 		/* Already checked type_id */		type = btf_type_by_id(btf, krecord[i].type_id);		info_aux[i].linkage = BTF_INFO_VLEN(type->info);		/* Already checked func_proto */		func_proto = btf_type_by_id(btf, type->type); 		ret_type = btf_type_skip_modifiers(btf, func_proto->type, NULL);		scalar_return =			btf_type_is_small_int(ret_type) || btf_is_any_enum(ret_type);		if (i && !scalar_return && env->subprog_info[i].has_ld_abs) {			verbose(env, "LD_ABS is only allowed in functions that return 'int'.\n");			goto err_free;		}		if (i && !scalar_return && env->subprog_info[i].has_tail_call) {			verbose(env, "tail_call is only allowed in functions that return 'int'.\n");			goto err_free;		} 		env->subprog_info[i].name = btf_name_by_offset(btf, type->name_off);		bpfptr_add(&urecord, urec_size);	} 	prog->aux->func_info_aux = info_aux;	return 0; err_free:	kfree(info_aux);	return ret;} #define MIN_BPF_LINEINFO_SIZE	offsetofend(struct bpf_line_info, line_col)#define MAX_LINEINFO_REC_SIZE	MAX_FUNCINFO_REC_SIZE static int check_btf_line(struct bpf_verifier_env *env,			  const union bpf_attr *attr,			  bpfptr_t uattr){	u32 i, s, nr_linfo, ncopy, expected_size, rec_size, prev_offset = 0;	struct bpf_subprog_info *sub;	struct bpf_line_info *linfo;	struct bpf_prog *prog;	const struct btf *btf;	bpfptr_t ulinfo;	int err; 	nr_linfo = attr->line_info_cnt;	if (!nr_linfo)		return 0;	if (nr_linfo > INT_MAX / sizeof(struct bpf_line_info))		return -EINVAL; 	rec_size = attr->line_info_rec_size;	if (rec_size < MIN_BPF_LINEINFO_SIZE ||	    rec_size > MAX_LINEINFO_REC_SIZE ||	    rec_size & (sizeof(u32) - 1))		return -EINVAL; 	/* Need to zero it in case the userspace may	 * pass in a smaller bpf_line_info object.	 */	linfo = kvzalloc_objs(struct bpf_line_info, nr_linfo,			      GFP_KERNEL_ACCOUNT | __GFP_NOWARN);	if (!linfo)		return -ENOMEM; 	prog = env->prog;	btf = prog->aux->btf; 	s = 0;	sub = env->subprog_info;	ulinfo = make_bpfptr(attr->line_info, uattr.is_kernel);	expected_size = sizeof(struct bpf_line_info);	ncopy = min_t(u32, expected_size, rec_size);	for (i = 0; i < nr_linfo; i++) {		err = bpf_check_uarg_tail_zero(ulinfo, expected_size, rec_size);		if (err) {			if (err == -E2BIG) {				verbose(env, "nonzero tailing record in line_info");				if (copy_to_bpfptr_offset(uattr,							  offsetof(union bpf_attr, line_info_rec_size),							  &expected_size, sizeof(expected_size)))					err = -EFAULT;			}			goto err_free;		} 		if (copy_from_bpfptr(&linfo[i], ulinfo, ncopy)) {			err = -EFAULT;			goto err_free;		} 		/*		 * Check insn_off to ensure		 * 1) strictly increasing AND		 * 2) bounded by prog->len		 *		 * The linfo[0].insn_off == 0 check logically falls into		 * the later "missing bpf_line_info for func..." case		 * because the first linfo[0].insn_off must be the		 * first sub also and the first sub must have		 * subprog_info[0].start == 0.		 */		if ((i && linfo[i].insn_off <= prev_offset) ||		    linfo[i].insn_off >= prog->len) {			verbose(env, "Invalid line_info[%u].insn_off:%u (prev_offset:%u prog->len:%u)\n",				i, linfo[i].insn_off, prev_offset,				prog->len);			err = -EINVAL;			goto err_free;		} 		if (!prog->insnsi[linfo[i].insn_off].code) {			verbose(env,				"Invalid insn code at line_info[%u].insn_off\n",				i);			err = -EINVAL;			goto err_free;		} 		if (!btf_name_by_offset(btf, linfo[i].line_off) ||		    !btf_name_by_offset(btf, linfo[i].file_name_off)) {			verbose(env, "Invalid line_info[%u].line_off or .file_name_off\n", i);			err = -EINVAL;			goto err_free;		} 		if (s != env->subprog_cnt) {			if (linfo[i].insn_off == sub[s].start) {				sub[s].linfo_idx = i;				s++;			} else if (sub[s].start < linfo[i].insn_off) {				verbose(env, "missing bpf_line_info for func#%u\n", s);				err = -EINVAL;				goto err_free;			}		} 		prev_offset = linfo[i].insn_off;		bpfptr_add(&ulinfo, rec_size);	} 	if (s != env->subprog_cnt) {		verbose(env, "missing bpf_line_info for %u funcs starting from func#%u\n",			env->subprog_cnt - s, s);		err = -EINVAL;		goto err_free;	} 	prog->aux->linfo = linfo;	prog->aux->nr_linfo = nr_linfo; 	return 0; err_free:	kvfree(linfo);	return err;} #define MIN_CORE_RELO_SIZE	sizeof(struct bpf_core_relo)#define MAX_CORE_RELO_SIZE	MAX_FUNCINFO_REC_SIZE static int check_core_relo(struct bpf_verifier_env *env,			   const union bpf_attr *attr,			   bpfptr_t uattr){	u32 i, nr_core_relo, ncopy, expected_size, rec_size;	struct bpf_core_relo core_relo = {};	struct bpf_prog *prog = env->prog;	const struct btf *btf = prog->aux->btf;	struct bpf_core_ctx ctx = {		.log = &env->log,		.btf = btf,	};	bpfptr_t u_core_relo;	int err; 	nr_core_relo = attr->core_relo_cnt;	if (!nr_core_relo)		return 0;	if (nr_core_relo > INT_MAX / sizeof(struct bpf_core_relo))		return -EINVAL; 	rec_size = attr->core_relo_rec_size;	if (rec_size < MIN_CORE_RELO_SIZE ||	    rec_size > MAX_CORE_RELO_SIZE ||	    rec_size % sizeof(u32))		return -EINVAL; 	u_core_relo = make_bpfptr(attr->core_relos, uattr.is_kernel);	expected_size = sizeof(struct bpf_core_relo);	ncopy = min_t(u32, expected_size, rec_size); 	/* Unlike func_info and line_info, copy and apply each CO-RE	 * relocation record one at a time.	 */	for (i = 0; i < nr_core_relo; i++) {		/* future proofing when sizeof(bpf_core_relo) changes */		err = bpf_check_uarg_tail_zero(u_core_relo, expected_size, rec_size);		if (err) {			if (err == -E2BIG) {				verbose(env, "nonzero tailing record in core_relo");				if (copy_to_bpfptr_offset(uattr,							  offsetof(union bpf_attr, core_relo_rec_size),							  &expected_size, sizeof(expected_size)))					err = -EFAULT;			}			break;		} 		if (copy_from_bpfptr(&core_relo, u_core_relo, ncopy)) {			err = -EFAULT;			break;		} 		if (core_relo.insn_off % 8 || core_relo.insn_off / 8 >= prog->len) {			verbose(env, "Invalid core_relo[%u].insn_off:%u prog->len:%u\n",				i, core_relo.insn_off, prog->len);			err = -EINVAL;			break;		} 		err = bpf_core_apply(&ctx, &core_relo, i,				     &prog->insnsi[core_relo.insn_off / 8]);		if (err)			break;		bpfptr_add(&u_core_relo, rec_size);	}	return err;} int bpf_prepare_btf_info(struct bpf_verifier_env *env,			 const union bpf_attr *attr,			 bpfptr_t uattr){	struct btf *btf;	int err; 	if (!attr->func_info_cnt && !attr->line_info_cnt) {		if (check_abnormal_return(env))			return -EINVAL;		return 0;	} 	btf = btf_get_by_fd(attr->prog_btf_fd);	if (IS_ERR(btf))		return PTR_ERR(btf);	if (btf_is_kernel(btf)) {		btf_put(btf);		return -EACCES;	}	env->prog->aux->btf = btf; 	err = prepare_btf_func(env, attr, uattr);	if (err)		return err;	return 0;} int bpf_check_btf_info(struct bpf_verifier_env *env,		       const union bpf_attr *attr,		       bpfptr_t uattr){	int err; 	if (!attr->func_info_cnt && !attr->line_info_cnt) {		if (check_abnormal_return(env))			return -EINVAL;		return 0;	} 	err = check_btf_func(env, attr, uattr);	if (err)		return err; 	err = check_btf_line(env, attr, uattr);	if (err)		return err; 	err = check_core_relo(env, attr, uattr);	if (err)		return err; 	return 0;}