From 80d364b55eb1d29c45a462d9a1e643bd09cb00fc Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Thu, 12 Nov 2026 23:44:09 +0100 Subject: [PATCH v19-2] Reject invalid base64 encoded values with data after padding When decoding Base64 encoded values the code accepted input after the padding bytes, which should have resulted in an error since the input isn't valid Base64. To avoid problems with data at rest in a minor version, this fix is not backpatched. The fixes for base64url are however backpatched to 19 since base64url is a new feature in 19 and no existing data exist. The bug was independently reported by Qifan Liu and Atish Prakash. Author: Shihao Zhong Reviewed-by: Daniel Gustafsson Reviewed-by: Masahiko Sawada Reported-by: Qifan Liu Reported-by: Atish Prakash <125atishprak@gmail.com> Discussion: https://postgr.es/m/19702-9ed4a131fcfadb9d@postgresql.org Discussion: https://postgr.es/m/19843-ebef332578fa09da@postgresql.org Backpatch-through: 19 --- src/backend/utils/adt/encode.c | 13 ++++++++----- src/test/regress/expected/strings.out | 10 ++++++++++ src/test/regress/sql/strings.sql | 5 +++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c index b9d4f2811d7..23c0570060f 100644 --- a/src/backend/utils/adt/encode.c +++ b/src/backend/utils/adt/encode.c @@ -504,7 +504,10 @@ pg_base64url_encode(const char *src, size_t len, char *dst) * pg_base64_decode_internal * * Helper for decoding base64 or base64url. When url is passed as true the - * input will be assumed to be encoded using base64url. + * input will be assumed to be encoded using base64url. Decoding base64url + * will check for trailing data after the padding which isn't allowed per the + * specification. Future versions of PostgreSQL will also reject trailing data + * in regular base64. */ static uint64 pg_base64_decode_internal(const char *src, size_t len, char *dst, bool url) @@ -537,7 +540,7 @@ pg_base64_decode_internal(const char *src, size_t len, char *dst, bool url) if (c == '=') { /* end sequence */ - if (!end) + if ((!url && !end) || (url && (!end || pos != 3))) { if (pos == 2) end = 1; @@ -556,7 +559,7 @@ pg_base64_decode_internal(const char *src, size_t len, char *dst, bool url) else { b = -1; - if (c > 0 && c < 127) + if ((!url && c > 0 && c < 127) || (url && c > 0 && c < 127 && !end)) b = b64lookup[(unsigned char) c]; if (b < 0) { @@ -583,12 +586,12 @@ pg_base64_decode_internal(const char *src, size_t len, char *dst, bool url) } } - if (url && pos == 2) + if (url && !end && pos == 2) { buf <<= 12; *p++ = (buf >> 16) & 0xFF; } - else if (url && pos == 3) + else if (url && !end && pos == 3) { buf <<= 6; *p++ = (buf >> 16) & 0xFF; diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index 313c5039465..cc9520943cb 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -2585,6 +2585,16 @@ SELECT decode(encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, \x1234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef00011234567890abcdef0001 (1 row) +-- nothing but whitespace may follow base64 padding +SELECT decode('YQ=', 'base64url'); -- error +ERROR: invalid base64url end sequence +HINT: Input data is missing padding, is truncated, or is otherwise corrupted. +SELECT decode('YQ===', 'base64url'); -- error +ERROR: unexpected "=" while decoding base64url sequence +SELECT decode('YQ=A', 'base64url'); -- error +ERROR: invalid symbol "A" found while decoding base64url sequence +SELECT decode('YQ==YQ==', 'base64url'); -- error +ERROR: invalid symbol "Y" found while decoding base64url sequence SELECT encode('\x1234567890abcdef00', 'escape'); encode ----------------------------- diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index 38946e8954d..3d92d46fc70 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -827,6 +827,11 @@ SELECT decode('1234567890abcdef00', 'hex'); SELECT encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, 'base64'); SELECT decode(encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, 'base64'), 'base64'); +-- nothing but whitespace may follow base64 padding +SELECT decode('YQ=', 'base64url'); -- error +SELECT decode('YQ===', 'base64url'); -- error +SELECT decode('YQ=A', 'base64url'); -- error +SELECT decode('YQ==YQ==', 'base64url'); -- error SELECT encode('\x1234567890abcdef00', 'escape'); SELECT decode(encode('\x1234567890abcdef00', 'escape'), 'escape'); -- 2.39.3 (Apple Git-146)