Skip to content

Commit 73fd9ae

Browse files
authored
Cranelift: x64: fix user-controlled recursion in cmp emission. (#12333) (#12339)
* Cranelift: x64: fix user-controlled recursion in cmp emission. We had a set of rules introduced in #11097 that attempted to optimize the case of testing the result of an `icmp` for a nonzero value. This allowed optimization of, for example, `(((x == 0) == 0) == 0 ...)` to a single level, either `x == 0` or `x != 0` depending on even/odd nesting depth. Unfortunately this kind of recursion in the backend has a depth bounded only by the user input, hence creates a DoS vulnerability: the wrong kind of compiler input can cause a stack overflow in Cranelift at compilation time. This case is reachable from Wasmtime's Wasm frontend via the `i32.eqz` operator (for example) as well. Ideally, this kind of deep rewrite is best done in our mid-end optimizer, where we think carefully about bounds for recursive rewrites. The left-hand sides for the backend rules should really be fixed shapes that correspond to machine instructions, rather than ad-hoc peephole optimizations in their own right. This fix thus simply removes the recursion case that causes the blowup. The patch includes two tests: one with optimizations disabled, showing correct compilation (without the fix, this case fails to compile with a stack overflow), and one with optimizations enabled, showing that the mid-end properly cleans up the nested expression and we get the expected one-level result anyway. * Preserve codegen on branches. This change works by splitting a rule so that the entry point used by `brif` lowering can still peel off one layer of `icmp` and emit it directly, without entering the unbounded structural recursion. It also adds a mid-end rule to catch one case that we were previously catching in the backend only: `fcmp(...) != 0`.
1 parent efb390d commit 73fd9ae

File tree

4 files changed

+60094
-18
lines changed

4 files changed

+60094
-18
lines changed

cranelift/codegen/src/isa/x64/inst.isle

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3423,14 +3423,14 @@
34233423
;; Note that this is used as the base entry case for instruction lowering such
34243424
;; as `select` and `brif`. The `Value` here is expected to, via CLIF validation,
34253425
;; have an integer type (and it can be I128)
3426-
(decl is_nonzero_cmp (Value) CondResult)
3426+
(decl is_nonzero (Value) CondResult)
34273427

34283428
;; Base case: fits in one GPR, use `x64_test`
3429-
(rule (is_nonzero_cmp val @ (value_type (is_single_register_gpr_type ty)))
3429+
(rule (is_nonzero val @ (value_type (is_single_register_gpr_type ty)))
34303430
(let ((gpr Gpr val)) (CondResult.CC (x64_test ty gpr gpr) (CC.NZ))))
34313431

34323432
;; Base case: i128
3433-
(rule 1 (is_nonzero_cmp val @ (value_type $I128))
3433+
(rule 1 (is_nonzero val @ (value_type $I128))
34343434
(let ((lo Gpr (value_regs_get_gpr val 0))
34353435
(hi Gpr (value_regs_get_gpr val 1)))
34363436
(CondResult.CC
@@ -3439,13 +3439,24 @@
34393439

34403440
;; Special case some instructions where lowerings directly produce condition
34413441
;; codes.
3442-
(rule 2 (is_nonzero_cmp (fcmp cc a b)) (emit_fcmp cc a b))
3443-
(rule 2 (is_nonzero_cmp (icmp cc a b)) (emit_cmp cc a b))
3444-
(rule 2 (is_nonzero_cmp (vall_true vec)) (is_vall_true vec))
3445-
(rule 2 (is_nonzero_cmp (vany_true vec)) (is_vany_true vec))
3446-
(rule 2 (is_nonzero_cmp (uextend val)) (is_nonzero_cmp val))
3447-
(rule 2 (is_nonzero_cmp (band a @ (value_type (ty_int (fits_in_64 ty))) b))
3448-
(is_nonzero_band ty a b))
3442+
(rule 2 (is_nonzero (vall_true vec)) (is_vall_true vec))
3443+
(rule 2 (is_nonzero (vany_true vec)) (is_vany_true vec))
3444+
(rule 2 (is_nonzero (uextend (vall_true vec))) (is_vall_true vec))
3445+
(rule 2 (is_nonzero (uextend (vany_true vec))) (is_vany_true vec))
3446+
(rule 2 (is_nonzero (band a @ (value_type (ty_int (fits_in_64 ty))) b))
3447+
(is_nonzero_band ty a b))
3448+
3449+
3450+
;; Like `is_nonzero` but with additional specializations for compare
3451+
;; operators. We break this out from `is_nonzero` because we want to
3452+
;; avoid unbounded recursion.
3453+
(decl is_nonzero_cmp (Value) CondResult)
3454+
3455+
(rule 1 (is_nonzero_cmp (fcmp cc a b)) (emit_fcmp cc a b))
3456+
(rule 1 (is_nonzero_cmp (icmp cc a b)) (emit_cmp cc a b))
3457+
(rule 1 (is_nonzero_cmp (uextend (fcmp cc a b))) (emit_fcmp cc a b))
3458+
(rule 1 (is_nonzero_cmp (uextend (icmp cc a b))) (emit_cmp cc a b))
3459+
(rule 0 (is_nonzero_cmp val) (is_nonzero val))
34493460

34503461
(decl is_nonzero_band (Type Value Value) CondResult)
34513462
(rule 0 (is_nonzero_band ty a b) (CondResult.CC (x64_test ty a b) (CC.NZ)))
@@ -3530,7 +3541,14 @@
35303541
(a_hi Gpr (value_regs_get_gpr a 1))
35313542
(b_lo Gpr (value_regs_get_gpr b 0))
35323543
(b_hi Gpr (value_regs_get_gpr b 1)))
3533-
(emit_cmp_i128 cc a_hi a_lo b_hi b_lo)))
3544+
(emit_cmp_i128 cc a_hi a_lo b_hi b_lo)))
3545+
3546+
;; For direct equality comparisons to zero transform the other operand into a
3547+
;; nonzero comparison and then invert the whole conditional to test for zero.
3548+
(rule 5 (emit_cmp (IntCC.Equal) a (u64_from_iconst 0)) (cond_invert (is_nonzero a)))
3549+
(rule 6 (emit_cmp (IntCC.Equal) (u64_from_iconst 0) a) (cond_invert (is_nonzero a)))
3550+
(rule 5 (emit_cmp (IntCC.NotEqual) a (u64_from_iconst 0)) (is_nonzero a))
3551+
(rule 6 (emit_cmp (IntCC.NotEqual) (u64_from_iconst 0) a) (is_nonzero a))
35343552

35353553
(decl emit_cmp_i128 (CC Gpr Gpr Gpr Gpr) CondResult)
35363554
;; Eliminate cases which compare something "or equal" by swapping arguments.
@@ -3543,13 +3561,6 @@
35433561
(rule 2 (emit_cmp_i128 (CC.BE) a_hi a_lo b_hi b_lo)
35443562
(emit_cmp_i128 (CC.NB) b_hi b_lo a_hi a_lo))
35453563

3546-
;; For direct equality comparisons to zero transform the other operand into a
3547-
;; nonzero comparison and then invert the whole conditional to test for zero.
3548-
(rule 5 (emit_cmp (IntCC.Equal) a (u64_from_iconst 0)) (cond_invert (is_nonzero_cmp a)))
3549-
(rule 6 (emit_cmp (IntCC.Equal) (u64_from_iconst 0) a) (cond_invert (is_nonzero_cmp a)))
3550-
(rule 5 (emit_cmp (IntCC.NotEqual) a (u64_from_iconst 0)) (is_nonzero_cmp a))
3551-
(rule 6 (emit_cmp (IntCC.NotEqual) (u64_from_iconst 0) a) (is_nonzero_cmp a))
3552-
35533564
;; 128-bit strict equality/inequality can't be easily tested using subtraction
35543565
;; but we can quickly determine whether any bits are different instead.
35553566
(rule 1 (emit_cmp_i128 (cc_nz_or_z cc) a_hi a_lo b_hi b_lo)

cranelift/codegen/src/opts/icmp.isle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
(iconst_u _ 0)))
5252
(subsume inner))
5353

54+
;; Likewise for icmp-of-fcmp.
55+
;; ne(fcmp(ty, cc, x, y), 0) == fcmp(ty, cc, x, y)
56+
(rule (simplify (ne ty
57+
(uextend_maybe _ inner @ (fcmp ty _ _ _))
58+
(iconst_u _ 0)))
59+
(subsume inner))
60+
5461
;; eq(icmp(ty, cc, x, y), 0) == icmp(ty, cc_complement, x, y)
5562
;; e.g. eq(ugt(x, y), 0) == ule(x, y)
5663
(rule (simplify (eq ty

0 commit comments

Comments
 (0)