selftest/bpf: Verifier tests for var-off access
authorAndrei Matei <andreimatei1@gmail.com>
Sun, 7 Feb 2021 01:10:26 +0000 (20:10 -0500)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 10 Feb 2021 18:44:19 +0000 (10:44 -0800)
Add tests for the new functionality - reading and writing to the stack
through a variable-offset pointer.

Signed-off-by: Andrei Matei <andreimatei1@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20210207011027.676572-4-andreimatei1@gmail.com
tools/testing/selftests/bpf/verifier/var_off.c

index 49b78a1..eab1f7f 100644 (file)
         * we don't know which
         */
        BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_10),
-       /* dereference it */
+       /* dereference it for a stack read */
+       BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_2, 0),
+       BPF_MOV64_IMM(BPF_REG_0, 0),
+       BPF_EXIT_INSN(),
+       },
+       .result = ACCEPT,
+       .result_unpriv = REJECT,
+       .errstr_unpriv = "R2 variable stack access prohibited for !root",
+       .prog_type = BPF_PROG_TYPE_CGROUP_SKB,
+},
+{
+       "variable-offset stack read, uninitialized",
+       .insns = {
+       /* Get an unknown value */
+       BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, 0),
+       /* Make it small and 4-byte aligned */
+       BPF_ALU64_IMM(BPF_AND, BPF_REG_2, 4),
+       BPF_ALU64_IMM(BPF_SUB, BPF_REG_2, 8),
+       /* add it to fp.  We now have either fp-4 or fp-8, but
+        * we don't know which
+        */
+       BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_10),
+       /* dereference it for a stack read */
        BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_2, 0),
+       BPF_MOV64_IMM(BPF_REG_0, 0),
        BPF_EXIT_INSN(),
        },
-       .errstr = "variable stack access var_off=(0xfffffffffffffff8; 0x4)",
        .result = REJECT,
+       .errstr = "invalid variable-offset read from stack R2",
        .prog_type = BPF_PROG_TYPE_LWT_IN,
 },
 {
+       "variable-offset stack write, priv vs unpriv",
+       .insns = {
+       /* Get an unknown value */
+       BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, 0),
+       /* Make it small and 8-byte aligned */
+       BPF_ALU64_IMM(BPF_AND, BPF_REG_2, 8),
+       BPF_ALU64_IMM(BPF_SUB, BPF_REG_2, 16),
+       /* Add it to fp.  We now have either fp-8 or fp-16, but
+        * we don't know which
+        */
+       BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_10),
+       /* Dereference it for a stack write */
+       BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0),
+       /* Now read from the address we just wrote. This shows
+        * that, after a variable-offset write, a priviledged
+        * program can read the slots that were in the range of
+        * that write (even if the verifier doesn't actually know
+        * if the slot being read was really written to or not.
+        */
+       BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_2, 0),
+       BPF_MOV64_IMM(BPF_REG_0, 0),
+       BPF_EXIT_INSN(),
+       },
+       /* Variable stack access is rejected for unprivileged.
+        */
+       .errstr_unpriv = "R2 variable stack access prohibited for !root",
+       .result_unpriv = REJECT,
+       .result = ACCEPT,
+},
+{
+       "variable-offset stack write clobbers spilled regs",
+       .insns = {
+       /* Dummy instruction; needed because we need to patch the next one
+        * and we can't patch the first instruction.
+        */
+       BPF_MOV64_IMM(BPF_REG_6, 0),
+       /* Make R0 a map ptr */
+       BPF_LD_MAP_FD(BPF_REG_0, 0),
+       /* Get an unknown value */
+       BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, 0),
+       /* Make it small and 8-byte aligned */
+       BPF_ALU64_IMM(BPF_AND, BPF_REG_2, 8),
+       BPF_ALU64_IMM(BPF_SUB, BPF_REG_2, 16),
+       /* Add it to fp. We now have either fp-8 or fp-16, but
+        * we don't know which.
+        */
+       BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_10),
+       /* Spill R0(map ptr) into stack */
+       BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_0, -8),
+       /* Dereference the unknown value for a stack write */
+       BPF_ST_MEM(BPF_DW, BPF_REG_2, 0, 0),
+       /* Fill the register back into R2 */
+       BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_10, -8),
+       /* Try to dereference R2 for a memory load */
+       BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_2, 8),
+       BPF_EXIT_INSN(),
+       },
+       .fixup_map_hash_8b = { 1 },
+       /* The unpriviledged case is not too interesting; variable
+        * stack access is rejected.
+        */
+       .errstr_unpriv = "R2 variable stack access prohibited for !root",
+       .result_unpriv = REJECT,
+       /* In the priviledged case, dereferencing a spilled-and-then-filled
+        * register is rejected because the previous variable offset stack
+        * write might have overwritten the spilled pointer (i.e. we lose track
+        * of the spilled register when we analyze the write).
+        */
+       .errstr = "R2 invalid mem access 'inv'",
+       .result = REJECT,
+},
+{
        "indirect variable-offset stack access, unbounded",
        .insns = {
        BPF_MOV64_IMM(BPF_REG_2, 6),