Heap buffer overflow in PostgreSQL's pgcrypto extension when processing a malicious "Public-Key Encrypted Session Key" (OpenPGP tag 1) packet. An attacker who can supply ciphertext to pgp_pub_decrypt() can trigger a copy of an oversized session key into a fixed 32-byte buffer, corrupting heap memory.
Fixed in PostgreSQL 18.2, 17.8, 16.12, 15.16, 14.21 (patch date: 2026-02-12). All earlier versions of these branches (and equivalent pre-18.2/17.8/16.12/15.16/14.21) are affected when the pgcrypto extension is used with pgp_pub_decrypt().
In contrib/pgcrypto/pgp-pubdec.c, the function pgp_parse_pubenc_sesskey() handles tag 1 packets. After RSA-decrypting the payload, it interprets the plaintext as: 1 byte (symmetric algorithm) + N bytes (session key) + 2 bytes (checksum). It sets sess_key_len = msglen - 3 and copies that many bytes into ctx->sess_key with memcpy(). The buffer ctx->sess_key has size PGP_MAX_KEY (32 bytes). There is no check that sess_key_len <= 32, so an attacker who controls the ciphertext can cause a heap buffer overflow.
From contrib/pgcrypto/pgp-pubdec.c, function pgp_parse_pubenc_sesskey():
/*
* extract message
*/
msg = check_eme_pkcs1_v15(m->data, m->bytes);
if (msg == NULL)
{
px_debug("check_eme_pkcs1_v15 failed");
res = PXE_PGP_WRONG_KEY;
goto out;
}
msglen = m->bytes - (msg - m->data);
res = control_cksum(msg, msglen);
if (res < 0)
goto out;
/*
* got sesskey
*/
ctx->cipher_algo = *msg;
ctx->sess_key_len = msglen - 3;
memcpy(ctx->sess_key, msg + 1, ctx->sess_key_len);The length msglen - 3 is used directly as the copy size; if it is greater than 32, memcpy writes past the end of sess_key.
The fix adds a bounds check before the copy and returns a dedicated error when the session key is too long:
res = control_cksum(msg, msglen);
if (res < 0)
goto out;
sess_key_len = msglen - 3;
if (sess_key_len > PGP_MAX_KEY)
{
px_debug("incorrect session key length=%u", sess_key_len);
res = PXE_PGP_KEY_TOO_BIG;
goto out;
}
/*
* got sesskey
*/
ctx->cipher_algo = *msg;
ctx->sess_key_len = sess_key_len;
memcpy(ctx->sess_key, msg + 1, ctx->sess_key_len);The same logic appears in 18.2 and other patched branches.
The destination buffer and its size are defined in contrib/pgcrypto/pgp.h:
#define PGP_MAX_KEY (256/8) /*
* read or generated data
*/
uint8 sess_key[PGP_MAX_KEY];
unsigned sess_key_len;So sess_key is 32 bytes; any copy of more than 32 bytes overwrites sess_key_len and whatever lies after the struct on the heap.
This repository contains a Python script that:
- Generates an RSA PGP key pair (4096 or 8192 bits, via
--rsa). - Builds a malicious secret message: 1 byte algorithm + N bytes "session key" + 2 bytes checksum, with N chosen to fill the RSA block (498 for RSA 4096, 1010 for RSA 8192).
- Pads it with PKCS#1 v1.5 and encrypts it with the public key.
- Wraps the ciphertext in a valid PGP tag 1 packet (Public-Key Encrypted Session Key) and appends a minimal tag 9 (Symmetrically Encrypted Data) so the parser continues.
- Calls
pgp_pub_decrypt(packet, secret_key)against a PostgreSQL server. On a vulnerable build, the server decrypts, setssess_key_lento N, and performsmemcpy(ctx->sess_key, msg+1, N)— writing N bytes into a 32-byte buffer and corrupting the heap.
Optional payload and offset options let you control the bytes written and sweep offsets to probe for crashes.
Requirements: Python 3, psycopg2-binary, pgpy, cryptography.
pip install psycopg2-binary pgpy cryptographyRun against local PostgreSQL (default RSA 4096):
python3 PoC.pyUse RSA 8192 for a larger overflow (1010 bytes instead of 498):
python3 PoC.py --rsa 8192Custom payload and offset (e.g. to sweep for crashes):
python3 PoC.py --payload-hex "deadbeef" --offset 100
python3 PoC.py --payload-file payload.bin --offset 0
# Sweep offsets: for i in $(seq 0 490); do python3 PoC.py --payload-hex "..." --offset $i; doneOnly print SQL (no pgpy; you must supply the matching secret key when running the SQL):
python3 PoC.py --sql-onlyConnection options: --host, -p / --port, -u / --user, -d / --db, -w / --password.
The diffs/ folder contains the upstream patch for this vulnerability (PostgreSQL 17.7 → 17.8). See diffs/pgp-pubdec.c.patch and diffs/README.txt.
This PoC is for authorized security research and testing only. Do not use it against systems you are not permitted to test. The authors are not responsible for misuse.