postgres.git / summary / log / commit / refs

commit    8a31ffc2d4cc5e2f3fb42b6a41b7db802716615d
Author:   Álvaro Herrera <alvherre@kurilemu.de>
Commit:   Noah Misch <noah@leadboat.com>
Date:     Mon Aug 10 13:38:11 2026 +0000
First released in: 18.6

    pg_stat_statements: Fix buffer overflow with query normalization
    
    Since commit 62d712ecfd94, pg_stat_statements has been underestimating
    the size of the result buffer possible for a normalized query, in cases
    where the query includes many squashable lists, causing the normalized
    query to write past the allocated area.
    
    The allocated buffer size forgot to account for the comment appended in
    a squashable list, "/*, ... */".  Instead of trying to track down
    precisely how much space we need, fix by switch to using an expansible
    StringInfo.  This not only fixes the bug, but it also makes the code
    simpler to follow.
    
    Author: Álvaro Herrera <alvherre@kurilemu.de>
    Reported-by: Sajeeb Lohani with TrendAI Zero Day Initiative
    Reported-by: Yuelin Wang <3020001251@tju.edu.cn>
    Diagnosed-by: Michaël Paquier <michael@paquier.xyz>
    Backpatch-through: 18
    Security: CVE-2026-14676
    Discussion: https://postgr.es/m/19528-7290dd7e6f7dcc22@postgresql.org


contrib/pg_stat_statements/pg_stat_statements.c | 45 ++++++++++--------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 1c07acf231e..44ada5a0acf 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2812,33 +2812,27 @@ static char * generate_normalized_query(JumbleState *jstate, const char *query, int query_loc, int *query_len_p) { - char *norm_query; + StringInfoData norm_query; int query_len = *query_len_p; - int norm_query_buflen, /* Space allowed for norm_query */ - len_to_wrt, /* Length (in bytes) to write */ + int len_to_wrt, /* Length (in bytes) to write */ quer_loc = 0, /* Source query byte location */ - n_quer_loc = 0, /* Normalized query byte location */ last_off = 0, /* Offset from start for previous tok */ last_tok_len = 0; /* Length (in bytes) of that tok */ int num_constants_replaced = 0; /* - * Get constants' lengths (core system only gives us locations). Note - * this also ensures the items are sorted by location. + * Our output buffer is an expansible StringInfo, but avoid enlarging it + * in most cases by reserving extra space for each constant location. */ - fill_in_constant_lengths(jstate, query, query_loc); + Assert(jstate->clocations_count > 0); + initStringInfoExt(&norm_query, query_len + jstate->clocations_count * 10); /* - * Allow for $n symbols to be longer than the constants they replace. - * Constants must take at least one byte in text form, while a $n symbol - * certainly isn't more than 11 bytes, even if n reaches INT_MAX. We - * could refine that limit based on the max value of n for the current - * query, but it hardly seems worth any extra effort to do so. + * Determine constants' lengths (core system only gives us locations), and + * return a sorted copy of jstate's LocationLen data with lengths filled + * in. */ - norm_query_buflen = query_len + jstate->clocations_count * 10; - - /* Allocate result buffer */ - norm_query = palloc(norm_query_buflen + 1); + fill_in_constant_lengths(jstate, query, query_loc); for (int i = 0; i < jstate->clocations_count; i++) { @@ -2869,17 +2863,16 @@ generate_normalized_query(JumbleState *jstate, const char *query, len_to_wrt = off - last_off; len_to_wrt -= last_tok_len; Assert(len_to_wrt >= 0); - memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt); - n_quer_loc += len_to_wrt; + appendBinaryStringInfo(&norm_query, query + quer_loc, len_to_wrt); /* * And insert a param symbol in place of the constant token; and, if * we have a squashable list, insert a placeholder comment starting * from the list's second value. */ - n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d%s", - num_constants_replaced + 1 + jstate->highest_extern_param_id, - jstate->clocations[i].squashed ? " /*, ... */" : ""); + appendStringInfo(&norm_query, "$%d%s", + num_constants_replaced + 1 + jstate->highest_extern_param_id, + jstate->clocations[i].squashed ? " /*, ... */" : ""); num_constants_replaced++; /* move forward */ @@ -2895,14 +2888,10 @@ generate_normalized_query(JumbleState *jstate, const char *query, len_to_wrt = query_len - quer_loc; Assert(len_to_wrt >= 0); - memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt); - n_quer_loc += len_to_wrt; - - Assert(n_quer_loc <= norm_query_buflen); - norm_query[n_quer_loc] = '\0'; + appendBinaryStringInfo(&norm_query, query + quer_loc, len_to_wrt); - *query_len_p = n_quer_loc; - return norm_query; + *query_len_p = norm_query.len; + return norm_query.data; } /* [parent: a1fa24127d6a]