merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 03c6aab2ec52b1ff413eb305c233a69e07df961c
parent 74292cba96c89561d4b6f7fa7076143153efde93
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 12 Jul 2026 09:42:58 +0200

bound VLA stack arrays

Diffstat:
Msrc/lib/merchant_api_get-private-kyc.c | 7+++++++
Msrc/lib/merchant_api_get-private-statistics-amount-SLUG.c | 6++++++
Msrc/lib/merchant_api_post-orders-ORDER_ID-pay.c | 68+++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/lib/merchant_api_post-private-orders.c | 11++++++++++-
4 files changed, 66 insertions(+), 26 deletions(-)

diff --git a/src/lib/merchant_api_get-private-kyc.c b/src/lib/merchant_api_get-private-kyc.c @@ -183,6 +183,13 @@ parse_kyc (struct TALER_MERCHANT_GetPrivateKycHandle *kyc, } num_limits += json_array_size (jlimits); num_kycauths += json_array_size (jkycauths); + if ( (num_limits > MAX_KYC) || + (num_kycauths > MAX_KYC) ) + { + /* Bound the stack VLAs declared below by an untrusted response. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } { diff --git a/src/lib/merchant_api_get-private-statistics-amount-SLUG.c b/src/lib/merchant_api_get-private-statistics-amount-SLUG.c @@ -162,6 +162,12 @@ parse_intervals_and_buckets_amt ( } total_amounts += json_array_size (amounts_arr); } + if (total_amounts > MAX_STATISTICS * (size_t) MAX_STATISTICS) + { + /* Bound the stack VLA declared below by an untrusted response. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } { struct TALER_Amount glob_amt_arr[GNUNET_NZL (total_amounts)]; diff --git a/src/lib/merchant_api_post-orders-ORDER_ID-pay.c b/src/lib/merchant_api_post-orders-ORDER_ID-pay.c @@ -38,6 +38,13 @@ #include <donau/donau_json_lib.h> /** + * Maximum number of exchange base URLs we accept in an (untrusted) + * response before considering it malformed. Bounds the stack VLA used + * to parse the array. + */ +#define MAX_EXCHANGE_BASE_URLS 1024 + +/** * Handle for a POST /orders/$ORDER_ID/pay operation. */ struct TALER_MERCHANT_PostOrdersPayHandle @@ -414,36 +421,47 @@ handle_pay_finished (void *cls, } { size_t alen = json_array_size (ebus); - const char *ebua[GNUNET_NZL (alen)]; - size_t idx; - json_t *jebu; - bool ok = true; - GNUNET_assert (alen <= UINT_MAX); - json_array_foreach (ebus, idx, jebu) + if (alen > MAX_EXCHANGE_BASE_URLS) + { + /* Bound the stack VLA below by an untrusted response. */ + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "too many exchange_base_urls in response"; + break; + } { - ebua[idx] = json_string_value (jebu); - if (NULL == ebua[idx]) + const char *ebua[GNUNET_NZL (alen)]; + size_t idx; + json_t *jebu; + bool ok = true; + + json_array_foreach (ebus, idx, jebu) { - GNUNET_break_op (0); - pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - pr.hr.http_status = 0; - pr.hr.hint - = "non-string value in exchange_base_urls in response"; - ok = false; - break; + ebua[idx] = json_string_value (jebu); + if (NULL == ebua[idx]) + { + GNUNET_break_op (0); + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint + = "non-string value in exchange_base_urls in response"; + ok = false; + break; + } } + if (! ok) + break; + pr.details.unavailable_for_legal_reasons.num_exchanges + = (unsigned int) alen; + pr.details.unavailable_for_legal_reasons.exchanges + = ebua; + poph->cb (poph->cb_cls, + &pr); + TALER_MERCHANT_post_orders_pay_cancel (poph); + return; } - if (! ok) - break; - pr.details.unavailable_for_legal_reasons.num_exchanges - = (unsigned int) alen; - pr.details.unavailable_for_legal_reasons.exchanges - = ebua; - poph->cb (poph->cb_cls, - &pr); - TALER_MERCHANT_post_orders_pay_cancel (poph); - return; } } break; diff --git a/src/lib/merchant_api_post-private-orders.c b/src/lib/merchant_api_post-private-orders.c @@ -33,6 +33,14 @@ /** + * Maximum number of exchange rejections we allow in a response before + * we consider the (untrusted) reply malformed. Bounds the stack VLA + * used to parse the array. + */ +#define MAX_EXCHANGE_REJECTIONS 1024 + + +/** * Handle for a POST /private/orders operation. */ struct TALER_MERCHANT_PostPrivateOrdersHandle @@ -284,7 +292,8 @@ handle_post_orders_finished (void *cls, { unsigned int rej_len = (unsigned int) json_array_size (jer); - if (json_array_size (jer) == (size_t) rej_len) + if ( (json_array_size (jer) == (size_t) rej_len) && + (rej_len <= MAX_EXCHANGE_REJECTIONS) ) { struct TALER_MERCHANT_ExchangeRejectionDetail rejs[ GNUNET_NZL (rej_len)];