I'm far from sure that my adjust_reg_min_max_vals() code remains correct
 in the face of maybe-signed-maybe-not bounds, even with the checks I've
 added in.  So this probably has security leaks in it; but it should
 suffice for measuring the effect on pruning.

The treat_as_signed part is loosely based on a patch by Josef Bacik 
<jba...@fb.com>.

Build-tested only.  Applies on top of patches 1-3.

Signed-off-by: Edward Cree <ec...@solarflare.com>
---
 include/linux/bpf_verifier.h |   5 +-
 kernel/bpf/verifier.c        | 179 ++++++++++++++++++++++++++-----------------
 2 files changed, 111 insertions(+), 73 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index ca7e2ce..ad02a9d 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -51,8 +51,9 @@ struct bpf_reg_state {
         * These refer to the same value as var_off, not necessarily the actual
         * contents of the register.
         */
-       s64 min_value; /* minimum possible (s64)value */
-       u64 max_value; /* maximum possible (u64)value */
+       bool treat_as_signed; /* Do below limits come from a JSGT/JSGE? */
+       s64 min_value;
+       u64 max_value;
 };
 
 enum bpf_stack_slot_type {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 82823f1..b59c09b 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -474,6 +474,7 @@ static void __mark_reg_known_zero(struct bpf_reg_state *reg)
        reg->var_off = tnum_const(0);
        reg->min_value = 0;
        reg->max_value = 0;
+       reg->treat_as_signed = false;
 }
 
 static void mark_reg_known_zero(struct bpf_reg_state *regs, u32 regno)
@@ -497,6 +498,7 @@ static void __mark_reg_unknown(struct bpf_reg_state *reg)
        reg->var_off = tnum_unknown;
        reg->min_value = BPF_REGISTER_MIN_RANGE;
        reg->max_value = BPF_REGISTER_MAX_RANGE;
+       reg->treat_as_signed = false;
 }
 
 static void mark_reg_unknown(struct bpf_reg_state *regs, u32 regno)
@@ -1087,6 +1089,7 @@ static int check_mem_access(struct bpf_verifier_env *env, 
int insn_idx, u32 regn
                                        state->regs[value_regno].var_off.value |
                                        state->regs[value_regno].var_off.mask,
                                        BPF_REGISTER_MAX_RANGE);
+               state->regs[value_regno].treat_as_signed = false;
        }
        return err;
 }
@@ -1601,6 +1604,9 @@ static void check_reg_overflow(struct bpf_reg_state *reg)
        if (reg->min_value < BPF_REGISTER_MIN_RANGE ||
            reg->min_value > BPF_REGISTER_MAX_RANGE)
                reg->min_value = BPF_REGISTER_MIN_RANGE;
+       /* Unsigned cannot be < 0 */
+       if (!reg->treat_as_signed && reg->min_value < 0)
+               reg->min_value = 0;
 }
 
 static void coerce_reg_to_32(struct bpf_reg_state *reg)
@@ -1612,10 +1618,12 @@ static void coerce_reg_to_32(struct bpf_reg_state *reg)
        reg->var_off = tnum_cast(reg->var_off, 4);
        /* Did value become known?  Then update bounds */
        if (tnum_is_const(reg->var_off)) {
-               if ((s64)reg->var_off.value > BPF_REGISTER_MIN_RANGE)
+               /* We're treating as unsigned, so min is always >= 0 */
+               if (reg->var_off.value < BPF_REGISTER_MAX_RANGE) {
                        reg->min_value = reg->var_off.value;
-               if (reg->var_off.value < BPF_REGISTER_MAX_RANGE)
                        reg->max_value = reg->var_off.value;
+               }
+               reg->treat_as_signed = false;
        }
 }
 
@@ -1637,6 +1645,18 @@ static int adjust_ptr_min_max_vals(struct 
bpf_verifier_env *env,
        u8 opcode = BPF_OP(insn->code);
        u32 dst = insn->dst_reg;
 
+       /* If we can cross the sign boundary, don't trust our bounds.
+        * Normally the program should have given us both upper and lower
+        * bounds (e.g. two signed checks or one unsigned upper bound), in
+        * which case this won't fire.
+        */
+       if (off_reg->treat_as_signed) {
+               if (min_val == BPF_REGISTER_MIN_RANGE)
+                       max_val = BPF_REGISTER_MAX_RANGE;
+       } else {
+               if (max_val == BPF_REGISTER_MAX_RANGE)
+                       min_val = BPF_REGISTER_MIN_RANGE;
+       }
        dst_reg = &regs[dst];
 
        if (WARN_ON_ONCE(known && (min_val != max_val))) {
@@ -1677,6 +1697,11 @@ static int adjust_ptr_min_max_vals(struct 
bpf_verifier_env *env,
         */
        dst_reg->type = ptr_reg->type;
        dst_reg->id = ptr_reg->id;
+       /* We also inherit the signedness of our offset.
+        * XXX If we already had an offset of a different signedness, this
+        * might lead to trouble!
+        */
+       dst_reg->treat_as_signed = off_reg->treat_as_signed;
 
        switch (opcode) {
        case BPF_ADD:
@@ -1820,6 +1845,18 @@ static int adjust_scalar_min_max_vals(struct 
bpf_verifier_env *env,
        }
        min_val = src_reg->min_value;
        max_val = src_reg->max_value;
+       /* If we can cross the sign boundary, don't trust our bounds.
+        * Normally the program should have given us both upper and lower
+        * bounds (e.g. two signed checks or one unsigned upper bound), in
+        * which case this won't fire.
+        */
+       if (src_reg->treat_as_signed) {
+               if (min_val == BPF_REGISTER_MIN_RANGE)
+                       max_val = BPF_REGISTER_MAX_RANGE;
+       } else {
+               if (max_val == BPF_REGISTER_MAX_RANGE)
+                       min_val = BPF_REGISTER_MIN_RANGE;
+       }
        src_known = tnum_is_const(src_reg->var_off);
        dst_known = tnum_is_const(dst_reg->var_off);
 
@@ -2004,10 +2041,7 @@ static int adjust_reg_min_max_vals(struct 
bpf_verifier_env *env,
                                                             src_reg, dst_reg);
                                if (rc == -EACCES && env->allow_ptr_leaks) {
                                        /* scalar += unknown scalar */
-                                       __mark_reg_unknown(&off_reg);
-                                       return adjust_scalar_min_max_vals(
-                                                       env, insn,
-                                                       dst_reg, &off_reg);
+                                       __mark_reg_unknown(dst_reg);
                                }
                                return rc;
                        }
@@ -2018,8 +2052,6 @@ static int adjust_reg_min_max_vals(struct 
bpf_verifier_env *env,
                        if (rc == -EACCES && env->allow_ptr_leaks) {
                                /* unknown scalar += scalar */
                                __mark_reg_unknown(dst_reg);
-                               return adjust_scalar_min_max_vals(
-                                               env, insn, dst_reg, src_reg);
                        }
                        return rc;
                }
@@ -2031,6 +2063,7 @@ static int adjust_reg_min_max_vals(struct 
bpf_verifier_env *env,
                off_reg.var_off = tnum_const(insn->imm);
                off_reg.min_value = insn->imm;
                off_reg.max_value = insn->imm;
+               off_reg.treat_as_signed = false;
                src_reg = &off_reg;
                if (ptr_reg) { /* pointer += K */
                        rc = adjust_ptr_min_max_vals(env, insn,
@@ -2038,8 +2071,6 @@ static int adjust_reg_min_max_vals(struct 
bpf_verifier_env *env,
                        if (rc == -EACCES && env->allow_ptr_leaks) {
                                /* unknown scalar += K */
                                __mark_reg_unknown(dst_reg);
-                               return adjust_scalar_min_max_vals(
-                                               env, insn, dst_reg, &off_reg);
                        }
                        return rc;
                }
@@ -2151,6 +2182,7 @@ static int check_alu_op(struct bpf_verifier_env *env, 
struct bpf_insn *insn)
                        regs[insn->dst_reg].var_off = tnum_const(insn->imm);
                        regs[insn->dst_reg].max_value = insn->imm;
                        regs[insn->dst_reg].min_value = insn->imm;
+                       regs[insn->dst_reg].treat_as_signed = false;
                        regs[insn->dst_reg].id = 0;
                }
 
@@ -2271,12 +2303,15 @@ static void reg_set_min_max(struct bpf_reg_state 
*true_reg,
                            struct bpf_reg_state *false_reg, u64 val,
                            u8 opcode)
 {
+       bool treat_as_signed = true;
+
        switch (opcode) {
        case BPF_JEQ:
                /* If this is false then we know nothing Jon Snow, but if it is
                 * true then we know for sure.
                 */
                true_reg->max_value = true_reg->min_value = val;
+               true_reg->treat_as_signed = false;
                true_reg->var_off = tnum_const(val);
                break;
        case BPF_JNE:
@@ -2284,45 +2319,42 @@ static void reg_set_min_max(struct bpf_reg_state 
*true_reg,
                 * we know the value for sure;
                 */
                false_reg->max_value = false_reg->min_value = val;
+               false_reg->treat_as_signed = false;
                false_reg->var_off = tnum_const(val);
                break;
        case BPF_JGT:
-               /* Unsigned comparison, can only tell us about max_value (since
-                * min_value is signed), unless we learn sign bit.
-                */
-               false_reg->max_value = val;
-               /* If we're not unsigned-greater-than a positive value, then
-                * we can't be negative.
-                */
-               if ((s64)val >= 0 && false_reg->min_value < 0)
-                       false_reg->min_value = 0;
-               break;
+               /* Unsigned comparison, min_value is 0 */
+               true_reg->min_value = 0;
+               treat_as_signed = false;
        case BPF_JSGT:
-               /* Signed comparison, can only tell us about min_value (since
-                * max_value is unsigned), unless we already know sign bit.
+               /* r > k => true->min = k+1
+                * r <= k => false->max = k
                 */
+               false_reg->max_value = val;
                true_reg->min_value = val + 1;
-               /* If we're not signed-greater than val, and we're not negative,
-                * then we can't be unsigned-greater than val either.
-                */
-               if (false_reg->min_value >= 0)
-                       false_reg->max_value = val;
+               if (false_reg->treat_as_signed != treat_as_signed)
+                       false_reg->min_value = BPF_REGISTER_MIN_RANGE;
+               if (true_reg->treat_as_signed != treat_as_signed)
+                       true_reg->max_value = BPF_REGISTER_MAX_RANGE;
+               false_reg->treat_as_signed = treat_as_signed;
+               true_reg->treat_as_signed = treat_as_signed;
                break;
        case BPF_JGE:
-               false_reg->max_value = val - 1;
-               /* If we're not unsigned-ge a positive value, then we can't be
-                * negative.
-                */
-               if ((s64)val >= 0 && false_reg->min_value < 0)
-                       false_reg->min_value = 0;
-               break;
+               /* Unsigned comparison, min_value is 0 */
+               true_reg->min_value = 0;
+               treat_as_signed = false;
        case BPF_JSGE:
-               true_reg->min_value = val;
-               /* If we're not signed-ge val, and we're not negative, then we
-                * can't be unsigned-ge val either.
+               /* r >= k => true->min = k
+                * r < k => false->max = k-1
                 */
-               if (false_reg->min_value >= 0)
-                       false_reg->max_value = val - 1;
+               false_reg->max_value = val - 1;
+               true_reg->min_value = val;
+               if (false_reg->treat_as_signed != treat_as_signed)
+                       false_reg->min_value = BPF_REGISTER_MIN_RANGE;
+               if (true_reg->treat_as_signed != treat_as_signed)
+                       true_reg->max_value = BPF_REGISTER_MAX_RANGE;
+               false_reg->treat_as_signed = treat_as_signed;
+               true_reg->treat_as_signed = treat_as_signed;
                break;
        default:
                break;
@@ -2339,12 +2371,15 @@ static void reg_set_min_max_inv(struct bpf_reg_state 
*true_reg,
                                struct bpf_reg_state *false_reg, u64 val,
                                u8 opcode)
 {
+       bool treat_as_signed = true;
+
        switch (opcode) {
        case BPF_JEQ:
                /* If this is false then we know nothing Jon Snow, but if it is
                 * true then we know for sure.
                 */
                true_reg->max_value = true_reg->min_value = val;
+               true_reg->treat_as_signed = false;
                true_reg->var_off = tnum_const(val);
                break;
        case BPF_JNE:
@@ -2352,45 +2387,42 @@ static void reg_set_min_max_inv(struct bpf_reg_state 
*true_reg,
                 * we know the value for sure;
                 */
                false_reg->max_value = false_reg->min_value = val;
+               false_reg->treat_as_signed = false;
                false_reg->var_off = tnum_const(val);
                break;
        case BPF_JGT:
-               /* Unsigned comparison, can only tell us about max_value (since
-                * min_value is signed), unless we learn sign bit.
-                */
-               true_reg->max_value = val - 1;
-               /* If a positive value is unsigned-greater-than us, then we
-                * can't be negative.
-                */
-               if ((s64)val >= 0 && true_reg->min_value < 0)
-                       true_reg->min_value = 0;
-               break;
+               /* Unsigned comparison, min_value is 0 */
+               true_reg->min_value = 0;
+               treat_as_signed = false;
        case BPF_JSGT:
-               /* Signed comparison, can only tell us about min_value (since
-                * max_value is unsigned), unless we already know sign bit.
+               /* k > r => true->max = k-1
+                * k <= r => false->min = k
                 */
                false_reg->min_value = val;
-               /* If val is signed-greater-than us, and we're not negative,
-                * then val must be unsigned-greater-than us.
-                */
-               if (true_reg->min_value >= 0)
-                       true_reg->max_value = val - 1;
+               true_reg->max_value = val - 1;
+               if (true_reg->treat_as_signed != treat_as_signed)
+                       true_reg->min_value = BPF_REGISTER_MIN_RANGE;
+               if (false_reg->treat_as_signed != treat_as_signed)
+                       false_reg->max_value = BPF_REGISTER_MAX_RANGE;
+               false_reg->treat_as_signed = treat_as_signed;
+               true_reg->treat_as_signed = treat_as_signed;
                break;
        case BPF_JGE:
-               true_reg->max_value = val;
-               /* If a positive value is unsigned-ge us, then we can't be
-                * negative.
-                */
-               if ((s64)val >= 0 && true_reg->min_value < 0)
-                       true_reg->min_value = 0;
-               break;
+               /* Unsigned comparison, min_value is 0 */
+               true_reg->min_value = 0;
+               treat_as_signed = false;
        case BPF_JSGE:
-               false_reg->min_value = val + 1;
-               /* If val is signed-ge us, and we're not negative, then val
-                * must be unsigned-ge us.
+               /* k >= r => true->max = k
+                * k < r => false->min = k + 1
                 */
-               if (true_reg->min_value >= 0)
-                       true_reg->max_value = val;
+               false_reg->min_value = val + 1;
+               true_reg->max_value = val;
+               if (true_reg->treat_as_signed != treat_as_signed)
+                       true_reg->min_value = BPF_REGISTER_MIN_RANGE;
+               if (false_reg->treat_as_signed != treat_as_signed)
+                       false_reg->max_value = BPF_REGISTER_MAX_RANGE;
+               false_reg->treat_as_signed = treat_as_signed;
+               true_reg->treat_as_signed = treat_as_signed;
                break;
        default:
                break;
@@ -2404,12 +2436,14 @@ static void reg_set_min_max_inv(struct bpf_reg_state 
*true_reg,
 static void __reg_combine_min_max(struct bpf_reg_state *src_reg,
                                  struct bpf_reg_state *dst_reg)
 {
+       src_reg->var_off = dst_reg->var_off = tnum_intersect(src_reg->var_off,
+                                                            dst_reg->var_off);
+       if (src_reg->treat_as_signed != dst_reg->treat_as_signed)
+               return;
        src_reg->min_value = dst_reg->min_value = max(src_reg->min_value,
                                                      dst_reg->min_value);
        src_reg->max_value = dst_reg->max_value = min(src_reg->max_value,
                                                      dst_reg->max_value);
-       src_reg->var_off = dst_reg->var_off = tnum_intersect(src_reg->var_off,
-                                                            dst_reg->var_off);
        check_reg_overflow(src_reg);
        check_reg_overflow(dst_reg);
 }
@@ -2443,6 +2477,7 @@ static void mark_map_reg(struct bpf_reg_state *regs, u32 
regno, u32 id,
                                 reg->var_off.value || reg->var_off.mask ||
                                 reg->off)) {
                        reg->min_value = reg->max_value = reg->off = 0;
+                       reg->treat_as_signed = false;
                        reg->var_off = tnum_const(0);
                }
                if (is_null) {
@@ -2637,6 +2672,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, 
struct bpf_insn *insn)
                regs[insn->dst_reg].type = SCALAR_VALUE;
                regs[insn->dst_reg].min_value = imm;
                regs[insn->dst_reg].max_value = imm;
+               regs[insn->dst_reg].treat_as_signed = false;
                check_reg_overflow(&regs[insn->dst_reg]);
                regs[insn->dst_reg].var_off = tnum_const(imm);
                regs[insn->dst_reg].id = 0;
@@ -2927,7 +2963,8 @@ static int check_cfg(struct bpf_verifier_env *env)
 static bool range_within(struct bpf_reg_state *old,
                         struct bpf_reg_state *cur)
 {
-       return old->min_value <= cur->min_value &&
+       return old->treat_as_signed == cur->treat_as_signed &&
+              old->min_value <= cur->min_value &&
               old->max_value >= cur->max_value;
 }
 

Reply via email to