Skip to content

Commit 07d0bc0

Browse files
fixed a bug where inv mod of 0 could result in UB (#1628)
1 parent f59e5a5 commit 07d0bc0

1 file changed

Lines changed: 63 additions & 23 deletions

File tree

src/libfuncs/uint256.rs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! # `u256`-related libfuncs
22
3-
use super::{increment_builtin_counter_conditionally_by, LibfuncHelper};
3+
use super::{increment_builtin_counter_by, LibfuncHelper};
44
use crate::{error::Result, metadata::MetadataStorage, utils::ProgramRegistryExt};
55
use cairo_lang_sierra::{
66
extensions::{
@@ -14,7 +14,7 @@ use cairo_lang_sierra::{
1414
use melior::{
1515
dialect::{
1616
arith::{self, CmpiPredicate},
17-
llvm, ods, scf,
17+
cf, llvm, ods, scf,
1818
},
1919
helpers::BuiltinBlockExt,
2020
ir::{
@@ -761,6 +761,61 @@ pub fn build_u256_guarantee_inv_mod_n<'ctx, 'this>(
761761
.result(0)?
762762
.into();
763763

764+
// `a` (= lhs) is not constrained to be non-zero by the signature
765+
// `u256_guarantee_inv_mod_n(a, n: NonZero<u256>)`, and the extended Euclidean loop
766+
// below divides by the current divisor (initially `a`) on its first iteration, so
767+
// `a == 0` would be a udiv-by-zero (UB at the LLVM IR level). Since gcd(0, n) = n has
768+
// no inverse, short-circuit the whole calculation when `a == 0` and branch straight to
769+
// the "no inverse" output, matching casm_vm's U256InvModN hint. The range check builtin
770+
// is incremented 7 times on this path, same as the regular not-invertible branch.
771+
let guarantee_type = registry.build_type(
772+
context,
773+
helper,
774+
metadata,
775+
info.output_types().next().unwrap().nth(2).unwrap(),
776+
)?;
777+
let range_check = entry.arg(0)?;
778+
779+
// The "no inverse" branch (output index 1) is reached from two places: the `a == 0`
780+
// short-circuit below and the regular `gcd != 1` exit of the Euclidean loop. The
781+
// LibfuncHelper reuses the values passed to `br`/`cond_br` directly in the shared
782+
// landing block, so they must dominate it. We therefore build them here in the entry
783+
// block (a common dominator of both predecessors): an `undef` guarantee and the range
784+
// check incremented 7 times.
785+
let guarantee = entry
786+
.append_operation(llvm::undef(guarantee_type, location))
787+
.result(0)?
788+
.into();
789+
let range_check_no_inverse =
790+
increment_builtin_counter_by(context, entry, location, range_check, 7)?;
791+
792+
let lhs_is_zero = entry
793+
.append_operation(arith::cmpi(context, CmpiPredicate::Eq, lhs, k0, location))
794+
.result(0)?
795+
.into();
796+
let no_inverse_block = helper.append_block(Block::new(&[]));
797+
let compute_block = helper.append_block(Block::new(&[]));
798+
entry.append_operation(cf::cond_br(
799+
context,
800+
lhs_is_zero,
801+
no_inverse_block,
802+
compute_block,
803+
&[],
804+
&[],
805+
location,
806+
));
807+
808+
helper.br(
809+
no_inverse_block,
810+
1,
811+
&[range_check_no_inverse, guarantee, guarantee],
812+
location,
813+
)?;
814+
815+
// `a != 0`: the inverse may exist, so run the extended Euclidean algorithm. From here
816+
// on everything is emitted into `compute_block` instead of the original entry block.
817+
let entry = compute_block;
818+
764819
let result = entry.append_operation(scf::r#while(
765820
&[lhs, rhs, k1, k0],
766821
&[i256_ty, i256_ty, i256_ty, i256_ty],
@@ -932,27 +987,12 @@ pub fn build_u256_guarantee_inv_mod_n<'ctx, 'this>(
932987
.result(0)?
933988
.into();
934989

935-
let guarantee_type = registry.build_type(
936-
context,
937-
helper,
938-
metadata,
939-
info.output_types().next().unwrap().nth(2).unwrap(),
940-
)?;
941-
let op = entry.append_operation(llvm::undef(guarantee_type, location));
942-
let guarantee = op.result(0)?.into();
943-
944990
// The sierra-to-casm compiler uses the range check builtin a total of 9 times if the inverse is
945-
// not equal to 0 and lhs is invertible. Otherwise it will be used 7 times.
991+
// not equal to 0 and lhs is invertible. Otherwise it will be used 7 times (handled by
992+
// `range_check_no_inverse`, computed in the entry block).
946993
// https://github.com/starkware-libs/cairo/blob/v2.12.0-dev.1/crates/cairo-lang-sierra-to-casm/src/invocations/int/unsigned256.rs#L21
947-
let range_check = increment_builtin_counter_conditionally_by(
948-
context,
949-
entry,
950-
location,
951-
entry.arg(0)?,
952-
9,
953-
7,
954-
inverse_exists_and_is_not_zero,
955-
)?;
994+
let range_check_inverse =
995+
increment_builtin_counter_by(context, entry, location, range_check, 9)?;
956996

957997
helper.cond_br(
958998
context,
@@ -961,7 +1001,7 @@ pub fn build_u256_guarantee_inv_mod_n<'ctx, 'this>(
9611001
[0, 1],
9621002
[
9631003
&[
964-
range_check,
1004+
range_check_inverse,
9651005
result_inv,
9661006
guarantee,
9671007
guarantee,
@@ -972,7 +1012,7 @@ pub fn build_u256_guarantee_inv_mod_n<'ctx, 'this>(
9721012
guarantee,
9731013
guarantee,
9741014
],
975-
&[range_check, guarantee, guarantee],
1015+
&[range_check_no_inverse, guarantee, guarantee],
9761016
],
9771017
location,
9781018
)

0 commit comments

Comments
 (0)