Skip to content

Commit 5c1a5f6

Browse files
authored
Cranelift: x64: fix user-controlled recursion in cmp emission. (#12333) (#12338)
* 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 8c97ae8 commit 5c1a5f6

4 files changed

Lines changed: 60094 additions & 18 deletions

File tree

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

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

34083408
;; Base case: fits in one GPR, use `x64_test`
3409-
(rule (is_nonzero_cmp val @ (value_type (is_single_register_gpr_type ty)))
3409+
(rule (is_nonzero val @ (value_type (is_single_register_gpr_type ty)))
34103410
(let ((gpr Gpr val)) (CondResult.CC (x64_test ty gpr gpr) (CC.NZ))))
34113411

34123412
;; Base case: i128
3413-
(rule 1 (is_nonzero_cmp val @ (value_type $I128))
3413+
(rule 1 (is_nonzero val @ (value_type $I128))
34143414
(let ((lo Gpr (value_regs_get_gpr val 0))
34153415
(hi Gpr (value_regs_get_gpr val 1)))
34163416
(CondResult.CC
@@ -3419,13 +3419,24 @@
34193419

34203420
;; Special case some instructions where lowerings directly produce condition
34213421
;; codes.
3422-
(rule 2 (is_nonzero_cmp (fcmp cc a b)) (emit_fcmp cc a b))
3423-
(rule 2 (is_nonzero_cmp (icmp cc a b)) (emit_cmp cc a b))
3424-
(rule 2 (is_nonzero_cmp (vall_true vec)) (is_vall_true vec))
3425-
(rule 2 (is_nonzero_cmp (vany_true vec)) (is_vany_true vec))
3426-
(rule 2 (is_nonzero_cmp (uextend val)) (is_nonzero_cmp val))
3427-
(rule 2 (is_nonzero_cmp (band a @ (value_type (ty_int (fits_in_64 ty))) b))
3428-
(is_nonzero_band ty a b))
3422+
(rule 2 (is_nonzero (vall_true vec)) (is_vall_true vec))
3423+
(rule 2 (is_nonzero (vany_true vec)) (is_vany_true vec))
3424+
(rule 2 (is_nonzero (uextend (vall_true vec))) (is_vall_true vec))
3425+
(rule 2 (is_nonzero (uextend (vany_true vec))) (is_vany_true vec))
3426+
(rule 2 (is_nonzero (band a @ (value_type (ty_int (fits_in_64 ty))) b))
3427+
(is_nonzero_band ty a b))
3428+
3429+
3430+
;; Like `is_nonzero` but with additional specializations for compare
3431+
;; operators. We break this out from `is_nonzero` because we want to
3432+
;; avoid unbounded recursion.
3433+
(decl is_nonzero_cmp (Value) CondResult)
3434+
3435+
(rule 1 (is_nonzero_cmp (fcmp cc a b)) (emit_fcmp cc a b))
3436+
(rule 1 (is_nonzero_cmp (icmp cc a b)) (emit_cmp cc a b))
3437+
(rule 1 (is_nonzero_cmp (uextend (fcmp cc a b))) (emit_fcmp cc a b))
3438+
(rule 1 (is_nonzero_cmp (uextend (icmp cc a b))) (emit_cmp cc a b))
3439+
(rule 0 (is_nonzero_cmp val) (is_nonzero val))
34293440

34303441
(decl is_nonzero_band (Type Value Value) CondResult)
34313442
(rule 0 (is_nonzero_band ty a b) (CondResult.CC (x64_test ty a b) (CC.NZ)))
@@ -3510,7 +3521,14 @@
35103521
(a_hi Gpr (value_regs_get_gpr a 1))
35113522
(b_lo Gpr (value_regs_get_gpr b 0))
35123523
(b_hi Gpr (value_regs_get_gpr b 1)))
3513-
(emit_cmp_i128 cc a_hi a_lo b_hi b_lo)))
3524+
(emit_cmp_i128 cc a_hi a_lo b_hi b_lo)))
3525+
3526+
;; For direct equality comparisons to zero transform the other operand into a
3527+
;; nonzero comparison and then invert the whole conditional to test for zero.
3528+
(rule 5 (emit_cmp (IntCC.Equal) a (u64_from_iconst 0)) (cond_invert (is_nonzero a)))
3529+
(rule 6 (emit_cmp (IntCC.Equal) (u64_from_iconst 0) a) (cond_invert (is_nonzero a)))
3530+
(rule 5 (emit_cmp (IntCC.NotEqual) a (u64_from_iconst 0)) (is_nonzero a))
3531+
(rule 6 (emit_cmp (IntCC.NotEqual) (u64_from_iconst 0) a) (is_nonzero a))
35143532

35153533
(decl emit_cmp_i128 (CC Gpr Gpr Gpr Gpr) CondResult)
35163534
;; Eliminate cases which compare something "or equal" by swapping arguments.
@@ -3523,13 +3541,6 @@
35233541
(rule 2 (emit_cmp_i128 (CC.BE) a_hi a_lo b_hi b_lo)
35243542
(emit_cmp_i128 (CC.NB) b_hi b_lo a_hi a_lo))
35253543

3526-
;; For direct equality comparisons to zero transform the other operand into a
3527-
;; nonzero comparison and then invert the whole conditional to test for zero.
3528-
(rule 5 (emit_cmp (IntCC.Equal) a (u64_from_iconst 0)) (cond_invert (is_nonzero_cmp a)))
3529-
(rule 6 (emit_cmp (IntCC.Equal) (u64_from_iconst 0) a) (cond_invert (is_nonzero_cmp a)))
3530-
(rule 5 (emit_cmp (IntCC.NotEqual) a (u64_from_iconst 0)) (is_nonzero_cmp a))
3531-
(rule 6 (emit_cmp (IntCC.NotEqual) (u64_from_iconst 0) a) (is_nonzero_cmp a))
3532-
35333544
;; 128-bit strict equality/inequality can't be easily tested using subtraction
35343545
;; but we can quickly determine whether any bits are different instead.
35353546
(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)