Raw File
raw_main.c
/*
 *  Copyright (C) 2017 Authors of Cilium
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>

#include <sys/resource.h>

#include "raw_insn.h"

#include "iproute2/bpf_elf.h"

#define BPF_MAX_FIXUPS	64

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

struct bpf_map_fixup {
	int off;
	enum bpf_map_type type;
	uint32_t size_key;
	uint32_t size_val;
	uint32_t flags;
};

struct bpf_test {
	const char *emits;
	enum bpf_prog_type type;
	struct bpf_insn insns[BPF_MAXINSNS];
	struct bpf_map_fixup fixup_map[BPF_MAX_FIXUPS];
	const char *warn;
};

static struct bpf_test tests[] = {
#include "raw_probe.t"
};

static uint64_t bpf_ptr_to_u64(const void *ptr)
{
	return (uint64_t)(unsigned long)ptr;
}

#ifndef __NR_bpf
# if defined(__i386__)
#  define __NR_bpf 357
# elif defined(__x86_64__)
#  define __NR_bpf 321
# elif defined(__aarch64__)
#  define __NR_bpf 280
# else
#  warning __NR_bpf not defined.
# endif
#endif

static int bpf(int cmd, union bpf_attr *attr, unsigned int size)
{
#ifdef __NR_bpf
	return syscall(__NR_bpf, cmd, attr, size);
#else
	errno = ENOSYS;
	return -1;
#endif
}

int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns,
		  size_t num_insns, const char *license, char *log,
		  size_t size_log)
{
	union bpf_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.prog_type = type;
	attr.insns = bpf_ptr_to_u64(insns);
	attr.insn_cnt = num_insns;
	attr.license = bpf_ptr_to_u64(license);

	if (size_log > 0) {
		attr.log_buf = bpf_ptr_to_u64(log);
		attr.log_size = size_log;
		attr.log_level = 1;
	}

	return bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
}

static int bpf_map_create(enum bpf_map_type type, uint32_t size_key,
			  uint32_t size_value, uint32_t max_elem,
			  uint32_t flags)
{
	union bpf_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.map_type = type;
	attr.key_size = size_key;
	attr.value_size = size_value;
	attr.max_entries = max_elem;
	attr.map_flags = flags;

	return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
}

static int bpf_test_length(const struct bpf_insn *insn)
{
	int len;

	for (len = BPF_MAXINSNS - 1; len > 0; --len)
		if (insn[len].code != 0 || insn[len].imm != 0)
			break;

	return len + 1;
}

/* From iproute2/lib/bpf.c */
static void bpf_map_pin_report(const struct bpf_elf_map *pin,
			       const struct bpf_elf_map *obj)
{
	fprintf(stderr, "Map specification differs from pinned file!\n");

	if (obj->type != pin->type)
		fprintf(stderr, " - Type:         %u (obj) != %u (pin)\n",
			obj->type, pin->type);
	if (obj->size_key != pin->size_key)
		fprintf(stderr, " - Size key:     %u (obj) != %u (pin)\n",
			obj->size_key, pin->size_key);
	if (obj->size_value != pin->size_value)
		fprintf(stderr, " - Size value:   %u (obj) != %u (pin)\n",
			obj->size_value, pin->size_value);
	if (obj->max_elem != pin->max_elem)
		fprintf(stderr, " - Max elems:    %u (obj) != %u (pin)\n",
			obj->max_elem, pin->max_elem);
	if (obj->flags != pin->flags)
		fprintf(stderr, " - Flags:        %#x (obj) != %#x (pin)\n",
			obj->flags, pin->flags);
	if (obj->pinning != pin->pinning)
		fprintf(stderr, " - Pinning:      %#x (obj) != %#x (pin)\n",
			obj->pinning, pin->pinning);

	fprintf(stderr, "\n");
}

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

