Commit 2c0cdc81 authored by Tom Lane's avatar Tom Lane

Extensive code review for GSSAPI encryption mechanism.

Fix assorted bugs in handling of non-blocking I/O when using GSSAPI
encryption.  The encryption layer could return the wrong status
information to its caller, resulting in effectively dropping some data
(or possibly in aborting a not-broken connection), or in a "livelock"
situation where data remains to be sent but the upper layers think
transmission is done and just go to sleep.  There were multiple small
thinkos contributing to that, as well as one big one (failure to think
through what to do when a send fails after having already transmitted
data).  Note that these errors could cause failures whether the client
application asked for non-blocking I/O or not, since both libpq and
the backend always run things in non-block mode at this level.

Also get rid of use of static variables for GSSAPI inside libpq;
that's entirely not okay given that multiple connections could be
open at once inside a single client process.

Also adjust a bunch of random small discrepancies between the frontend
and backend versions of the send/receive functions -- except for error
handling, they should be identical, and now they are.

Also extend the Kerberos TAP tests to exercise cases where nontrivial
amounts of data need to be pushed through encryption.  Before, those
tests didn't provide any useful coverage at all for the cases of
interest here.  (They still might not, depending on timing, but at
least there's a chance.)

Per complaint from pmc@citylink and subsequent investigation.
Back-patch to v12 where this code was introduced.

Discussion: https://postgr.es/m/20200109181822.GA74698@gate.oper.dinoex.org
parent c67a55da
This diff is collapsed.
......@@ -462,7 +462,7 @@ pqDropConnection(PGconn *conn, bool flushInput)
/* Always discard any unsent data */
conn->outCount = 0;
/* Free authentication state */
/* Free authentication/encryption state */
#ifdef ENABLE_GSS
{
OM_uint32 min_s;
......@@ -471,6 +471,21 @@ pqDropConnection(PGconn *conn, bool flushInput)
gss_delete_sec_context(&min_s, &conn->gctx, GSS_C_NO_BUFFER);
if (conn->gtarg_nam)
gss_release_name(&min_s, &conn->gtarg_nam);
if (conn->gss_SendBuffer)
{
free(conn->gss_SendBuffer);
conn->gss_SendBuffer = NULL;
}
if (conn->gss_RecvBuffer)
{
free(conn->gss_RecvBuffer);
conn->gss_RecvBuffer = NULL;
}
if (conn->gss_ResultBuffer)
{
free(conn->gss_ResultBuffer);
conn->gss_ResultBuffer = NULL;
}
}
#endif
#ifdef ENABLE_SSPI
......
This diff is collapsed.
......@@ -493,6 +493,23 @@ struct pg_conn
bool try_gss; /* GSS attempting permitted */
bool gssenc; /* GSS encryption is usable */
gss_cred_id_t gcred; /* GSS credential temp storage. */
/* GSS encryption I/O state --- see fe-secure-gssapi.c */
char *gss_SendBuffer; /* Encrypted data waiting to be sent */
int gss_SendLength; /* End of data available in gss_SendBuffer */
int gss_SendNext; /* Next index to send a byte from
* gss_SendBuffer */
int gss_SendConsumed; /* Number of *unencrypted* bytes consumed
* for current contents of gss_SendBuffer */
char *gss_RecvBuffer; /* Received, encrypted data */
int gss_RecvLength; /* End of data available in gss_RecvBuffer */
char *gss_ResultBuffer; /* Decryption of data in gss_RecvBuffer */
int gss_ResultLength; /* End of data available in
* gss_ResultBuffer */
int gss_ResultNext; /* Next index to read a byte from
* gss_ResultBuffer */
uint32 gss_MaxPktSize; /* Maximum size we can encrypt and fit the
* results into our output buffer */
#endif
#ifdef ENABLE_SSPI
......
......@@ -19,7 +19,7 @@ use Test::More;
if ($ENV{with_gssapi} eq 'yes')
{
plan tests => 12;
plan tests => 18;
}
else
{
......@@ -166,15 +166,15 @@ $node->safe_psql('postgres', 'CREATE USER test1;');
note "running tests";
# Test connection success or failure, and if success, that query returns true.
sub test_access
{
my ($node, $role, $server_check, $expected_res, $gssencmode, $test_name)
= @_;
my ($node, $role, $query, $expected_res, $gssencmode, $test_name) = @_;
# need to connect over TCP/IP for Kerberos
my ($res, $stdoutres, $stderrres) = $node->psql(
'postgres',
"$server_check",
"$query",
extra_params => [
'-XAtd',
$node->connstr('postgres')
......@@ -195,6 +195,29 @@ sub test_access
return;
}
# As above, but test for an arbitrary query result.
sub test_query
{
my ($node, $role, $query, $expected, $gssencmode, $test_name) = @_;
# need to connect over TCP/IP for Kerberos
my ($res, $stdoutres, $stderrres) = $node->psql(
'postgres',
"$query",
extra_params => [
'-XAtd',
$node->connstr('postgres')
. " host=$host hostaddr=$hostaddr $gssencmode",
'-U',
$role
]);
is($res, 0, $test_name);
like($stdoutres, $expected, $test_name);
is($stderrres, "", $test_name);
return;
}
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
qq{host all all $hostaddr/32 gss map=mymap});
......@@ -231,6 +254,27 @@ test_access(
"gssencmode=require",
"succeeds with GSS-encrypted access required with host hba");
# Test that we can transport a reasonable amount of data.
test_query(
$node,
"test1",
'SELECT * FROM generate_series(1, 100000);',
qr/^1\n.*\n1024\n.*\n9999\n.*\n100000$/s,
"gssencmode=require",
"receiving 100K lines works");
test_query(
$node,
"test1",
"CREATE TABLE mytab (f1 int primary key);\n"
. "COPY mytab FROM STDIN;\n"
. join("\n", (1 .. 100000))
. "\n\\.\n"
. "SELECT COUNT(*) FROM mytab;",
qr/^100000$/s,
"gssencmode=require",
"sending 100K lines works");
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
qq{hostgssenc all all $hostaddr/32 gss map=mymap});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment