Avoid UB in overflow checks

Some of the overflow checks in zend_may_overflow were optimized
away by clang, causing JIT failures on release macos.
This commit is contained in:
Nikita Popov 2019-06-19 11:58:42 +02:00
parent 089e45c91a
commit bb940d9969
4 changed files with 39 additions and 44 deletions

View file

@ -575,19 +575,21 @@ static int zend_inference_calc_binary_op_range(
op2_min = OP2_MIN_RANGE(); op2_min = OP2_MIN_RANGE();
op1_max = OP1_MAX_RANGE(); op1_max = OP1_MAX_RANGE();
op2_max = OP2_MAX_RANGE(); op2_max = OP2_MAX_RANGE();
tmp->min = op1_min + op2_min;
tmp->max = op1_max + op2_max;
if (OP1_RANGE_UNDERFLOW() || if (OP1_RANGE_UNDERFLOW() ||
OP2_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
(op1_min < 0 && op2_min < 0 && tmp->min >= 0)) { zend_add_will_overflow(op1_min, op2_min)) {
tmp->underflow = 1; tmp->underflow = 1;
tmp->min = ZEND_LONG_MIN; tmp->min = ZEND_LONG_MIN;
} else {
tmp->min = op1_min + op2_min;
} }
if (OP1_RANGE_OVERFLOW() || if (OP1_RANGE_OVERFLOW() ||
OP2_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() ||
(op1_max > 0 && op2_max > 0 && tmp->max <= 0)) { zend_add_will_overflow(op1_max, op2_max)) {
tmp->overflow = 1; tmp->overflow = 1;
tmp->max = ZEND_LONG_MAX; tmp->max = ZEND_LONG_MAX;
} else {
tmp->max = op1_max + op2_max;
} }
return 1; return 1;
} }
@ -598,19 +600,21 @@ static int zend_inference_calc_binary_op_range(
op2_min = OP2_MIN_RANGE(); op2_min = OP2_MIN_RANGE();
op1_max = OP1_MAX_RANGE(); op1_max = OP1_MAX_RANGE();
op2_max = OP2_MAX_RANGE(); op2_max = OP2_MAX_RANGE();
tmp->min = op1_min - op2_max;
tmp->max = op1_max - op2_min;
if (OP1_RANGE_UNDERFLOW() || if (OP1_RANGE_UNDERFLOW() ||
OP2_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() ||
(op1_min < 0 && op2_max > 0 && tmp->min >= 0)) { zend_sub_will_overflow(op1_min, op2_max)) {
tmp->underflow = 1; tmp->underflow = 1;
tmp->min = ZEND_LONG_MIN; tmp->min = ZEND_LONG_MIN;
} else {
tmp->min = op1_min - op2_max;
} }
if (OP1_RANGE_OVERFLOW() || if (OP1_RANGE_OVERFLOW() ||
OP2_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
(op1_max > 0 && op2_min < 0 && tmp->max <= 0)) { zend_sub_will_overflow(op1_max, op2_min)) {
tmp->overflow = 1; tmp->overflow = 1;
tmp->max = ZEND_LONG_MAX; tmp->max = ZEND_LONG_MAX;
} else {
tmp->max = op1_max - op2_min;
} }
return 1; return 1;
} }

View file

@ -238,6 +238,14 @@ DEFINE_SSA_OP_DEF_INFO(result)
#define OP2_DATA_DEF_INFO() (_ssa_op2_def_info(op_array, ssa, (opline+1))) #define OP2_DATA_DEF_INFO() (_ssa_op2_def_info(op_array, ssa, (opline+1)))
#define RES_INFO() (_ssa_result_def_info(op_array, ssa, opline)) #define RES_INFO() (_ssa_result_def_info(op_array, ssa, opline))
static zend_always_inline zend_bool zend_add_will_overflow(zend_long a, zend_long b) {
return (b > 0 && a > ZEND_LONG_MAX - b)
|| (b < 0 && a < ZEND_LONG_MIN - b);
}
static zend_always_inline zend_bool zend_sub_will_overflow(zend_long a, zend_long b) {
return (b > 0 && a < ZEND_LONG_MIN + b)
|| (b < 0 && a > ZEND_LONG_MAX + b);
}
BEGIN_EXTERN_C() BEGIN_EXTERN_C()