/* From iproute2/lib/bpf.c */
static int bpf_map_selfcheck_pinned(int fd, const struct bpf_elf_map *map,
				    int length, enum bpf_prog_type type)
{
	char file[PATH_MAX], buff[4096];
	struct bpf_elf_map tmp = {}, zero = {};
	unsigned int val, owner_type = 0;
	FILE *fp;

	snprintf(file, sizeof(file), "/proc/%d/fdinfo/%d", getpid(), fd);

	fp = fopen(file, "r");
	if (!fp) {
		fprintf(stderr, "No procfs support?!\n");
		return -EIO;
	}

	while (fgets(buff, sizeof(buff), fp)) {
		if (sscanf(buff, "map_type:\t%u", &val) == 1)
			tmp.type = val;
		else if (sscanf(buff, "key_size:\t%u", &val) == 1)
			tmp.size_key = val;
		else if (sscanf(buff, "value_size:\t%u", &val) == 1)
			tmp.size_value = val;
		else if (sscanf(buff, "max_entries:\t%u", &val) == 1)
			tmp.max_elem = val;
		else if (sscanf(buff, "map_flags:\t%i", &val) == 1)
			tmp.flags = val;
		else if (sscanf(buff, "owner_prog_type:\t%i", &val) == 1)
			owner_type = val;
	}

	fclose(fp);

	/* The decision to reject this is on kernel side eventually, but
	 * at least give the user a chance to know what's wrong.
	 */
	if (owner_type && owner_type != type)
		fprintf(stderr, "Program array map owner types differ: %u (obj) != %u (pin)\n",
			type, owner_type);

	if (!memcmp(&tmp, map, length)) {
		return 0;
	} else {
		/* If kernel doesn't have eBPF-related fdinfo, we cannot do much,
		 * so just accept it. We know we do have an eBPF fd and in this
		 * case, everything is 0. It is guaranteed that no such map exists
		 * since map type of 0 is unloadable BPF_MAP_TYPE_UNSPEC.
		 */
		if (!memcmp(&tmp, &zero, length))
			return 0;

		bpf_map_pin_report(&tmp, map);
		return -EINVAL;
	}
}

static void bpf_report(const struct bpf_test *test, int success,
		       int debug_mode)
{
	static char bpf_vlog[1U << 16];
	int fd;

	printf("%s#define %s\n\n", success ? "" : "// ", test->emits);

	if (!success || debug_mode) {
		printf("#if 0\n");
		printf("%s %s: ", test->emits, success ?
		       "debug output" : "failed due to load error");

		memset(bpf_vlog, 0, sizeof(bpf_vlog));
		fd = bpf_prog_load(test->type, test->insns,
				   bpf_test_length(test->insns), "GPL",
				   bpf_vlog, sizeof(bpf_vlog));
		printf("%s\n%s", strerror(errno), bpf_vlog);
		printf("#endif\n\n");
		if (fd > 0)
			close(fd);
	}

	if (!success && test->warn)
		fprintf(stderr, "%s: %s\n", test->emits, test->warn);
}

static void bpf_run_test(struct bpf_test *test, int debug_mode)
{
	struct bpf_map_fixup *map = test->fixup_map;
	int fd;

	/* We can use off here as it's never first insns. */
	while (map->off) {
		struct bpf_elf_map elf_map = {
			.type		= map->type,
			.size_key	= map->size_key,
			.size_value	= map->size_val,
			.pinning	= 0,
			.max_elem	= 1,
			.flags		= map->flags,
		};
	  
		fd = bpf_map_create(map->type, map->size_key,
				    map->size_val, 1, map->flags);
		if (fd < 0) {
			if (debug_mode) {
				printf("#if 0\n");
				printf("%s: bpf_map_create(): %s\n",
				       test->emits, strerror(errno));
				printf("#endif\n\n");
			}
			/* We fail in verifier eventually. */
			break;
		}
		
		if (bpf_map_selfcheck_pinned(fd, &elf_map, sizeof elf_map, test->type) != 0)
			break;

		test->insns[map->off].imm = fd;
		map++;
	}

	fd = bpf_prog_load(test->type, test->insns,
			   bpf_test_length(test->insns), "GPL", NULL, 0);
	bpf_report(test, fd > 0, debug_mode);
	if (fd > 0)
		close(fd);
}

int main(int argc, char **argv)
{
	struct rlimit rold, rinf = { RLIM_INFINITY, RLIM_INFINITY };
	int debug_mode = 0;
	int i;

	if (argc > 1 && !strncmp(argv[argc - 1], "debug", sizeof("debug")))
		debug_mode = 1;

	getrlimit(RLIMIT_MEMLOCK, &rold);
	setrlimit(RLIMIT_MEMLOCK, &rinf);

	for (i = 0; i < ARRAY_SIZE(tests); i++)
		bpf_run_test(&tests[i], debug_mode);

	setrlimit(RLIMIT_MEMLOCK, &rold);
	return 0;
}
back to top