/* * Xtables BPF extension * * Written by Willem de Bruijn (willemb@google.com) * Copyright Google, Inc. 2013 * Licensed under the GNU General Public License version 2 (GPLv2) */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef HAVE_LINUX_BPF_H #include #endif #include #include #define BCODE_FILE_MAX_LEN_B 1024 enum { O_BCODE_STDIN = 0, O_OBJ_PINNED = 1, }; static void bpf_help(void) { printf( "bpf match options:\n" "--bytecode : a bpf program as generated by\n" " $(nfbpf_compile RAW '')\n"); } static void bpf_help_v1(void) { printf( "bpf match options:\n" "--bytecode : a bpf program as generated by\n" " $(nfbpf_compile RAW '')\n" "--object-pinned : a path to a pinned BPF object in bpf fs\n"); } static const struct xt_option_entry bpf_opts[] = { {.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING}, XTOPT_TABLEEND, }; static const struct xt_option_entry bpf_opts_v1[] = { {.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING}, {.name = "object-pinned" , .id = O_OBJ_PINNED, .type = XTTYPE_STRING, .flags = XTOPT_PUT, XTOPT_POINTER(struct xt_bpf_info_v1, path)}, XTOPT_TABLEEND, }; static int bpf_obj_get_readonly(const char *filepath) { #if defined HAVE_LINUX_BPF_H && defined __NR_bpf && defined BPF_FS_MAGIC /* union bpf_attr includes this in an anonymous struct, but the * file_flags field and the BPF_F_RDONLY constant are only present * in Linux 4.15+ kernel headers (include/uapi/linux/bpf.h) */ struct { // this part of union bpf_attr is for BPF_OBJ_* commands __aligned_u64 pathname; __u32 bpf_fd; __u32 file_flags; } attr = { .pathname = (__u64)filepath, .file_flags = (1U << 3), // BPF_F_RDONLY }; int fd = syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr)); if (fd >= 0) return fd; /* on any error fallback to default R/W access for pre-4.15-rc1 kernels */ attr.file_flags = 0; return syscall(__NR_bpf, BPF_OBJ_GET, &attr, sizeof(attr)); #else xtables_error(OTHER_PROBLEM, "No bpf header, kernel headers too old?\n"); return -EINVAL; #endif } static void bpf_parse_string(struct sock_filter *pc, __u16 *lenp, __u16 len_max, const char *bpf_program) { const char separator = ','; const char *token; char sp; int i; __u16 len; /* parse head: length. */ if (sscanf(bpf_program, "%hu%c", &len, &sp) != 2 || sp != separator) xtables_error(PARAMETER_PROBLEM, "bpf: error parsing program length"); if (!len) xtables_error(PARAMETER_PROBLEM, "bpf: illegal zero length program"); if (len > len_max) xtables_error(PARAMETER_PROBLEM, "bpf: number of instructions exceeds maximum"); /* parse instructions. */ i = 0; token = bpf_program; while ((token = strchr(token, separator)) && (++token)[0]) { if (i >= len) xtables_error(PARAMETER_PROBLEM, "bpf: real program length exceeds" " the encoded length parameter"); if (sscanf(token, "%hu %hhu %hhu %u,", &pc->code, &pc->jt, &pc->jf, &pc->k) != 4) xtables_error(PARAMETER_PROBLEM, "bpf: error at instr %d", i); i++; pc++; } if (i != len) xtables_error(PARAMETER_PROBLEM, "bpf: parsed program length is less than the" " encoded length parameter"); *lenp = len; } static void bpf_parse_obj_pinned(struct xt_bpf_info_v1 *bi, const char *filepath) { bi->fd = bpf_obj_get_readonly(filepath); if (bi->fd < 0) xtables_error(PARAMETER_PROBLEM, "bpf: failed to get bpf object"); /* Cannot close bi->fd explicitly. Rely on exit */ if (fcntl(bi->fd, F_SETFD, FD_CLOEXEC) == -1) { xtables_error(OTHER_PROBLEM, "Could not set close on exec: %s\n", strerror(errno)); } } static void bpf_parse(struct xt_option_call *cb) { struct xt_bpf_info *bi = (void *) cb->data; xtables_option_parse(cb); switch (cb->entry->id) { case O_BCODE_STDIN: bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem, ARRAY_SIZE(bi->bpf_program), cb->arg); break; default: xtables_error(PARAMETER_PROBLEM, "bpf: unknown option"); } } static void bpf_parse_v1(struct xt_option_call *cb) { struct xt_bpf_info_v1 *bi = (void *) cb->data; xtables_option_parse(cb); switch (cb->entry->id) { case O_BCODE_STDIN: bpf_parse_string(bi->bpf_program, &bi->bpf_program_num_elem, ARRAY_SIZE(bi->bpf_program), cb->arg); bi->mode = XT_BPF_MODE_BYTECODE; break; case O_OBJ_PINNED: bpf_parse_obj_pinned(bi, cb->arg); bi->mode = XT_BPF_MODE_FD_PINNED; break; default: xtables_error(PARAMETER_PROBLEM, "bpf: unknown option"); } } static void bpf_print_code(const struct sock_filter *pc, __u16 len, char tail) { for (; len; len--, pc++) printf("%hu %hhu %hhu %u%c", pc->code, pc->jt, pc->jf, pc->k, len > 1 ? ',' : tail); } static void bpf_save_code(const struct sock_filter *pc, __u16 len) { printf(" --bytecode \"%hu,", len); bpf_print_code(pc, len, '\"'); } static void bpf_save(const void *ip, const struct xt_entry_match *match) { const struct xt_bpf_info *info = (void *) match->data; bpf_save_code(info->bpf_program, info->bpf_program_num_elem); } static void bpf_save_v1(const void *ip, const struct xt_entry_match *match) { const struct xt_bpf_info_v1 *info = (void *) match->data; if (info->mode == XT_BPF_MODE_BYTECODE) bpf_save_code(info->bpf_program, info->bpf_program_num_elem); else if (info->mode == XT_BPF_MODE_FD_PINNED) printf(" --object-pinned %s", info->path); else xtables_error(OTHER_PROBLEM, "unknown bpf mode"); } static void bpf_fcheck(struct xt_fcheck_call *cb) { if (!(cb->xflags & (1 << O_BCODE_STDIN))) xtables_error(PARAMETER_PROBLEM, "bpf: missing --bytecode parameter"); } static void bpf_fcheck_v1(struct xt_fcheck_call *cb) { const unsigned int bit_bcode = 1 << O_BCODE_STDIN; const unsigned int bit_pinned = 1 << O_OBJ_PINNED; unsigned int flags; flags = cb->xflags & (bit_bcode | bit_pinned); if (flags != bit_bcode && flags != bit_pinned) xtables_error(PARAMETER_PROBLEM, "bpf: one of --bytecode or --pinned is required"); } static void bpf_print(const void *ip, const struct xt_entry_match *match, int numeric) { const struct xt_bpf_info *info = (void *) match->data; printf("match bpf "); bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0'); } static void bpf_print_v1(const void *ip, const struct xt_entry_match *match, int numeric) { const struct xt_bpf_info_v1 *info = (void *) match->data; printf("match bpf "); if (info->mode == XT_BPF_MODE_BYTECODE) bpf_print_code(info->bpf_program, info->bpf_program_num_elem, '\0'); else if (info->mode == XT_BPF_MODE_FD_PINNED) printf("pinned %s", info->path); else printf("unknown"); } static struct xtables_match bpf_matches[] = { { .family = NFPROTO_UNSPEC, .name = "bpf", .version = XTABLES_VERSION, .revision = 0, .size = XT_ALIGN(sizeof(struct xt_bpf_info)), .userspacesize = XT_ALIGN(offsetof(struct xt_bpf_info, filter)), .help = bpf_help, .print = bpf_print, .save = bpf_save, .x6_parse = bpf_parse, .x6_fcheck = bpf_fcheck, .x6_options = bpf_opts, }, { .family = NFPROTO_UNSPEC, .name = "bpf", .version = XTABLES_VERSION, .revision = 1, .size = XT_ALIGN(sizeof(struct xt_bpf_info_v1)), .userspacesize = XT_ALIGN(offsetof(struct xt_bpf_info_v1, filter)), .help = bpf_help_v1, .print = bpf_print_v1, .save = bpf_save_v1, .x6_parse = bpf_parse_v1, .x6_fcheck = bpf_fcheck_v1, .x6_options = bpf_opts_v1, }, }; void _init(void) { xtables_register_matches(bpf_matches, ARRAY_SIZE(bpf_matches)); }