View file

@ -221,15 +221,6 @@ static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_f
} }
/* }}} */ /* }}} */
static inline zend_bool add_will_overflow(zend_long a, zend_long b) {
return (b > 0 && a > ZEND_LONG_MAX - b)
|| (b < 0 && a < ZEND_LONG_MIN - b);
}
static inline zend_bool sub_will_overflow(zend_long a, zend_long b) {
return (b > 0 && a < ZEND_LONG_MIN + b)
|| (b < 0 && a > ZEND_LONG_MAX + b);
}
/* e-SSA construction: Pi placement (Pi is actually a Phi with single /* e-SSA construction: Pi placement (Pi is actually a Phi with single
* source and constraint). * source and constraint).
* Order of Phis is importent, Pis must be placed before Phis * Order of Phis is importent, Pis must be placed before Phis
@ -291,7 +282,7 @@ static void place_essa_pis(
} }
if (var1 >= 0 && var2 >= 0) { if (var1 >= 0 && var2 >= 0) {
if (!sub_will_overflow(val1, val2) && !sub_will_overflow(val2, val1)) { if (!zend_sub_will_overflow(val1, val2) && !zend_sub_will_overflow(val2, val1)) {
zend_long tmp = val1; zend_long tmp = val1;
val1 -= val2; val1 -= val2;
val2 -= tmp; val2 -= tmp;
@ -316,7 +307,7 @@ static void place_essa_pis(
} else { } else {
var1 = -1; var1 = -1;
} }
if (!add_will_overflow(val2, add_val2)) { if (!zend_add_will_overflow(val2, add_val2)) {
val2 += add_val2; val2 += add_val2;
} else { } else {
var1 = -1; var1 = -1;
@ -337,7 +328,7 @@ static void place_essa_pis(
} else { } else {
var2 = -1; var2 = -1;
} }
if (!add_will_overflow(val1, add_val1)) { if (!zend_add_will_overflow(val1, add_val1)) {
val1 += add_val1; val1 += add_val1;
} else { } else {
var2 = -1; var2 = -1;

View file

@ -433,28 +433,26 @@ static int zend_may_overflow(const zend_op *opline, zend_op_array *op_array, zen
return 1; return 1;
} }
if (ssa->var_info[res].range.underflow) { if (ssa->var_info[res].range.underflow) {
zend_long op1_min, op2_min, res_min; zend_long op1_min, op2_min;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_min = OP1_MIN_RANGE(); op1_min = OP1_MIN_RANGE();
op2_min = OP2_MIN_RANGE(); op2_min = OP2_MIN_RANGE();
res_min = op1_min + op2_min; if (zend_add_will_overflow(op1_min, op2_min)) {
if (op1_min < 0 && op2_min < 0 && res_min >= 0) {
return 1; return 1;
} }
} }
if (ssa->var_info[res].range.overflow) { if (ssa->var_info[res].range.overflow) {
zend_long op1_max, op2_max, res_max; zend_long op1_max, op2_max;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_max = OP1_MAX_RANGE(); op1_max = OP1_MAX_RANGE();
op2_max = OP2_MAX_RANGE(); op2_max = OP2_MAX_RANGE();
res_max = op1_max + op2_max; if (zend_add_will_overflow(op1_max, op2_max)) {
if (op1_max > 0 && op2_max > 0 && res_max <= 0) {
return 1; return 1;
} }
} }
@ -467,28 +465,26 @@ static int zend_may_overflow(const zend_op *opline, zend_op_array *op_array, zen
return 1; return 1;
} }
if (ssa->var_info[res].range.underflow) { if (ssa->var_info[res].range.underflow) {
zend_long op1_min, op2_max, res_min; zend_long op1_min, op2_max;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_min = OP1_MIN_RANGE(); op1_min = OP1_MIN_RANGE();
op2_max = OP2_MAX_RANGE(); op2_max = OP2_MAX_RANGE();
res_min = op1_min - op2_max; if (zend_sub_will_overflow(op1_min, op2_max)) {
if (op1_min < 0 && op2_max > 0 && res_min >= 0) {
return 1; return 1;
} }
} }
if (ssa->var_info[res].range.overflow) { if (ssa->var_info[res].range.overflow) {
zend_long op1_max, op2_min, res_max; zend_long op1_max, op2_min;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_max = OP1_MAX_RANGE(); op1_max = OP1_MAX_RANGE();
op2_min = OP2_MIN_RANGE(); op2_min = OP2_MIN_RANGE();
res_max = op1_max - op2_min; if (zend_sub_will_overflow(op1_max, op2_min)) {
if (op1_max > 0 && op2_min < 0 && res_max <= 0) {
return 1; return 1;
} }
} }
@ -511,28 +507,26 @@ static int zend_may_overflow(const zend_op *opline, zend_op_array *op_array, zen
return 1; return 1;
} }
if (ssa->var_info[res].range.underflow) { if (ssa->var_info[res].range.underflow) {
zend_long op1_min, op2_min, res_min; zend_long op1_min, op2_min;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_min = OP1_MIN_RANGE(); op1_min = OP1_MIN_RANGE();
op2_min = OP2_MIN_RANGE(); op2_min = OP2_MIN_RANGE();
res_min = op1_min + op2_min; if (zend_add_will_overflow(op1_min, op2_min)) {
if (op1_min < 0 && op2_min < 0 && res_min >= 0) {
return 1; return 1;
} }
} }
if (ssa->var_info[res].range.overflow) { if (ssa->var_info[res].range.overflow) {
zend_long op1_max, op2_max, res_max; zend_long op1_max, op2_max;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_max = OP1_MAX_RANGE(); op1_max = OP1_MAX_RANGE();
op2_max = OP2_MAX_RANGE(); op2_max = OP2_MAX_RANGE();
res_max = op1_max + op2_max; if (zend_add_will_overflow(op1_max, op2_max)) {
if (op1_max > 0 && op2_max > 0 && res_max <= 0) {
return 1; return 1;
} }
} }
@ -548,28 +542,26 @@ static int zend_may_overflow(const zend_op *opline, zend_op_array *op_array, zen
return 1; return 1;
} }
if (ssa->var_info[res].range.underflow) { if (ssa->var_info[res].range.underflow) {
zend_long op1_min, op2_max, res_min; zend_long op1_min, op2_max;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_min = OP1_MIN_RANGE(); op1_min = OP1_MIN_RANGE();
op2_max = OP2_MAX_RANGE(); op2_max = OP2_MAX_RANGE();
res_min = op1_min - op2_max; if (zend_sub_will_overflow(op1_min, op2_max)) {
if (op1_min < 0 && op2_max > 0 && res_min >= 0) {
return 1; return 1;
} }
} }
if (ssa->var_info[res].range.overflow) { if (ssa->var_info[res].range.overflow) {
zend_long op1_max, op2_min, res_max; zend_long op1_max, op2_min;
if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) { if (!OP1_HAS_RANGE() || !OP2_HAS_RANGE()) {
return 1; return 1;
} }
op1_max = OP1_MAX_RANGE(); op1_max = OP1_MAX_RANGE();
op2_min = OP2_MIN_RANGE(); op2_min = OP2_MIN_RANGE();
res_max = op1_max - op2_min; if (zend_sub_will_overflow(op1_max, op2_min)) {
if (op1_max > 0 && op2_min < 0 && res_max <= 0) {
return 1; return 1;
} }
} }