pg.ddx.io  pgsql-bugs@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Michael Paquier <michael@paquier.xyz>
To: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Cc: tgl@sss.pgh.pa.us
Cc: jeff.janes@gmail.com
Cc: frank.gagnepain@intm.fr
Cc: pgsql-bugs@lists.postgresql.org
Subject: Re: BUG #16476: pgp_sym_encrypt_bytea with compress-level=6 : Wrong key or corrupt data
Date: Thu, 25 Jun 2020 15:45:41 +0900
Message-ID: <20200625064541.GH130132@paquier.xyz> (raw)
In-Reply-To: <20200624075921.GC130132@paquier.xyz>
References: <CAKPRHzJ-dgYbTfNLgbiMR8tUdjpd9=HTHqAcNvSUd5uJCV6M6Q@mail.gmail.com>
	<20200612.105131.63288858615945654.horikyota.ntt@gmail.com>
	<608105.1591927045@sss.pgh.pa.us>
	<20200612.145412.475791851624925277.horikyota.ntt@gmail.com>
	<20200624075921.GC130132@paquier.xyz>

On Wed, Jun 24, 2020 at 04:59:21PM +0900, Michael Paquier wrote:
> FYI, I have begun looking at this report, and I am reviewing the
> proposed patch.

Okay.  First I found the test case you added a bit hard to parse, so I
have refactored it with a CTE in charge of building the random string,
with the main query doing the compression/decompression and a check
making sure that the original string and the result match.  I quite
liked the logic with lpad() to append zeros if the computation with
random()*256 returned a result less than 16, as well as the use of
string_agg() for that purpose to build the string.  I have also
switched the second arguments of the functions to just use 'key', for
readability.  Using a random string sounds good to me here.  It could
always be possible that we finish with something less random, causing
it to to become compressed but I'll believe in the rule of chaos
here.

Then, the fix you are proposing is to simply make sure that all the
input from the source stream is properly consumed even after the zlib
stream has ended in this corner case thanks to pullf_read(), and that
sounds good to me.  However, I had an idea slightly different than
yours, consisting of simply reading the contents of the source before
checking if there is any available in the decompressed buffer (the
check on buf_data before the goto restart step).  That makes the fix a
bit simpler, without changing the logic.

I am also attaching an extra script I used to validate this stuff
based on the regression test of the patch, that has allowed me to
check the logic for random strings up to 33kB, like that for example
(this one took 8 mins on my laptop):
select count(test_crypto(len)) from generate_series(0, 33000) as len;

The inputs and outputs perfectly matched in all my tests, with the
16kB string being the only one failing in the range I have tested on
HEAD, test passing with the patch of course.  One or more extra pairs
of eyes is welcome, so please feel free to look at the version
attached.

Thanks,
--
Michael

Attachments:

  crypto.sql (application/x-sql, 854B) download

pgcrypto-zlib-v4.patch (text/x-diff, 3.5K) download diff --git a/contrib/pgcrypto/expected/pgp-compression.out b/contrib/pgcrypto/expected/pgp-compression.out index 32b350b8fe..d4c57feba3 100644 --- a/contrib/pgcrypto/expected/pgp-compression.out +++ b/contrib/pgcrypto/expected/pgp-compression.out @@ -48,3 +48,33 @@ select pgp_sym_decrypt( Secret message (1 row) +-- check corner case involving an input string of 16kB, as per bug #16476. +SELECT setseed(0); + setseed +--------- + +(1 row) + +WITH random_string AS +( + -- This generates a random string of 16366 bytes. This is chosen + -- as random so that it does not get compressed, and the decompression + -- would work on a string with the same length as the origin, making the + -- test behavior more predictible. lpad() ensures that the generated + -- hexadecimal value is completed by extra zero characters if random() + -- has generated a value strictly lower than 16. + SELECT string_agg(decode(lpad(to_hex((random()*256)::int), 2, '0'), 'hex'), '') as bytes + FROM generate_series(0, 16365) +) +SELECT bytes = + pgp_sym_decrypt_bytea( + pgp_sym_encrypt_bytea(bytes, 'key', + 'compress-algo=1,compress-level=1'), + 'key', 'expect-compress-algo=1') + AS is_same + FROM random_string; + is_same +--------- + t +(1 row) + diff --git a/contrib/pgcrypto/pgp-compress.c b/contrib/pgcrypto/pgp-compress.c index 0505bdee92..a37595dcc9 100644 --- a/contrib/pgcrypto/pgp-compress.c +++ b/contrib/pgcrypto/pgp-compress.c @@ -243,6 +243,20 @@ decompress_read(void *priv, PullFilter *src, int len, struct DecomprData *dec = priv; restart: + if (dec->stream.avail_in == 0) + { + uint8 *tmp; + + res = pullf_read(src, 8192, &tmp); + if (res < 0) + return res; + + Assert(res != Z_STREAM_END || dec->buf_data > 0); + + dec->stream.next_in = tmp; + dec->stream.avail_in = res; + } + if (dec->buf_data > 0) { if (len > dec->buf_data) @@ -256,17 +270,6 @@ restart: if (dec->eof) return 0; - if (dec->stream.avail_in == 0) - { - uint8 *tmp; - - res = pullf_read(src, 8192, &tmp); - if (res < 0) - return res; - dec->stream.next_in = tmp; - dec->stream.avail_in = res; - } - dec->stream.next_out = dec->buf; dec->stream.avail_out = dec->buf_len; dec->pos = dec->buf; diff --git a/contrib/pgcrypto/sql/pgp-compression.sql b/contrib/pgcrypto/sql/pgp-compression.sql index ca9ee1fc00..87c59c6cab 100644 --- a/contrib/pgcrypto/sql/pgp-compression.sql +++ b/contrib/pgcrypto/sql/pgp-compression.sql @@ -28,3 +28,24 @@ select pgp_sym_decrypt( pgp_sym_encrypt('Secret message', 'key', 'compress-algo=2, compress-level=0'), 'key', 'expect-compress-algo=0'); + +-- check corner case involving an input string of 16kB, as per bug #16476. +SELECT setseed(0); +WITH random_string AS +( + -- This generates a random string of 16366 bytes. This is chosen + -- as random so that it does not get compressed, and the decompression + -- would work on a string with the same length as the origin, making the + -- test behavior more predictible. lpad() ensures that the generated + -- hexadecimal value is completed by extra zero characters if random() + -- has generated a value strictly lower than 16. + SELECT string_agg(decode(lpad(to_hex((random()*256)::int), 2, '0'), 'hex'), '') as bytes + FROM generate_series(0, 16365) +) +SELECT bytes = + pgp_sym_decrypt_bytea( + pgp_sym_encrypt_bytea(bytes, 'key', + 'compress-algo=1,compress-level=1'), + 'key', 'expect-compress-algo=1') + AS is_same + FROM random_string;
signature.asc (application/pgp-signature, 832B) download

view thread (10+ messages)

Message-ID: <20200625064541.GH130132@paquier.xyz>
Permalink:  ../20200625064541.GH130132@paquier.xyz/
Also on:    postgresql.org/message-id/20200625064541.GH130132@paquier.xyz

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-bugs@postgresql.org
  Cc: michael@paquier.xyz, horikyota.ntt@gmail.com, tgl@sss.pgh.pa.us, jeff.janes@gmail.com, frank.gagnepain@intm.fr, pgsql-bugs@lists.postgresql.org
  Subject: Re: BUG #16476: pgp_sym_encrypt_bytea with compress-level=6 : Wrong key or corrupt data
  In-Reply-To: <20200625064541.GH130132@paquier.xyz>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox