19d28fbd30
BPF alignment tests got a conflict because the registers are output as Rn_w instead of just Rn in net-next, and in net a fixup for a testcase prohibits logical operations on pointers before using them. Also, we should attempt to patch BPF call args if JIT always on is enabled. Instead, if we fail to JIT the subprogs we should pass an error back up and fail immediately. Signed-off-by: David S. Miller <davem@davemloft.net>
711 lines
22 KiB
C
711 lines
22 KiB
C
#include <asm/types.h>
|
|
#include <linux/types.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#include <linux/unistd.h>
|
|
#include <linux/filter.h>
|
|
#include <linux/bpf_perf_event.h>
|
|
#include <linux/bpf.h>
|
|
|
|
#include <bpf/bpf.h>
|
|
|
|
#include "../../../include/linux/filter.h"
|
|
|
|
#ifndef ARRAY_SIZE
|
|
# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
|
#endif
|
|
|
|
#define MAX_INSNS 512
|
|
#define MAX_MATCHES 16
|
|
|
|
struct bpf_reg_match {
|
|
unsigned int line;
|
|
const char *match;
|
|
};
|
|
|
|
struct bpf_align_test {
|
|
const char *descr;
|
|
struct bpf_insn insns[MAX_INSNS];
|
|
enum {
|
|
UNDEF,
|
|
ACCEPT,
|
|
REJECT
|
|
} result;
|
|
enum bpf_prog_type prog_type;
|
|
/* Matches must be in order of increasing line */
|
|
struct bpf_reg_match matches[MAX_MATCHES];
|
|
};
|
|
|
|
static struct bpf_align_test tests[] = {
|
|
/* Four tests of known constants. These aren't staggeringly
|
|
* interesting since we track exact values now.
|
|
*/
|
|
{
|
|
.descr = "mov",
|
|
.insns = {
|
|
BPF_MOV64_IMM(BPF_REG_3, 2),
|
|
BPF_MOV64_IMM(BPF_REG_3, 4),
|
|
BPF_MOV64_IMM(BPF_REG_3, 8),
|
|
BPF_MOV64_IMM(BPF_REG_3, 16),
|
|
BPF_MOV64_IMM(BPF_REG_3, 32),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{1, "R1=ctx(id=0,off=0,imm=0)"},
|
|
{1, "R10=fp0"},
|
|
{1, "R3_w=inv2"},
|
|
{2, "R3_w=inv4"},
|
|
{3, "R3_w=inv8"},
|
|
{4, "R3_w=inv16"},
|
|
{5, "R3_w=inv32"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "shift",
|
|
.insns = {
|
|
BPF_MOV64_IMM(BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_3, 4),
|
|
BPF_MOV64_IMM(BPF_REG_4, 32),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{1, "R1=ctx(id=0,off=0,imm=0)"},
|
|
{1, "R10=fp0"},
|
|
{1, "R3_w=inv1"},
|
|
{2, "R3_w=inv2"},
|
|
{3, "R3_w=inv4"},
|
|
{4, "R3_w=inv8"},
|
|
{5, "R3_w=inv16"},
|
|
{6, "R3_w=inv1"},
|
|
{7, "R4_w=inv32"},
|
|
{8, "R4_w=inv16"},
|
|
{9, "R4_w=inv8"},
|
|
{10, "R4_w=inv4"},
|
|
{11, "R4_w=inv2"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "addsub",
|
|
.insns = {
|
|
BPF_MOV64_IMM(BPF_REG_3, 4),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, 4),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, 2),
|
|
BPF_MOV64_IMM(BPF_REG_4, 8),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 2),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{1, "R1=ctx(id=0,off=0,imm=0)"},
|
|
{1, "R10=fp0"},
|
|
{1, "R3_w=inv4"},
|
|
{2, "R3_w=inv8"},
|
|
{3, "R3_w=inv10"},
|
|
{4, "R4_w=inv8"},
|
|
{5, "R4_w=inv12"},
|
|
{6, "R4_w=inv14"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "mul",
|
|
.insns = {
|
|
BPF_MOV64_IMM(BPF_REG_3, 7),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_3, 2),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_3, 4),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{1, "R1=ctx(id=0,off=0,imm=0)"},
|
|
{1, "R10=fp0"},
|
|
{1, "R3_w=inv7"},
|
|
{2, "R3_w=inv7"},
|
|
{3, "R3_w=inv14"},
|
|
{4, "R3_w=inv56"},
|
|
},
|
|
},
|
|
|
|
/* Tests using unknown values */
|
|
#define PREP_PKT_POINTERS \
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, \
|
|
offsetof(struct __sk_buff, data)), \
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_1, \
|
|
offsetof(struct __sk_buff, data_end))
|
|
|
|
#define LOAD_UNKNOWN(DST_REG) \
|
|
PREP_PKT_POINTERS, \
|
|
BPF_MOV64_REG(BPF_REG_0, BPF_REG_2), \
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 8), \
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_0, 1), \
|
|
BPF_EXIT_INSN(), \
|
|
BPF_LDX_MEM(BPF_B, DST_REG, BPF_REG_2, 0)
|
|
|
|
{
|
|
.descr = "unknown shift",
|
|
.insns = {
|
|
LOAD_UNKNOWN(BPF_REG_3),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_3, 1),
|
|
LOAD_UNKNOWN(BPF_REG_4),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_4, 5),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_ALU64_IMM(BPF_RSH, BPF_REG_4, 1),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{7, "R0=pkt(id=0,off=8,r=8,imm=0)"},
|
|
{7, "R3_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{8, "R3_w=inv(id=0,umax_value=510,var_off=(0x0; 0x1fe))"},
|
|
{9, "R3_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
{10, "R3_w=inv(id=0,umax_value=2040,var_off=(0x0; 0x7f8))"},
|
|
{11, "R3_w=inv(id=0,umax_value=4080,var_off=(0x0; 0xff0))"},
|
|
{18, "R3=pkt_end(id=0,off=0,imm=0)"},
|
|
{18, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{19, "R4_w=inv(id=0,umax_value=8160,var_off=(0x0; 0x1fe0))"},
|
|
{20, "R4_w=inv(id=0,umax_value=4080,var_off=(0x0; 0xff0))"},
|
|
{21, "R4_w=inv(id=0,umax_value=2040,var_off=(0x0; 0x7f8))"},
|
|
{22, "R4_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
{23, "R4_w=inv(id=0,umax_value=510,var_off=(0x0; 0x1fe))"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "unknown mul",
|
|
.insns = {
|
|
LOAD_UNKNOWN(BPF_REG_3),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_3),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, 1),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_3),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, 2),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_3),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, 4),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_3),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, 8),
|
|
BPF_ALU64_IMM(BPF_MUL, BPF_REG_4, 2),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{7, "R3_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{8, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{9, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{10, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{11, "R4_w=inv(id=0,umax_value=510,var_off=(0x0; 0x1fe))"},
|
|
{12, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{13, "R4_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
{14, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{15, "R4_w=inv(id=0,umax_value=2040,var_off=(0x0; 0x7f8))"},
|
|
{16, "R4_w=inv(id=0,umax_value=4080,var_off=(0x0; 0xff0))"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "packet const offset",
|
|
.insns = {
|
|
PREP_PKT_POINTERS,
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
|
|
/* Skip over ethernet header. */
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 14),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
|
|
BPF_LDX_MEM(BPF_B, BPF_REG_4, BPF_REG_5, 0),
|
|
BPF_LDX_MEM(BPF_B, BPF_REG_4, BPF_REG_5, 1),
|
|
BPF_LDX_MEM(BPF_B, BPF_REG_4, BPF_REG_5, 2),
|
|
BPF_LDX_MEM(BPF_B, BPF_REG_4, BPF_REG_5, 3),
|
|
BPF_LDX_MEM(BPF_H, BPF_REG_4, BPF_REG_5, 0),
|
|
BPF_LDX_MEM(BPF_H, BPF_REG_4, BPF_REG_5, 2),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_5, 0),
|
|
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
{4, "R5_w=pkt(id=0,off=0,r=0,imm=0)"},
|
|
{5, "R5_w=pkt(id=0,off=14,r=0,imm=0)"},
|
|
{6, "R4_w=pkt(id=0,off=14,r=0,imm=0)"},
|
|
{10, "R2=pkt(id=0,off=0,r=18,imm=0)"},
|
|
{10, "R5=pkt(id=0,off=14,r=18,imm=0)"},
|
|
{10, "R4_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff))"},
|
|
{14, "R4_w=inv(id=0,umax_value=65535,var_off=(0x0; 0xffff))"},
|
|
{15, "R4_w=inv(id=0,umax_value=65535,var_off=(0x0; 0xffff))"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "packet variable offset",
|
|
.insns = {
|
|
LOAD_UNKNOWN(BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_6, 2),
|
|
|
|
/* First, add a constant to the R5 packet pointer,
|
|
* then a variable with a known alignment.
|
|
*/
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 14),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_5, 0),
|
|
|
|
/* Now, test in the other direction. Adding first
|
|
* the variable offset to R5, then the constant.
|
|
*/
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 14),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_5, 0),
|
|
|
|
/* Test multiple accumulations of unknown values
|
|
* into a packet pointer.
|
|
*/
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 14),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 4),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_5, 0),
|
|
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
/* Calculated offset in R6 has unknown value, but known
|
|
* alignment of 4.
|
|
*/
|
|
{8, "R2=pkt(id=0,off=0,r=8,imm=0)"},
|
|
{8, "R6_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Offset is added to packet pointer R5, resulting in
|
|
* known fixed offset, and variable offset from R6.
|
|
*/
|
|
{11, "R5_w=pkt(id=1,off=14,r=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* it's total offset is NET_IP_ALIGN + reg->off (0) +
|
|
* reg->aux_off (14) which is 16. Then the variable
|
|
* offset is considered using reg->aux_off_align which
|
|
* is 4 and meets the load's requirements.
|
|
*/
|
|
{15, "R4=pkt(id=1,off=18,r=18,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
{15, "R5=pkt(id=1,off=14,r=18,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Variable offset is added to R5 packet pointer,
|
|
* resulting in auxiliary alignment of 4.
|
|
*/
|
|
{18, "R5_w=pkt(id=2,off=0,r=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Constant offset is added to R5, resulting in
|
|
* reg->off of 14.
|
|
*/
|
|
{19, "R5_w=pkt(id=2,off=14,r=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* its total fixed offset is NET_IP_ALIGN + reg->off
|
|
* (14) which is 16. Then the variable offset is 4-byte
|
|
* aligned, so the total offset is 4-byte aligned and
|
|
* meets the load's requirements.
|
|
*/
|
|
{23, "R4=pkt(id=2,off=18,r=18,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
{23, "R5=pkt(id=2,off=14,r=18,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Constant offset is added to R5 packet pointer,
|
|
* resulting in reg->off value of 14.
|
|
*/
|
|
{26, "R5_w=pkt(id=0,off=14,r=8"},
|
|
/* Variable offset is added to R5, resulting in a
|
|
* variable offset of (4n).
|
|
*/
|
|
{27, "R5_w=pkt(id=3,off=14,r=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Constant is added to R5 again, setting reg->off to 18. */
|
|
{28, "R5_w=pkt(id=3,off=18,r=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* And once more we add a variable; resulting var_off
|
|
* is still (4n), fixed offset is not changed.
|
|
* Also, we create a new reg->id.
|
|
*/
|
|
{29, "R5_w=pkt(id=4,off=18,r=0,umax_value=2040,var_off=(0x0; 0x7fc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* its total fixed offset is NET_IP_ALIGN + reg->off (18)
|
|
* which is 20. Then the variable offset is (4n), so
|
|
* the total offset is 4-byte aligned and meets the
|
|
* load's requirements.
|
|
*/
|
|
{33, "R4=pkt(id=4,off=22,r=22,umax_value=2040,var_off=(0x0; 0x7fc))"},
|
|
{33, "R5=pkt(id=4,off=18,r=22,umax_value=2040,var_off=(0x0; 0x7fc))"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "packet variable offset 2",
|
|
.insns = {
|
|
/* Create an unknown offset, (4n+2)-aligned */
|
|
LOAD_UNKNOWN(BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_6, 2),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 14),
|
|
/* Add it to the packet pointer */
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
/* Check bounds and perform a read */
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_5, 0),
|
|
/* Make a (4n) offset from the value we just read */
|
|
BPF_ALU64_IMM(BPF_AND, BPF_REG_6, 0xff),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_6, 2),
|
|
/* Add it to the packet pointer */
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
/* Check bounds and perform a read */
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_5, 0),
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
/* Calculated offset in R6 has unknown value, but known
|
|
* alignment of 4.
|
|
*/
|
|
{8, "R2=pkt(id=0,off=0,r=8,imm=0)"},
|
|
{8, "R6_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Adding 14 makes R6 be (4n+2) */
|
|
{9, "R6_w=inv(id=0,umin_value=14,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
/* Packet pointer has (4n+2) offset */
|
|
{11, "R5_w=pkt(id=1,off=0,r=0,umin_value=14,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
{13, "R4=pkt(id=1,off=4,r=0,umin_value=14,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
|
|
* which is 2. Then the variable offset is (4n+2), so
|
|
* the total offset is 4-byte aligned and meets the
|
|
* load's requirements.
|
|
*/
|
|
{15, "R5=pkt(id=1,off=0,r=4,umin_value=14,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
/* Newly read value in R6 was shifted left by 2, so has
|
|
* known alignment of 4.
|
|
*/
|
|
{18, "R6_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Added (4n) to packet pointer's (4n+2) var_off, giving
|
|
* another (4n+2).
|
|
*/
|
|
{19, "R5_w=pkt(id=2,off=0,r=0,umin_value=14,umax_value=2054,var_off=(0x2; 0xffc))"},
|
|
{21, "R4=pkt(id=2,off=4,r=0,umin_value=14,umax_value=2054,var_off=(0x2; 0xffc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
|
|
* which is 2. Then the variable offset is (4n+2), so
|
|
* the total offset is 4-byte aligned and meets the
|
|
* load's requirements.
|
|
*/
|
|
{23, "R5=pkt(id=2,off=0,r=4,umin_value=14,umax_value=2054,var_off=(0x2; 0xffc))"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "dubious pointer arithmetic",
|
|
.insns = {
|
|
PREP_PKT_POINTERS,
|
|
BPF_MOV64_IMM(BPF_REG_0, 0),
|
|
/* ptr & const => unknown & const */
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_IMM(BPF_AND, BPF_REG_5, 0x40),
|
|
/* ptr << const => unknown << const */
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_5, 2),
|
|
/* We have a (4n) value. Let's make a packet offset
|
|
* out of it. First add 14, to make it a (4n+2)
|
|
*/
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 14),
|
|
/* Then make sure it's nonnegative */
|
|
BPF_JMP_IMM(BPF_JSGE, BPF_REG_5, 0, 1),
|
|
BPF_EXIT_INSN(),
|
|
/* Add it to packet pointer */
|
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_2),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_6, BPF_REG_5),
|
|
/* Check bounds and perform a read */
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_6, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.result = REJECT,
|
|
.matches = {
|
|
{4, "R5_w=pkt(id=0,off=0,r=0,imm=0)"},
|
|
/* R5 bitwise operator &= on pointer prohibited */
|
|
}
|
|
},
|
|
{
|
|
.descr = "variable subtraction",
|
|
.insns = {
|
|
/* Create an unknown offset, (4n+2)-aligned */
|
|
LOAD_UNKNOWN(BPF_REG_6),
|
|
BPF_MOV64_REG(BPF_REG_7, BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_6, 2),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 14),
|
|
/* Create another unknown, (4n)-aligned, and subtract
|
|
* it from the first one
|
|
*/
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_7, 2),
|
|
BPF_ALU64_REG(BPF_SUB, BPF_REG_6, BPF_REG_7),
|
|
/* Bounds-check the result */
|
|
BPF_JMP_IMM(BPF_JSGE, BPF_REG_6, 0, 1),
|
|
BPF_EXIT_INSN(),
|
|
/* Add it to the packet pointer */
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_6),
|
|
/* Check bounds and perform a read */
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_5, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
/* Calculated offset in R6 has unknown value, but known
|
|
* alignment of 4.
|
|
*/
|
|
{7, "R2=pkt(id=0,off=0,r=8,imm=0)"},
|
|
{9, "R6_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Adding 14 makes R6 be (4n+2) */
|
|
{10, "R6_w=inv(id=0,umin_value=14,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
/* New unknown value in R7 is (4n) */
|
|
{11, "R7_w=inv(id=0,umax_value=1020,var_off=(0x0; 0x3fc))"},
|
|
/* Subtracting it from R6 blows our unsigned bounds */
|
|
{12, "R6=inv(id=0,smin_value=-1006,smax_value=1034,var_off=(0x2; 0xfffffffffffffffc))"},
|
|
/* Checked s>= 0 */
|
|
{14, "R6=inv(id=0,umin_value=2,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
|
|
* which is 2. Then the variable offset is (4n+2), so
|
|
* the total offset is 4-byte aligned and meets the
|
|
* load's requirements.
|
|
*/
|
|
{20, "R5=pkt(id=1,off=0,r=4,umin_value=2,umax_value=1034,var_off=(0x2; 0x7fc))"},
|
|
},
|
|
},
|
|
{
|
|
.descr = "pointer variable subtraction",
|
|
.insns = {
|
|
/* Create an unknown offset, (4n+2)-aligned and bounded
|
|
* to [14,74]
|
|
*/
|
|
LOAD_UNKNOWN(BPF_REG_6),
|
|
BPF_MOV64_REG(BPF_REG_7, BPF_REG_6),
|
|
BPF_ALU64_IMM(BPF_AND, BPF_REG_6, 0xf),
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_6, 2),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 14),
|
|
/* Subtract it from the packet pointer */
|
|
BPF_MOV64_REG(BPF_REG_5, BPF_REG_2),
|
|
BPF_ALU64_REG(BPF_SUB, BPF_REG_5, BPF_REG_6),
|
|
/* Create another unknown, (4n)-aligned and >= 74.
|
|
* That in fact means >= 76, since 74 % 4 == 2
|
|
*/
|
|
BPF_ALU64_IMM(BPF_LSH, BPF_REG_7, 2),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 76),
|
|
/* Add it to the packet pointer */
|
|
BPF_ALU64_REG(BPF_ADD, BPF_REG_5, BPF_REG_7),
|
|
/* Check bounds and perform a read */
|
|
BPF_MOV64_REG(BPF_REG_4, BPF_REG_5),
|
|
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 4),
|
|
BPF_JMP_REG(BPF_JGE, BPF_REG_3, BPF_REG_4, 1),
|
|
BPF_EXIT_INSN(),
|
|
BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_5, 0),
|
|
BPF_EXIT_INSN(),
|
|
},
|
|
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
|
|
.matches = {
|
|
/* Calculated offset in R6 has unknown value, but known
|
|
* alignment of 4.
|
|
*/
|
|
{7, "R2=pkt(id=0,off=0,r=8,imm=0)"},
|
|
{10, "R6_w=inv(id=0,umax_value=60,var_off=(0x0; 0x3c))"},
|
|
/* Adding 14 makes R6 be (4n+2) */
|
|
{11, "R6_w=inv(id=0,umin_value=14,umax_value=74,var_off=(0x2; 0x7c))"},
|
|
/* Subtracting from packet pointer overflows ubounds */
|
|
{13, "R5_w=pkt(id=1,off=0,r=8,umin_value=18446744073709551542,umax_value=18446744073709551602,var_off=(0xffffffffffffff82; 0x7c))"},
|
|
/* New unknown value in R7 is (4n), >= 76 */
|
|
{15, "R7_w=inv(id=0,umin_value=76,umax_value=1096,var_off=(0x0; 0x7fc))"},
|
|
/* Adding it to packet pointer gives nice bounds again */
|
|
{16, "R5_w=pkt(id=2,off=0,r=0,umin_value=2,umax_value=1082,var_off=(0x2; 0x7fc))"},
|
|
/* At the time the word size load is performed from R5,
|
|
* its total fixed offset is NET_IP_ALIGN + reg->off (0)
|
|
* which is 2. Then the variable offset is (4n+2), so
|
|
* the total offset is 4-byte aligned and meets the
|
|
* load's requirements.
|
|
*/
|
|
{20, "R5=pkt(id=2,off=0,r=4,umin_value=2,umax_value=1082,var_off=(0x2; 0x7fc))"},
|
|
},
|
|
},
|
|
};
|
|
|
|
static int probe_filter_length(const struct bpf_insn *fp)
|
|
{
|
|
int len;
|
|
|
|
for (len = MAX_INSNS - 1; len > 0; --len)
|
|
if (fp[len].code != 0 || fp[len].imm != 0)
|
|
break;
|
|
return len + 1;
|
|
}
|
|
|
|
static char bpf_vlog[32768];
|
|
|
|
static int do_test_single(struct bpf_align_test *test)
|
|
{
|
|
struct bpf_insn *prog = test->insns;
|
|
int prog_type = test->prog_type;
|
|
char bpf_vlog_copy[32768];
|
|
const char *line_ptr;
|
|
int cur_line = -1;
|
|
int prog_len, i;
|
|
int fd_prog;
|
|
int ret;
|
|
|
|
prog_len = probe_filter_length(prog);
|
|
fd_prog = bpf_verify_program(prog_type ? : BPF_PROG_TYPE_SOCKET_FILTER,
|
|
prog, prog_len, 1, "GPL", 0,
|
|
bpf_vlog, sizeof(bpf_vlog), 2);
|
|
if (fd_prog < 0 && test->result != REJECT) {
|
|
printf("Failed to load program.\n");
|
|
printf("%s", bpf_vlog);
|
|
ret = 1;
|
|
} else if (fd_prog >= 0 && test->result == REJECT) {
|
|
printf("Unexpected success to load!\n");
|
|
printf("%s", bpf_vlog);
|
|
ret = 1;
|
|
close(fd_prog);
|
|
} else {
|
|
ret = 0;
|
|
/* We make a local copy so that we can strtok() it */
|
|
strncpy(bpf_vlog_copy, bpf_vlog, sizeof(bpf_vlog_copy));
|
|
line_ptr = strtok(bpf_vlog_copy, "\n");
|
|
for (i = 0; i < MAX_MATCHES; i++) {
|
|
struct bpf_reg_match m = test->matches[i];
|
|
|
|
if (!m.match)
|
|
break;
|
|
while (line_ptr) {
|
|
cur_line = -1;
|
|
sscanf(line_ptr, "%u: ", &cur_line);
|
|
if (cur_line == m.line)
|
|
break;
|
|
line_ptr = strtok(NULL, "\n");
|
|
}
|
|
if (!line_ptr) {
|
|
printf("Failed to find line %u for match: %s\n",
|
|
m.line, m.match);
|
|
ret = 1;
|
|
printf("%s", bpf_vlog);
|
|
break;
|
|
}
|
|
if (!strstr(line_ptr, m.match)) {
|
|
printf("Failed to find match %u: %s\n",
|
|
m.line, m.match);
|
|
ret = 1;
|
|
printf("%s", bpf_vlog);
|
|
break;
|
|
}
|
|
}
|
|
if (fd_prog >= 0)
|
|
close(fd_prog);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int do_test(unsigned int from, unsigned int to)
|
|
{
|
|
int all_pass = 0;
|
|
int all_fail = 0;
|
|
unsigned int i;
|
|
|
|
for (i = from; i < to; i++) {
|
|
struct bpf_align_test *test = &tests[i];
|
|
int fail;
|
|
|
|
printf("Test %3d: %s ... ",
|
|
i, test->descr);
|
|
fail = do_test_single(test);
|
|
if (fail) {
|
|
all_fail++;
|
|
printf("FAIL\n");
|
|
} else {
|
|
all_pass++;
|
|
printf("PASS\n");
|
|
}
|
|
}
|
|
printf("Results: %d pass %d fail\n",
|
|
all_pass, all_fail);
|
|
return all_fail ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
unsigned int from = 0, to = ARRAY_SIZE(tests);
|
|
struct rlimit rinf = { RLIM_INFINITY, RLIM_INFINITY };
|
|
|
|
setrlimit(RLIMIT_MEMLOCK, &rinf);
|
|
|
|
if (argc == 3) {
|
|
unsigned int l = atoi(argv[argc - 2]);
|
|
unsigned int u = atoi(argv[argc - 1]);
|
|
|
|
if (l < to && u < to) {
|
|
from = l;
|
|
to = u + 1;
|
|
}
|
|
} else if (argc == 2) {
|
|
unsigned int t = atoi(argv[argc - 1]);
|
|
|
|
if (t < to) {
|
|
from = t;
|
|
to = t + 1;
|
|
}
|
|
}
|
|
return do_test(from, to);
|
|
}
|