Headline
CVE-2019-3883: PR#50331: Ticket 50309 - Possible Security Issue: DOS due to ioblocktimeout not applying to TLS - 389-ds-base
In 389-ds-base up to version 1.4.1.2, requests are handled by workers threads. Each sockets will be waited by the worker for at most ‘ioblocktimeout’ seconds. However this timeout applies only for un-encrypted requests. Connections using SSL/TLS are not taking this timeout into account during reads, and may hang longer.An unauthenticated attacker could repeatedly create hanging LDAP requests to hang all the workers, resulting in a Denial of Service.
@@ -940,6 +940,10 @@
#define CONN_TURBO_PERCENTILE 50 /* proportion of threads allowed to be in turbo mode */
#define CONN_TURBO_HYSTERESIS 0 /* avoid flip flopping in and out of turbo mode */
+ #define CONN_TIMEOUT_READ_SECURE 100 /* on secure connection a read can block if there
+ * nothing to read. This timeout (millisecond)
+ * is the maximum delay a worker will poll the connection
+ */
void
connection_make_new_pb(Slapi_PBlock *pb, Connection *conn)
{
@@ -1091,11 +1095,78 @@
return 0;
}
+ /* this function is specific to secure socket that are in blocking mode
+ * It polls the socket to check if we can read it
+ * It returns
+ * ret < 0: a poll failed (different from E_WOULD_BLOCK) 'err' is set
+ * ret = 0: the socket can not be read for ioblocktimeout
+ * ret > 0: the socket can be read
+ */
+ static int
+ connection_poll_read_secure(Connection *conn, PRInt32 *err)
+ {
+ int32_t ioblocktimeout_waits = conn->c_ioblocktimeout / CONN_TIMEOUT_READ_SECURE;
+ int32_t waits_done = 0;
+ struct PRPollDesc pr_pd;
+ PRInt32 ret;
+ PRInt32 syserr = 0;
+ PRIntervalTime timeout = PR_MillisecondsToInterval(CONN_TIMEOUT_READ_SECURE);
+
+ /* The purpose of the loop is to check we can read the socket within the ioblocktimeout limit */
+ do {
+ pr_pd.fd = (PRFileDesc *) conn->c_prfd;
+ pr_pd.in_flags = PR_POLL_READ;
+ pr_pd.out_flags = 0;
+ ret = PR_Poll(&pr_pd, 1, timeout);
+ if (ret > 1) {
+ /* most frequent case. Socket can be read right now so exit from the loop */
+ break;
+ } else if (ret == 0) {
+ /* timeout */
+ waits_done++;
+ } else if (ret < 0) {
+ syserr = PR_GetOSError();
+ if (SLAPD_SYSTEM_WOULD_BLOCK_ERROR(syserr)) {
+ /* If we would block, let's count it as a timeout and continue the loop */
+ waits_done++;
+ ret = 0;
+ } else {
+ /* A failure on a poll, no need to continue */
+ *err = PR_GetError();
+ break;
+ }
+ }
+
+ if (waits_done > ioblocktimeout_waits) {
+ /* We have been polling longer than ioblocktimeout
+ * It is time to say it hang for too long
+ */
+ slapi_log_err(SLAPI_LOG_INFO, "connection_poll_read_secure",
+ "Timeout (%d ms) while reading secured conn %" PRIu64 "\n", conn->c_ioblocktimeout, conn->c_connid);
+ ret = 0;
+ break;
+ }
+ } while (ret == 0);
+
+ /* At this point we have 3 options
+ * ret < 0: a poll failed
+ * ret = 0: the socket can not be read for ioblocktimeout
+ * ret > 0: the socket can be read
+ */
+ return (int) ret;
+ }
/* Either read read data into the connection buffer, or fail with err set */
static int
connection_read_ldap_data(Connection *conn, PRInt32 *err)
{
int ret = 0;
+ if (conn->c_flags & CONN_FLAG_SSL) {
+ ret = connection_poll_read_secure(conn, err);
+ if (ret <= 0) {
+ /* The socket hang for ioblocktimeout or poll failed */
+ return ret;
+ }
+ }
ret = PR_Recv(conn->c_prfd, conn->c_private->c_buffer, conn->c_private->c_buffer_size, 0, PR_INTERVAL_NO_WAIT);
if (ret < 0) {
*err = PR_GetError();
@@ -1140,7 +1211,7 @@
{
ber_len_t len = 0;
int ret = 0;
- int waits_done = 0;
+ int32_t waits_done = 0;
ber_int_t msgid;
int new_operation = 1; /* Are we doing the first I/O read for a new operation ? */
char *buffer = conn->c_private->c_buffer;