#50331 Ticket 50309 - Possible Security Issue: DOS due to ioblocktimeout not applying to TLS
Merged 4 months ago by tbordaz. Opened 4 months ago by tbordaz.
tbordaz/389-ds-base ticket_50309  into  master

@@ -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;

Bug Description:
A secure socket is configured in blocking mode. If an event
is detected on a secure socket a worker, tries to read the request.
The read can hang indefinitely if there is nothing to read.
As a consequence ioblocktimeout is not enforced when reading secure socket

Fix Description:
The fix is specific to secure socket read.
Before reading it polls the socket for a read. The socket is poll
(with a 0.1s timeout) until read is possible or sum of poll timeout
is greater than ioblocktimeout.

https://pagure.io/389-ds-base/issue/50329

Reviewed by: Mark Reynolds

Platforms tested: F28

Flag Day: no

Doc impact: no

rebased onto 4d9cc24

4 months ago

Pull-Request has been merged by tbordaz

4 months ago
Metadata