From 013f6cb9054735712cf84c32ee0db1e8f54048e5 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Wed, 11 Nov 2026 00:06:11 +0100 Subject: [PATCH v2] pgcrypto: reject invalid PGP options The PGP option parser used atoi() for integer options which would convert non-numeric options to 0. Since 0 is a legitimate input value for the PGP options this would mask invalid input. In most cases that was not a problem as 0 was the default, s2k-mode used 3 as the default however with 0 being a weaker option. Fix by using strtoint() to parse the input instead and reject invalid or out-of-range values. Author: Shihao Zhong Reported-by: Qifan Liu Discussion: https://postgr.es/m/19714-2c7439b39f73bde9@postgresql.org --- contrib/pgcrypto/expected/pgp-encrypt.out | 7 +++ contrib/pgcrypto/expected/pgp-encrypt_1.out | 7 +++ contrib/pgcrypto/pgp-pgsql.c | 57 +++++++++++++++------ contrib/pgcrypto/pgp.c | 3 +- contrib/pgcrypto/pgp.h | 3 ++ contrib/pgcrypto/sql/pgp-encrypt.sql | 5 ++ 6 files changed, 65 insertions(+), 17 deletions(-) diff --git a/contrib/pgcrypto/expected/pgp-encrypt.out b/contrib/pgcrypto/expected/pgp-encrypt.out index 50cd3f6daa0..a6ca7334079 100644 --- a/contrib/pgcrypto/expected/pgp-encrypt.out +++ b/contrib/pgcrypto/expected/pgp-encrypt.out @@ -120,6 +120,13 @@ NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712 Secret. (1 row) +-- integer options must be valid integers +select pgp_sym_encrypt('Secret.', 'key', 's2k-mode=not_a_number'); +ERROR: Illegal argument to function +select pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3x'); +ERROR: Illegal argument to function +select pgp_sym_encrypt('Secret.', 'key', 's2k-count=4294968320'); +ERROR: Illegal argument to function -- s2k digest change select pgp_sym_decrypt( pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'), diff --git a/contrib/pgcrypto/expected/pgp-encrypt_1.out b/contrib/pgcrypto/expected/pgp-encrypt_1.out index 36b1052809c..e828300f415 100644 --- a/contrib/pgcrypto/expected/pgp-encrypt_1.out +++ b/contrib/pgcrypto/expected/pgp-encrypt_1.out @@ -116,6 +116,13 @@ NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712 Secret. (1 row) +-- integer options must be valid integers +select pgp_sym_encrypt('Secret.', 'key', 's2k-mode=not_a_number'); +ERROR: Illegal argument to function +select pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3x'); +ERROR: Illegal argument to function +select pgp_sym_encrypt('Secret.', 'key', 's2k-count=4294968320'); +ERROR: Illegal argument to function -- s2k digest change select pgp_sym_decrypt( pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'), diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c index 05ef45fbe4c..7ba2834ab3e 100644 --- a/contrib/pgcrypto/pgp-pgsql.c +++ b/contrib/pgcrypto/pgp-pgsql.c @@ -164,6 +164,27 @@ show_debug(const char *msg) ereport(NOTICE, (errmsg("dbg: %s", msg))); } +/* + * Parse the value of an integer option. Don't use atoi() here, as it would + * silently turn a value like "foo" into 0, which for s2k-mode means no salt. + */ +static int +parse_int_arg(const char *val, int min, int max) +{ + char *endptr; + int result; + + errno = 0; + result = strtoint(val, &endptr, 10); + if (errno != 0 || endptr == val || *endptr != '\0') + px_THROW_ERROR(PXE_ARGUMENT_ERROR); + if (result < min || result > max) + px_THROW_ERROR(PXE_ARGUMENT_ERROR); + return result; +} + +#define PARSE_INT_AS_BOOL(a) parse_int_arg((a), 0, 1) + static int set_arg(PGP_Context *ctx, char *key, char *val, struct debug_expect *ex) @@ -173,34 +194,38 @@ set_arg(PGP_Context *ctx, char *key, char *val, if (strcmp(key, "cipher-algo") == 0) res = pgp_set_cipher_algo(ctx, val); else if (strcmp(key, "disable-mdc") == 0) - res = pgp_disable_mdc(ctx, atoi(val)); + res = pgp_disable_mdc(ctx, PARSE_INT_AS_BOOL(val)); else if (strcmp(key, "sess-key") == 0) - res = pgp_set_sess_key(ctx, atoi(val)); + res = pgp_set_sess_key(ctx, PARSE_INT_AS_BOOL(val)); else if (strcmp(key, "s2k-mode") == 0) - res = pgp_set_s2k_mode(ctx, atoi(val)); + res = pgp_set_s2k_mode(ctx, parse_int_arg(val, + PGP_S2K_SIMPLE, + PGP_S2K_ISALTED)); else if (strcmp(key, "s2k-count") == 0) - res = pgp_set_s2k_count(ctx, atoi(val)); + res = pgp_set_s2k_count(ctx, parse_int_arg(val, + PGP_S2K_COUNT_MIN, + PGP_S2K_COUNT_MAX)); else if (strcmp(key, "s2k-digest-algo") == 0) res = pgp_set_s2k_digest_algo(ctx, val); else if (strcmp(key, "s2k-cipher-algo") == 0) res = pgp_set_s2k_cipher_algo(ctx, val); else if (strcmp(key, "compress-algo") == 0) - res = pgp_set_compress_algo(ctx, atoi(val)); + res = pgp_set_compress_algo(ctx, parse_int_arg(val, PGP_COMPR_NONE, PGP_COMPR_ZLIB)); else if (strcmp(key, "compress-level") == 0) - res = pgp_set_compress_level(ctx, atoi(val)); + res = pgp_set_compress_level(ctx, parse_int_arg(val, 0, 9)); else if (strcmp(key, "convert-crlf") == 0) - res = pgp_set_convert_crlf(ctx, atoi(val)); + res = pgp_set_convert_crlf(ctx, PARSE_INT_AS_BOOL(val)); else if (strcmp(key, "unicode-mode") == 0) - res = pgp_set_unicode_mode(ctx, atoi(val)); + res = pgp_set_unicode_mode(ctx, PARSE_INT_AS_BOOL(val)); else if (strcmp(key, "ignore-cipher-failure") == 0) - res = pgp_set_ignore_cipher_failure(ctx, atoi(val)); + res = pgp_set_ignore_cipher_failure(ctx, PARSE_INT_AS_BOOL(val)); /* * The remaining options are for debugging/testing and are therefore not * documented in the user-facing docs. */ else if (ex != NULL && strcmp(key, "debug") == 0) - ex->debug = atoi(val); + ex->debug = PARSE_INT_AS_BOOL(val); else if (ex != NULL && strcmp(key, "expect-cipher-algo") == 0) { ex->expect = 1; @@ -209,22 +234,22 @@ set_arg(PGP_Context *ctx, char *key, char *val, else if (ex != NULL && strcmp(key, "expect-disable-mdc") == 0) { ex->expect = 1; - ex->disable_mdc = atoi(val); + ex->disable_mdc = PARSE_INT_AS_BOOL(val); } else if (ex != NULL && strcmp(key, "expect-sess-key") == 0) { ex->expect = 1; - ex->use_sess_key = atoi(val); + ex->use_sess_key = PARSE_INT_AS_BOOL(val); } else if (ex != NULL && strcmp(key, "expect-s2k-mode") == 0) { ex->expect = 1; - ex->s2k_mode = atoi(val); + ex->s2k_mode = PARSE_INT_AS_BOOL(val); } else if (ex != NULL && strcmp(key, "expect-s2k-count") == 0) { ex->expect = 1; - ex->s2k_count = atoi(val); + ex->s2k_count = parse_int_arg(val, PGP_S2K_COUNT_MIN, PGP_S2K_COUNT_MAX); } else if (ex != NULL && strcmp(key, "expect-s2k-digest-algo") == 0) { @@ -239,12 +264,12 @@ set_arg(PGP_Context *ctx, char *key, char *val, else if (ex != NULL && strcmp(key, "expect-compress-algo") == 0) { ex->expect = 1; - ex->compress_algo = atoi(val); + ex->compress_algo = parse_int_arg(val, PGP_COMPR_NONE, PGP_COMPR_ZLIB); } else if (ex != NULL && strcmp(key, "expect-unicode-mode") == 0) { ex->expect = 1; - ex->unicode_mode = atoi(val); + ex->unicode_mode = PARSE_INT_AS_BOOL(val); } else res = PXE_ARGUMENT_ERROR; diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c index e3c9e5fb394..1ea06addba8 100644 --- a/contrib/pgcrypto/pgp.c +++ b/contrib/pgcrypto/pgp.c @@ -264,7 +264,8 @@ pgp_set_s2k_mode(PGP_Context *ctx, int mode) int pgp_set_s2k_count(PGP_Context *ctx, int count) { - if (ctx->s2k_mode == PGP_S2K_ISALTED && count >= 1024 && count <= 65011712) + if (ctx->s2k_mode == PGP_S2K_ISALTED && + count >= PGP_S2K_COUNT_MIN && count <= PGP_S2K_COUNT_MAX) { ctx->s2k_count = count; return PXE_OK; diff --git a/contrib/pgcrypto/pgp.h b/contrib/pgcrypto/pgp.h index 3c4610608d1..b7ec6af2a26 100644 --- a/contrib/pgcrypto/pgp.h +++ b/contrib/pgcrypto/pgp.h @@ -114,6 +114,9 @@ enum PGP_DIGEST_TYPE #define PGP_MAX_DIGEST (512/8) #define PGP_S2K_SALT 8 +#define PGP_S2K_COUNT_MIN 1024 +#define PGP_S2K_COUNT_MAX 65011712 + typedef struct PGP_MPI PGP_MPI; typedef struct PGP_PubKey PGP_PubKey; typedef struct PGP_Context PGP_Context; diff --git a/contrib/pgcrypto/sql/pgp-encrypt.sql b/contrib/pgcrypto/sql/pgp-encrypt.sql index f67329c2c30..e806f5761af 100644 --- a/contrib/pgcrypto/sql/pgp-encrypt.sql +++ b/contrib/pgcrypto/sql/pgp-encrypt.sql @@ -62,6 +62,11 @@ select pgp_sym_decrypt( pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'), 'key', 'expect-s2k-count=65000000'); +-- integer options must be valid integers +select pgp_sym_encrypt('Secret.', 'key', 's2k-mode=not_a_number'); +select pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3x'); +select pgp_sym_encrypt('Secret.', 'key', 's2k-count=4294968320'); + -- s2k digest change select pgp_sym_decrypt( pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'), -- 2.39.3 (Apple Git-146)