From cafbf8ca6b4663ccd911a8a18b880d3e0d60a887 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Mon, 2 Feb 2026 22:32:37 +0900 Subject: [PATCH] bigint.c: fix memory leak in mpz_mul_sparse and bint_mul mpz_mul_sparse allocated temporary mpz_t variables (shifted, temp) that were leaked when an exception was raised (e.g., RangeError from shift width too large). bint_mul had the same issue with its output mpz_t z. Wrap both functions with MRB_ENSURE to guarantee cleanup runs regardless of exceptions, following the existing pattern used by mpz_mul_all_ones. Co-authored-by: Claude --- mrbgems/mruby-bigint/core/bigint.c | 75 ++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/mrbgems/mruby-bigint/core/bigint.c b/mrbgems/mruby-bigint/core/bigint.c index af85a5ddc..7d5b60a8a 100644 --- a/mrbgems/mruby-bigint/core/bigint.c +++ b/mrbgems/mruby-bigint/core/bigint.c @@ -2016,13 +2016,25 @@ mpz_sparse_p(mpz_t *x) * * O(k * n) where k = popcount, much faster than Karatsuba when k is small. */ -static void -mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) -{ - mpz_t shifted, temp; +struct mpz_mul_sparse_data { + mpz_ctx_t *ctx; + mpz_t *w; + mpz_t *sparse; + mpz_t *dense; + mpz_t shifted, temp; /* cleanup targets */ +}; - mpz_init(ctx, &shifted); - mpz_init(ctx, &temp); +static mrb_value +mpz_mul_sparse_body(mrb_state *mrb, void *userdata) +{ + struct mpz_mul_sparse_data *d = (struct mpz_mul_sparse_data *)userdata; + mpz_ctx_t *ctx = d->ctx; + mpz_t *w = d->w; + mpz_t *sparse = d->sparse; + mpz_t *dense = d->dense; + + mpz_init(ctx, &d->shifted); + mpz_init(ctx, &d->temp); zero(w); for (size_t i = 0; i < sparse->sz; i++) { @@ -2042,9 +2054,9 @@ mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) #endif /* Add dense << (base_bit + bit) to result */ - mpz_mul_2exp(ctx, &shifted, dense, base_bit + bit); - mpz_add(ctx, &temp, w, &shifted); - mpz_set(ctx, w, &temp); + mpz_mul_2exp(ctx, &d->shifted, dense, base_bit + bit); + mpz_add(ctx, &d->temp, w, &d->shifted); + mpz_set(ctx, w, &d->temp); /* Clear this bit */ limb &= limb - 1; @@ -2054,8 +2066,19 @@ mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) /* Handle sign */ if (sparse->sn < 0) w->sn = -w->sn; - mpz_clear(ctx, &shifted); - mpz_clear(ctx, &temp); + return mrb_nil_value(); +} + +static void +mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) +{ + struct mpz_mul_sparse_data d = {ctx, w, sparse, dense, {0,0,0}, {0,0,0}}; + mrb_value exc; + MRB_ENSURE(MPZ_MRB(ctx), exc, mpz_mul_sparse_body, &d) { + /* Cleanup always runs (mpz_clear is safe on zero-initialized mpz_t) */ + mpz_clear(ctx, &d.shifted); + mpz_clear(ctx, &d.temp); + } } /* @@ -5576,19 +5599,41 @@ mrb_bint_sub(mrb_state *mrb, mrb_value x, mrb_value y) return bint_norm(mrb, RBIGINT(x)); } +struct bint_mul_data { + mpz_ctx_t *ctx; + mpz_t *a; + mpz_t *b; + mpz_t z; /* cleanup target */ +}; + +static mrb_value +bint_mul_body(mrb_state *mrb, void *userdata) +{ + struct bint_mul_data *d = (struct bint_mul_data *)userdata; + mpz_init(d->ctx, &d->z); + mpz_mul(d->ctx, &d->z, d->a, d->b); + return mrb_nil_value(); +} + static struct RBigint* bint_mul(mrb_state *mrb, mrb_value x, mrb_value y) { - mpz_t a, b, z; + mpz_t a, b; y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(x), &a); bint_as_mpz(RBIGINT(y), &b); MPZ_CTX_INIT(mrb, ctx, pool); - mpz_init(ctx, &z); - mpz_mul(ctx, &z, &a, &b); - return bint_new(ctx, &z); + struct bint_mul_data d = {ctx, &a, &b, {0,0,0}}; + mrb_value exc; + MRB_ENSURE(mrb, exc, bint_mul_body, &d) { + /* On exception, cleanup z (mpz_clear is safe on zero-initialized mpz_t) */ + if (mrb->exc) { + mpz_clear(ctx, &d.z); + } + } + return bint_new(ctx, &d.z); } mrb_value