Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tj6xm-0069ic-Mh for pgsql-hackers@arkaria.postgresql.org; Sat, 15 Feb 2025 01:27:22 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1tj6xl-00FhCn-79 for pgsql-hackers@arkaria.postgresql.org; Sat, 15 Feb 2025 01:27:21 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tj6xk-00FhCf-SO for pgsql-hackers@lists.postgresql.org; Sat, 15 Feb 2025 01:27:20 +0000 Received: from mail-pl1-x634.google.com ([2607:f8b0:4864:20::634]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1tj6xh-000uFM-2u for pgsql-hackers@postgresql.org; Sat, 15 Feb 2025 01:27:19 +0000 Received: by mail-pl1-x634.google.com with SMTP id d9443c01a7336-219f8263ae0so47224485ad.0 for ; Fri, 14 Feb 2025 17:27:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=leadboat.com; s=google; t=1739582835; x=1740187635; darn=postgresql.org; h=user-agent:content-disposition:mime-version:message-id:subject:cc :to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=n2VOd6H43s4vgEXrWesva17rsxUPE6dhiFZ9IqBMfx4=; b=NWJo5Tmzdzzry60YAHrZYcPGFwz2bvYfoRuJsySSWXeezbrgXBThTVXovXTGPKmCsn jZ2W+sD7KNhmyGnUOU6gxkveRrz0Ras0isg9eh1CpJNylwwWvcxUmgDImOP3LNKCvX5n krc0jYXIv0RNC9C58GwH4XdXRx2+qWEP+xPEU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1739582835; x=1740187635; h=user-agent:content-disposition:mime-version:message-id:subject:cc :to:from:date:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=n2VOd6H43s4vgEXrWesva17rsxUPE6dhiFZ9IqBMfx4=; b=M6cZnizSLcfv+8TXTcTGN/eMoYzGwKmrJ3VJNA60SG1WnetBSylhS3UEYnrzvMTk0F 5J5tZkYIYV+yfalg4LIdT0fxUV1TSs4MbhSpait+KxQNvlmffndaoJKHX0uDUg5jaz6z O5ymcdabPEHDsgf9AQ8c4KTr4pH72zWD+cq5CNpxtFmC5cZyZiCsbFYlupOwERZGPOnY l+uzJzZ3KXz9nZ1oTBs7Gg6TTJ4bbgnyy3d5QM/iUWAiCrgkfq2P8IHGW5EilFo8oYL+ LxhkmxtmyEYHUFcaxIA8gz7rnNWRrD3CrwnxOT9QoLS7hdjL/5dM1fxjQ2QB5kiQZO6H w1LQ== X-Gm-Message-State: AOJu0Yz3edoAKZzKELuYFG71jDZoYGvvmDxnK14UqYbGNia1FrEY+9Yv lI44w/fie/NxZs9By86d5otmOhM+kyx8zwAHGf9401f3ISww8eBwZ7/LVELrOpucppMotLP82O8 = X-Gm-Gg: ASbGncshZbmXfBwXMXGLuhuZ780pWBDpzBUpFeYj3Pna/jAfg9ynbFdk3yf1jkRx/1g xcHK23W5F1DRMcDLNREyvfBqwQb6qfgF6gWXkDYAfp2YqjSeRCZ8UBQyVQOfY+AxAWlRNegiux9 dx/JFAsRMR5z8IAF4+Mxw5tsJJFEoqi8vEJI/xhtkA3lj5tYjScN63yeuA3TpIai46lJ++BTTZ6 Q1tiPVDMmShI9C6fWMEeuO+DL10DwcgzaNBoMXf2a9cCXmSKDvT3TLt0RnCYkMb1xfafvzY/5kf rz0Do9sgmC2ome09+w== X-Google-Smtp-Source: AGHT+IHPaojIxcd9I0S/MFVze+pzWE/HctnLBVeNvjj8fRwViwHvB9aWm1WkBFqal/hPKQ3pD+yJWw== X-Received: by 2002:a17:903:2f88:b0:216:2259:a4bd with SMTP id d9443c01a7336-221040eb63dmr22984225ad.52.1739582835216; Fri, 14 Feb 2025 17:27:15 -0800 (PST) Received: from rfd.leadboat.com ([2600:1702:a20:5750::46]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2fbf98f0d17sm5872348a91.21.2025.02.14.17.27.14 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 14 Feb 2025 17:27:14 -0800 (PST) Date: Fri, 14 Feb 2025 17:27:12 -0800 From: Noah Misch To: pgsql-hackers@postgresql.org Cc: andres@anarazel.de, pgsql@j-davis.com, tgl@sss.pgh.pa.us Subject: Decision by Monday: PQescapeString() vs. encoding violation Message-ID: <20250215012712.45@rfd.leadboat.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="Tv5V3cbrx1lBT+Iw" Content-Disposition: inline User-Agent: Mutt/2.2.12 (2023-09-09) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Tv5V3cbrx1lBT+Iw Content-Type: text/plain; charset=us-ascii Content-Disposition: inline The security team has a question below about how best to proceed with a recent behavior change. Commit 5dc1e42b4fa6a4434afa7d7cdcf0291351a7b873 for this week's CVE-2025-1094 changed how PQescapeString()[1] reacts to input that is not valid in the client encoding. Before that commit, the function would ignore encoding problems except at the end of the string. Now, it replaces the bad sequence up to the length implied by the first byte. For example, if UTF8 input has 0xc2 followed by an ASCII byte, the function removes both bytes. Jeff Davis reported to the security team that http://www.unicode.org/reports/tr36/#Ill-Formed_Subsequences forbids something like this, saying a UTF-8 converter "must not consume the [second byte] if it continues". (That's my summary, not his. He might reply to add color here.) While PQescapeString() is not a UTF-8 converter, that standard still felt to multiple security team members like a decent argument for removing only the invalid 0xc2, not the byte following it. UTF8 is the most important encoding, and other encodings tolerate this. Either way, the server will report an encoding error. The difference doesn't have functional consequences if one simply puts the function result in a query. The difference could matter for debugging or if applications are postprocessing the PQescapeString() result in some way. Postprocessing is not supported, but we'd still like to do the best thing for applications that may already be doing it. Security team members disagreed on whether next week's releases are the last reasonable chance to change this, or whether changing it in e.g. May would be reasonable. If applications make changes to cope with the new behavior, that could be an argument against further change. Question for all: would you switch to the "remove fewer bytes" behavior in next week's releases, switch later, or switch never? Why so? Please answer in the next 24hr if possible, given the little time until we wrap next week's releases on Monday. I regret the late notice. I'm attaching a WIP patch from Andres Freund. We may use it to adopt the "remove fewer bytes" behavior, if that's the decision. Thanks, nm [1] The commit changed other functions, but PQescapeString() is most interesting for this discussion. Others have ways to report errors, or they have reason to believe the input is already checked. New code should be using the others and checking the error indicator. --Tv5V3cbrx1lBT+Iw Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="v3-0002-Have-escape-functions-process-bytes-after-invalid.patch" From 6f0b93afb6a4ba1157482e674e71f56cd9c555c9 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 14 Feb 2025 18:31:15 -0500 Subject: [PATCH v3 2/2] Have escape functions process bytes after invalid multi-byte char Reviewed-by: Jeff Davis Backpatch: 13 --- src/fe_utils/string_utils.c | 40 ++++++++++++++++++---------------- src/interfaces/libpq/fe-exec.c | 17 ++++++++------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c index b6a7b197087..8621856fbc1 100644 --- a/src/fe_utils/string_utils.c +++ b/src/fe_utils/string_utils.c @@ -206,14 +206,13 @@ fmtIdEnc(const char *rawid, int encoding) * "skip" over quote characters, e.g. when parsing * character-by-character. * - * Replace the bytes corresponding to the invalid character - * with an invalid sequence, for the same reason as above. + * Replace the current byte with with an invalid sequence, for the + * same reason as above. * - * It would be a bit faster to verify the whole string the - * first time we encounter a set highbit, but this way we can - * replace just the invalid characters, which probably makes - * it easier for users to find the invalidly encoded portion - * of a larger string. + * It would be a bit faster to verify the whole string the first + * time we encounter a set highbit, but this way we can replace + * just the invalid byte, which probably makes it easier for users + * to find the invalidly encoded portion of a larger string. */ enlargePQExpBuffer(id_return, 2); pg_encoding_set_invalid(encoding, @@ -222,11 +221,13 @@ fmtIdEnc(const char *rawid, int encoding) id_return->data[id_return->len] = '\0'; /* - * Copy the rest of the string after the invalid multi-byte - * character. + * Handle the following bytes as if this byte didn't exist, + * that's safer in case the subsequent bytes contain + * characters that are significant for the caller (e.g. '>' in + * html). */ - remaining -= charlen; - cp += charlen; + remaining -= 1; + cp += 1; } else { @@ -421,23 +422,24 @@ appendStringLiteral(PQExpBuffer buf, const char *str, * over quote characters, e.g. when parsing * character-by-character. * - * Replace the bytes corresponding to the invalid character with - * an invalid sequence, for the same reason as above. + * Replace the current byte with with an invalid sequence, for the + * same reason as above. * * It would be a bit faster to verify the whole string the first * time we encounter a set highbit, but this way we can replace - * just the invalid characters, which probably makes it easier for - * users to find the invalidly encoded portion of a larger string. + * just the invalid byte, which probably makes it easier for users + * to find the invalidly encoded portion of a larger string. */ pg_encoding_set_invalid(encoding, target); target += 2; - remaining -= charlen; /* - * Copy the rest of the string after the invalid multi-byte - * character. + * Handle the following bytes as if this byte didn't exist, that's + * safer in case the subsequent bytes contain important characters + * for the caller (e.g. '>' in html). */ - source += charlen; + remaining -= 1; + source += 1; } else { diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 120d4d032ec..53b906f9562 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -4139,13 +4139,13 @@ PQescapeStringInternal(PGconn *conn, * over quote characters, e.g. when parsing * character-by-character. * - * Replace the bytes corresponding to the invalid character with - * an invalid sequence, for the same reason as above. + * Replace the current byte with with an invalid sequence, for the + * same reason as above. * * It would be a bit faster to verify the whole string the first * time we encounter a set highbit, but this way we can replace - * just the invalid characters, which probably makes it easier for - * users to find the invalidly encoded portion of a larger string. + * just the invalid byte, which probably makes it easier for users + * to find the invalidly encoded portion of a larger string. */ if (error) *error = 1; @@ -4154,13 +4154,14 @@ PQescapeStringInternal(PGconn *conn, pg_encoding_set_invalid(encoding, target); target += 2; - remaining -= charlen; /* - * Copy the rest of the string after the invalid multi-byte - * character. + * Handle the following bytes as if this byte didn't exist, that's + * safer in case the subsequent bytes contain important characters + * for the caller (e.g. '>' in html). */ - source += charlen; + remaining -= 1; + source += 1; } else { -- 2.48.1.76.g4e746b1a31.dirty --Tv5V3cbrx1lBT+Iw--