From f40f49e7aa3e5c612df99cab23132f0367396936 Mon Sep 17 00:00:00 2001 From: Aras14HD Date: Fri, 5 Jun 2026 17:57:51 +0200 Subject: [PATCH] fix fuzz crashes fix chrash on divison by 0 fix chrash char boundary parse as ComplexInfinity on non-finite float input fix crash on inf Float or Approximate in calculate_appropriate_factorial fix crash on effectively infinite (as float) Exact in calculate_appropriate_factorial --- Cargo.lock | 8 +++---- factorion-bot-discord/Cargo.toml | 4 ++-- factorion-bot-reddit/Cargo.toml | 4 ++-- factorion-lib/Cargo.toml | 4 ++-- factorion-lib/src/calculation_tasks.rs | 30 +++++++++++++++++++++----- factorion-lib/src/parse.rs | 29 +++++++++++++++++-------- factorion-math/Cargo.toml | 2 +- factorion-math/src/lib.rs | 5 +++-- 8 files changed, 59 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63a15d1..74e9973 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,7 +399,7 @@ dependencies = [ [[package]] name = "factorion-bot-discord" -version = "3.0.3" +version = "3.0.4" dependencies = [ "anyhow", "dotenvy", @@ -414,7 +414,7 @@ dependencies = [ [[package]] name = "factorion-bot-reddit" -version = "6.0.3" +version = "6.0.4" dependencies = [ "anyhow", "base64 0.22.1", @@ -434,7 +434,7 @@ dependencies = [ [[package]] name = "factorion-lib" -version = "6.0.3" +version = "6.0.4" dependencies = [ "arbtest", "chrono", @@ -447,7 +447,7 @@ dependencies = [ [[package]] name = "factorion-math" -version = "1.0.5" +version = "1.0.6" dependencies = [ "gmp-mpfr-sys", "rug", diff --git a/factorion-bot-discord/Cargo.toml b/factorion-bot-discord/Cargo.toml index 06bedec..1817cc4 100644 --- a/factorion-bot-discord/Cargo.toml +++ b/factorion-bot-discord/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factorion-bot-discord" -version = "3.0.3" +version = "3.0.4" edition = "2024" description = "factorion-bot (for factorials and related) on Discord" license = "MIT" @@ -10,7 +10,7 @@ keywords = ["factorial", "termial", "bot", "math", "discord"] categories = ["mathematics", "web-programming", "parser-implementations"] [dependencies] -factorion-lib = { path = "../factorion-lib", version = "6.0.3", features = ["serde", "influxdb"] } +factorion-lib = { path = "../factorion-lib", version = "6.0.4", features = ["serde", "influxdb"] } serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "time"] } dotenvy = "^0.15.7" diff --git a/factorion-bot-reddit/Cargo.toml b/factorion-bot-reddit/Cargo.toml index 4db254f..09ed5bb 100644 --- a/factorion-bot-reddit/Cargo.toml +++ b/factorion-bot-reddit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factorion-bot-reddit" -version = "6.0.3" +version = "6.0.4" edition = "2024" description = "factorion-bot (for factorials and related) on Reddit" license = "MIT" @@ -10,7 +10,7 @@ keywords = ["factorial", "termial", "bot", "math"] categories = ["mathematics", "web-programming", "parser-implementations"] [dependencies] -factorion-lib = {path = "../factorion-lib", version = "6.0.3", features = ["serde", "influxdb"]} +factorion-lib = {path = "../factorion-lib", version = "6.0.4", features = ["serde", "influxdb"]} reqwest = { version = "0.12.28", features = ["json", "native-tls"], default-features = false } serde = { version = "1.0.219", default-features = false, features = ["derive"] } serde_json = "1.0.145" diff --git a/factorion-lib/Cargo.toml b/factorion-lib/Cargo.toml index b66c8f8..8cdd3eb 100644 --- a/factorion-lib/Cargo.toml +++ b/factorion-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factorion-lib" -version = "6.0.3" +version = "6.0.4" edition = "2024" description = "A library used to create bots to recognize and calculate factorials and related concepts" license = "MIT" @@ -10,7 +10,7 @@ keywords = ["factorial", "termial", "bot", "math"] categories = ["mathematics", "web-programming", "parser-implementations"] [dependencies] -factorion-math = {path = "../factorion-math", version = "1.0.5"} +factorion-math = {path = "../factorion-math", version = "1.0.6"} serde = {version = "1.0", features = ["derive"], optional = true} serde_json = {version = "1.0", optional = true} concat-idents = "1.1.4" diff --git a/factorion-lib/src/calculation_tasks.rs b/factorion-lib/src/calculation_tasks.rs index f303f14..4f236a1 100644 --- a/factorion-lib/src/calculation_tasks.rs +++ b/factorion-lib/src/calculation_tasks.rs @@ -131,6 +131,12 @@ impl CalculationJob { ) -> Option { let prec = consts.float_precision; let calc_num = match num { + CalculationResult::ComplexInfinity => return Some(CalculationResult::ComplexInfinity), + Number::Float(num) | CalculationResult::Approximate(num, _) + if !num.as_float().is_finite() => + { + return Some(CalculationResult::ComplexInfinity); + } CalculationResult::Approximate(base, exponent) => { if exponent <= consts.integer_construction_limit { let x: Float = base.as_float() * Float::with_val(prec, 10).pow(&exponent); @@ -194,7 +200,6 @@ impl CalculationJob { CalculationResult::ApproximateDigitsTower(false, false, depth + 1, exponent) }); } - CalculationResult::ComplexInfinity => return Some(CalculationResult::ComplexInfinity), Number::Float(num) => match level { ..-1 => { // We don't support multitermials of decimals @@ -233,6 +238,20 @@ impl CalculationJob { } } }, + Number::Exact(num) if num.significant_bits() >= math::rug::float::exp_max() as u32 => { + let sig_bits = num.significant_bits(); + return Self::calculate_appropriate_factorial( + CalculationResult::Approximate( + (Float::with_val(consts.float_precision, num / consts.float_precision) + / (sig_bits - consts.float_precision)) + .into(), + sig_bits.into(), + ), + level, + negative, + consts, + ); + } Number::Exact(num) => num, }; if level > 0 { @@ -332,10 +351,11 @@ impl CalculationJob { } else if level < 0 { Some( if calc_num.significant_bits() > consts.upper_termial_approximation_limit { - let termial = math::approximate_termial_digits(calc_num, -level as u32, prec); + let termial = + math::approximate_termial_digits(calc_num, level.unsigned_abs(), prec); CalculationResult::ApproximateDigits(!negative.is_multiple_of(2), termial) - } else if calc_num > consts.upper_termial_limit { - let termial = math::approximate_termial(calc_num, -level as u32, prec); + } else if *calc_num.as_abs() > consts.upper_termial_limit { + let termial = math::approximate_termial(calc_num, level.unsigned_abs(), prec); CalculationResult::Approximate( ((termial.0 * if !negative.is_multiple_of(2) { -1 } else { 1 }) as Float) .into(), @@ -343,7 +363,7 @@ impl CalculationJob { ) } else { let termial = if level < -1 { - math::multitermial(calc_num, -level as u32) + math::multitermial(calc_num, level.unsigned_abs()) } else { math::termial(calc_num) }; diff --git a/factorion-lib/src/parse.rs b/factorion-lib/src/parse.rs index aba16d8..d76c625 100644 --- a/factorion-lib/src/parse.rs +++ b/factorion-lib/src/parse.rs @@ -15,6 +15,8 @@ pub mod recommended { pub static INTEGER_CONSTRUCTION_LIMIT: fn() -> Integer = || 200_000_000u128.into(); } +// NOTE: Most of these rely on being ascii (byte indxed) + const POI_STARTS: &[char] = &[ NEGATION, '!', // PREFIX_OPS @@ -167,7 +169,7 @@ pub fn parse( text = &text[position_of_interest..]; if text.starts_with(ESCAPE) { // Escapes - text = &text[1..]; + text = &text[ESCAPE.len_utf8()..]; let end = if text.starts_with(SPOILER_START) { 1 } else if text.starts_with(SPOILER_HTML_START) { @@ -195,7 +197,10 @@ pub fn parse( } end += e; // is escaped -> look further - if text[end.saturating_sub(1)..].starts_with(ESCAPE) { + let potential_escape = end.saturating_sub(ESCAPE.len_utf8()); + if text.is_char_boundary(potential_escape) + && text[end.saturating_sub(ESCAPE.len_utf8())..].starts_with(ESCAPE) + { end += 1; continue; } @@ -220,7 +225,10 @@ pub fn parse( } end += e; // is escaped -> look further - if text[end.saturating_sub(1)..].starts_with(ESCAPE) { + let potential_escape = end.saturating_sub(ESCAPE.len_utf8()); + if text.is_char_boundary(potential_escape) + && text[end.saturating_sub(ESCAPE.len_utf8())..].starts_with(ESCAPE) + { end += 1; continue; } @@ -248,11 +256,11 @@ pub fn parse( jobs.push(*job); } current_negative = 0; - text = &text[1..]; + text = &text[PAREN_START.len_utf8()..]; continue; } else if text.starts_with(PAREN_END) { // Paren End (5.) - text = &text[1..]; + text = &text[PAREN_END.len_utf8()..]; current_negative = 0; // Paren mismatch? let Some(step) = paren_steps.pop() else { @@ -388,7 +396,7 @@ pub fn parse( if text.starts_with(PAREN_START) { paren_steps.push((current_negative, Some(level), false)); current_negative = 0; - text = &text[1..]; + text = &text[PAREN_START.len_utf8()..]; } continue; }; @@ -708,7 +716,7 @@ fn parse_num_simple( part }; let decimal_part = if text.starts_with(locale.decimal) { - *text = &text[1..]; + *text = &text[locale.decimal.len_utf8()..]; let end = text .find(|c: char| (!c.is_numeric() && !SEPARATORS.contains(&c)) || c == locale.decimal) .unwrap_or(text.len()); @@ -828,6 +836,9 @@ fn parse_num_simple( } else { Integer::ONE.clone() }; + if divisor == 0 { + return Some(Number::ComplexInfinity); + } let integer_part = integer_part.replace(SEPARATORS, ""); let decimal_part = decimal_part.replace(SEPARATORS, ""); if exponent >= decimal_part.len() as i64 @@ -855,7 +866,7 @@ fn parse_num_simple( } else if x.is_finite() { Some(Number::Float(x.into())) } else { - None + Some(Number::ComplexInfinity) } } else { let x = Float::parse(format!("{integer_part}.{decimal_part}")).ok()?; @@ -864,7 +875,7 @@ fn parse_num_simple( let (b, e) = crate::math::adjust_approximate((x, exponent)); Some(Number::Approximate(b.into(), e)) } else { - None + Some(Number::ComplexInfinity) } } } diff --git a/factorion-math/Cargo.toml b/factorion-math/Cargo.toml index 30087a3..8c37df7 100644 --- a/factorion-math/Cargo.toml +++ b/factorion-math/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "factorion-math" -version = "1.0.5" +version = "1.0.6" edition = "2024" description = "The math (factorials and related functions) used by factorion" license = "MIT" diff --git a/factorion-math/src/lib.rs b/factorion-math/src/lib.rs index 9884072..e67f88a 100644 --- a/factorion-math/src/lib.rs +++ b/factorion-math/src/lib.rs @@ -15,7 +15,7 @@ pub fn factorial(n: u64, k: u32) -> Integer { /// The k-factorial of -n is the factorial of n-k times this factor (inf if None) pub fn negative_multifacorial_factor(n: Integer, k: i32) -> Option { let n = -n; - let rem = n.rem(2 * k); + let rem = n.rem(2 * (k as i64)); if rem == 0 { None } else if rem < k { @@ -271,6 +271,7 @@ pub fn approximate_termial_float(k: u32, n: Float) -> (Float, Integer) { let prec = n.prec(); let len: Integer = n .clone() + .abs() .log10() .to_integer_round(rug::float::Round::Down) .unwrap() @@ -278,7 +279,7 @@ pub fn approximate_termial_float(k: u32, n: Float) -> (Float, Integer) { let len_10 = Float::with_val(prec, 10).pow(len.clone()); let a = n.clone() / len_10.clone(); let b = (n + k) / len_10; - adjust_approximate(((a * b) / (2 * k), 2 * len)) + adjust_approximate(((a * b) / (2 * Float::with_val(prec, k)), 2 * len)) } /// The k-termial of x * 10^e as a * 10^b