commit 03ae6e414c8ea3a8b0572c0144a687dca1691348
parent 9dee32f283879f33ad216d676b60e8ee909e36de
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 9 Aug 2026 13:11:18 +0200
db: handle INT64_MIN negation and cap the rows returned per query
Negating the signed limit is undefined for INT64_MIN and the wrapped
value reached Postgres as a negative LIMIT; the /private/products,
/transfers, /incoming and /tokens handlers pass the client's limit
through unclamped, so this was remotely reachable.
Diffstat:
8 files changed, 90 insertions(+), 7 deletions(-)
diff --git a/src/backenddb/helper.h b/src/backenddb/helper.h
@@ -81,6 +81,30 @@ extern uint64_t TMH_PG_prep_gen_;
/**
+ * Convert the signed @a limit of a paginated listing into the row
+ * limit to pass to SQL. The sign selects the iteration direction and
+ * the magnitude the number of rows, but computing `-limit` directly is
+ * undefined behaviour for `INT64_MIN`, and the resulting value would
+ * reach Postgres as `LIMIT -9223372036854775808`, which errors out.
+ * `INT64_MIN` is therefore saturated to `INT64_MAX`.
+ *
+ * @param limit signed limit as given by the caller
+ * @return absolute value of @a limit, without signed overflow
+ */
+static inline uint64_t
+TALER_MERCHANTDB_abs_limit (int64_t limit)
+{
+ /* Postgres binds INT8 as a *signed* 64-bit integer, so a magnitude
+ above INT64_MAX would reach LIMIT as a negative number. */
+ if (INT64_MIN == limit)
+ return (uint64_t) INT64_MAX;
+ if (limit < 0)
+ return (uint64_t) -limit;
+ return (uint64_t) limit;
+}
+
+
+/**
* Prepares SQL statement @a sql under @a name for
* connection @a pg once.
* Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
diff --git a/src/backenddb/iterate_expected_transfers.c b/src/backenddb/iterate_expected_transfers.c
@@ -24,6 +24,14 @@
#include "helper.h"
#include <microhttpd.h> /* for HTTP status codes */
+
+/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
/**
* Closure for #lookup_expected_transfers_cb().
*/
@@ -161,7 +169,8 @@ TALER_MERCHANTDB_iterate_expected_transfers (
TALER_MERCHANTDB_IncomingCallback cb,
void *cb_cls)
{
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
bool by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
(! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
struct LookupExpectedTransfersContext ltc = {
diff --git a/src/backenddb/iterate_login_tokens.c b/src/backenddb/iterate_login_tokens.c
@@ -23,6 +23,14 @@
#include "merchant-database/iterate_login_tokens.h"
#include "helper.h"
+
+/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
/**
* Context used for TALER_MERCHANTDB_iterate_products().
*/
@@ -114,7 +122,8 @@ TALER_MERCHANTDB_iterate_login_tokens (
void *cb_cls)
{
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
struct LookupLoginTokensContext plc = {
.cb = cb,
.cb_cls = cb_cls,
diff --git a/src/backenddb/iterate_money_pots.c b/src/backenddb/iterate_money_pots.c
@@ -25,6 +25,13 @@
/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
+/**
* Context used for TALER_MERCHANTDB_iterate_money_pots().
*/
struct LookupMoneyPotsContext
@@ -113,7 +120,8 @@ TALER_MERCHANTDB_iterate_money_pots (
TALER_MERCHANTDB_MoneyPotsCallback cb,
void *cb_cls)
{
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
struct LookupMoneyPotsContext plc = {
.pg = pg,
.cb = cb,
diff --git a/src/backenddb/iterate_product_groups.c b/src/backenddb/iterate_product_groups.c
@@ -25,6 +25,13 @@
/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
+/**
* Context used for TALER_MERCHANTDB_iterate_product_groups().
*/
struct LookupProductGroupsContext
@@ -103,7 +110,8 @@ TALER_MERCHANTDB_iterate_product_groups (
TALER_MERCHANTDB_ProductGroupsCallback cb,
void *cb_cls)
{
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
struct LookupProductGroupsContext plc = {
.cb = cb,
.cb_cls = cb_cls,
diff --git a/src/backenddb/iterate_products.c b/src/backenddb/iterate_products.c
@@ -23,6 +23,14 @@
#include "merchant-database/iterate_products.h"
#include "helper.h"
+
+/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
/**
* Context used for TALER_MERCHANTDB_iterate_products().
*/
@@ -102,7 +110,8 @@ TALER_MERCHANTDB_iterate_products (
TALER_MERCHANTDB_ProductsCallback cb,
void *cb_cls)
{
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
struct LookupProductsContext plc = {
.cb = cb,
.cb_cls = cb_cls,
diff --git a/src/backenddb/iterate_reports.c b/src/backenddb/iterate_reports.c
@@ -25,6 +25,13 @@
/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
+/**
* Context used for TALER_MERCHANTDB_iterate_reports().
*/
struct SelectReportsContext
@@ -103,7 +110,8 @@ TALER_MERCHANTDB_iterate_reports (
TALER_MERCHANTDB_ReportsCallback cb,
void *cb_cls)
{
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
struct SelectReportsContext plc = {
.cb = cb,
.cb_cls = cb_cls,
diff --git a/src/backenddb/iterate_transfers.c b/src/backenddb/iterate_transfers.c
@@ -25,6 +25,13 @@
/**
+ * Hard upper bound on the number of records returned by a single
+ * call, regardless of the limit requested by the client.
+ */
+#define MAX_RECORDS 50000
+
+
+/**
* Closure for #lookup_transfers_cb().
*/
struct LookupTransfersContext
@@ -136,7 +143,8 @@ TALER_MERCHANTDB_iterate_transfers (
TALER_MERCHANTDB_TransferCallback cb,
void *cb_cls)
{
- uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+ uint64_t plimit = GNUNET_MIN ((uint64_t) MAX_RECORDS,
+ TALER_MERCHANTDB_abs_limit (limit));
bool by_time = ( (! GNUNET_TIME_absolute_is_never (before.abs_time)) ||
(! GNUNET_TIME_absolute_is_zero (after.abs_time)) );
struct LookupTransfersContext ltc = {