Fix portability bugs in use of credentials control messages for peer auth.

Even though our existing code for handling credentials control messages has
been basically unchanged since 2001, it was fundamentally wrong: it did not
ensure proper alignment of the supplied buffer, and it was calculating
buffer sizes and message sizes incorrectly.  This led to failures on
platforms where alignment padding is relevant, for instance FreeBSD on
64-bit platforms, as seen in a recent Debian bug report passed on by
Martin Pitt (http://bugs.debian.org//cgi-bin/bugreport.cgi?bug=612888).

Rewrite to do the message-whacking using the macros specified in RFC 2292,
following a suggestion from Theo de Raadt in that thread.  Tested by me
on Debian/kFreeBSD-amd64; since OpenBSD and NetBSD document the identical
CMSG API, it should work there too.

Back-patch to all supported branches.
This commit is contained in:
Tom Lane 2011-05-30 19:16:05 -04:00
parent b4b6923e03
commit 13c00ae8c7
2 changed files with 47 additions and 35 deletions

View File

@ -1788,7 +1788,7 @@ auth_peer(hbaPort *port)
char ident_user[IDENT_USERNAME_MAX + 1]; char ident_user[IDENT_USERNAME_MAX + 1];
#if defined(HAVE_GETPEEREID) #if defined(HAVE_GETPEEREID)
/* OpenBSD style: */ /* OpenBSD (also Mac OS X) style: use getpeereid() */
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
struct passwd *pass; struct passwd *pass;
@ -1843,7 +1843,7 @@ auth_peer(hbaPort *port)
strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1); strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
#elif defined(HAVE_GETPEERUCRED) #elif defined(HAVE_GETPEERUCRED)
/* Solaris > 10 */ /* Solaris > 10: use getpeerucred() */
uid_t uid; uid_t uid;
struct passwd *pass; struct passwd *pass;
ucred_t *ucred; ucred_t *ucred;
@ -1878,9 +1878,7 @@ auth_peer(hbaPort *port)
strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1); strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
#elif defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS)) #elif defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
struct msghdr msg; /* Assorted BSDen: use a credentials control message */
/* Credentials structure */
#if defined(HAVE_STRUCT_CMSGCRED) #if defined(HAVE_STRUCT_CMSGCRED)
typedef struct cmsgcred Cred; typedef struct cmsgcred Cred;
@ -1894,36 +1892,35 @@ auth_peer(hbaPort *port)
#define cruid sc_uid #define cruid sc_uid
#endif #endif
Cred *cred;
/* Compute size without padding */
char cmsgmem[ALIGN(sizeof(struct cmsghdr)) + ALIGN(sizeof(Cred))]; /* for NetBSD */
/* Point to start of first structure */
struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem;
struct msghdr msg;
struct cmsghdr *cmsg;
union
{
struct cmsghdr hdr;
unsigned char buf[CMSG_SPACE(sizeof(Cred))];
} cmsgbuf;
struct iovec iov; struct iovec iov;
char buf; char buf;
Cred *cred;
struct passwd *pw; struct passwd *pw;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = (char *) cmsg;
msg.msg_controllen = sizeof(cmsgmem);
memset(cmsg, 0, sizeof(cmsgmem));
/* /*
* The one character which is received here is not meaningful; its * The one character that is received here is not meaningful; its purpose
* purposes is only to make sure that recvmsg() blocks long enough for the * is only to make sure that recvmsg() blocks long enough for the other
* other side to send its credentials. * side to send its credentials.
*/ */
iov.iov_base = &buf; iov.iov_base = &buf;
iov.iov_len = 1; iov.iov_len = 1;
if (recvmsg(port->sock, &msg, 0) < 0 || memset(&msg, 0, sizeof(msg));
cmsg->cmsg_len < sizeof(cmsgmem) || msg.msg_iov = &iov;
cmsg->cmsg_type != SCM_CREDS) msg.msg_iovlen = 1;
msg.msg_control = &cmsgbuf.buf;
msg.msg_controllen = sizeof(cmsgbuf.buf);
memset(&cmsgbuf, 0, sizeof(cmsgbuf));
if (recvmsg(port->sock, &msg, 0) < 0)
{ {
ereport(LOG, ereport(LOG,
(errcode_for_socket_access(), (errcode_for_socket_access(),
@ -1931,6 +1928,19 @@ auth_peer(hbaPort *port)
return STATUS_ERROR; return STATUS_ERROR;
} }
cmsg = CMSG_FIRSTHDR(&msg);
if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC) ||
cmsg == NULL ||
cmsg->cmsg_len < CMSG_LEN(sizeof(Cred)) ||
cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_CREDS)
{
ereport(LOG,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("could not get peer credentials: incorrect control message")));
return STATUS_ERROR;
}
cred = (Cred *) CMSG_DATA(cmsg); cred = (Cred *) CMSG_DATA(cmsg);
pw = getpwuid(cred->cruid); pw = getpwuid(cred->cruid);

View File

@ -693,11 +693,12 @@ pg_local_sendauth(PGconn *conn)
struct msghdr msg; struct msghdr msg;
#ifdef HAVE_STRUCT_CMSGCRED #ifdef HAVE_STRUCT_CMSGCRED
/* Prevent padding */ struct cmsghdr *cmsg;
char cmsgmem[sizeof(struct cmsghdr) + sizeof(struct cmsgcred)]; union
{
/* Point to start of first structure */ struct cmsghdr hdr;
struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem; unsigned char buf[CMSG_SPACE(sizeof(struct cmsgcred))];
} cmsgbuf;
#endif #endif
/* /*
@ -713,11 +714,12 @@ pg_local_sendauth(PGconn *conn)
msg.msg_iovlen = 1; msg.msg_iovlen = 1;
#ifdef HAVE_STRUCT_CMSGCRED #ifdef HAVE_STRUCT_CMSGCRED
/* Create control header, FreeBSD */ /* FreeBSD needs us to set up a message that will be filled in by kernel */
msg.msg_control = cmsg; memset(&cmsgbuf, 0, sizeof(cmsgbuf));
msg.msg_controllen = sizeof(cmsgmem); msg.msg_control = &cmsgbuf.buf;
memset(cmsg, 0, sizeof(cmsgmem)); msg.msg_controllen = sizeof(cmsgbuf.buf);
cmsg->cmsg_len = sizeof(cmsgmem); cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(struct cmsgcred));
cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDS; cmsg->cmsg_type = SCM_CREDS;
#endif #endif