Fix bio_wait() in crypto/bio/bio_lib.c in case OPENSSL_NO_SOCK
[openssl.git] / crypto / bio / bio_lib.c
index 0effdd2a139dcfbe8d0501a64c0866504ec038d1..1579f7c36608fe571baae5d51258ffe7fc2cee46 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
  *
- * Licensed under the OpenSSL license (the "License").  You may not use
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
  * in the file LICENSE in the source distribution or at
  * https://www.openssl.org/source/license.html
@@ -10,7 +10,7 @@
 #include <stdio.h>
 #include <errno.h>
 #include <openssl/crypto.h>
-#include "bio_lcl.h"
+#include "bio_local.h"
 #include "internal/cryptlib.h"
 
 
@@ -34,9 +34,8 @@ static long bio_call_callback(BIO *b, int oper, const char *argp, size_t len,
     long ret;
     int bareoper;
 
-    if (b->callback_ex != NULL) {
+    if (b->callback_ex != NULL)
         return b->callback_ex(b, oper, argp, len, argi, argl, inret, processed);
-    }
 
     /* Strip off any BIO_CB_RETURN flag */
     bareoper = oper & ~BIO_CB_RETURN;
@@ -51,20 +50,17 @@ static long bio_call_callback(BIO *b, int oper, const char *argp, size_t len,
             return -1;
 
         argi = (int)len;
+    }
 
-        if (inret && (oper & BIO_CB_RETURN)) {
-            if (*processed > INT_MAX)
-                return -1;
-            inret = *processed;
-        }
+    if (inret > 0 && (oper & BIO_CB_RETURN) && bareoper != BIO_CB_CTRL) {
+        if (*processed > INT_MAX)
+            return -1;
+        inret = *processed;
     }
 
     ret = b->callback(b, oper, argp, argi, argl, inret);
 
-    if (ret > LONG_MAX || ret < LONG_MIN)
-        return -1;
-
-    if (ret >= 0 && (HAS_LEN_OPER(bareoper) || bareoper == BIO_CB_PUTS)) {
+    if (ret > 0 && (oper & BIO_CB_RETURN) && bareoper != BIO_CB_CTRL) {
         *processed = (size_t)ret;
         ret = 1;
     }
@@ -78,7 +74,7 @@ BIO *BIO_new(const BIO_METHOD *method)
 
     if (bio == NULL) {
         BIOerr(BIO_F_BIO_NEW, ERR_R_MALLOC_FAILURE);
-        return (NULL);
+        return NULL;
     }
 
     bio->method = method;
@@ -101,6 +97,8 @@ BIO *BIO_new(const BIO_METHOD *method)
         CRYPTO_THREAD_lock_free(bio->lock);
         goto err;
     }
+    if (method->create == NULL)
+        bio->init = 1;
 
     return bio;
 
@@ -116,7 +114,7 @@ int BIO_free(BIO *a)
     if (a == NULL)
         return 0;
 
-    if (CRYPTO_atomic_add(&a->references, -1, &ret, a->lock) <= 0)
+    if (CRYPTO_DOWN_REF(&a->references, &ret, a->lock) <= 0)
         return 0;
 
     REF_PRINT_COUNT("BIO", a);
@@ -181,7 +179,7 @@ int BIO_up_ref(BIO *a)
 {
     int i;
 
-    if (CRYPTO_atomic_add(&a->references, 1, &i, a->lock) <= 0)
+    if (CRYPTO_UP_REF(&a->references, &i, a->lock) <= 0)
         return 0;
 
     REF_PRINT_COUNT("BIO", a);
@@ -244,111 +242,152 @@ int BIO_method_type(const BIO *b)
     return b->method->type;
 }
 
-int BIO_read(BIO *b, void *out, int outl)
-{
-    size_t read;
-    int ret;
-
-    if (outl < 0)
-        return 0;
-
-    ret = BIO_read_ex(b, out, (size_t)outl, &read);
-
-    if (ret > 0) {
-        /* *read should always be <= outl */
-        ret = (int)read;
-    }
-
-    return ret;
-}
-
-int BIO_read_ex(BIO *b, void *out, size_t outl, size_t *read)
+/*
+ * This is essentially the same as BIO_read_ex() except that it allows
+ * 0 or a negative value to indicate failure (retryable or not) in the return.
+ * This is for compatibility with the old style BIO_read(), where existing code
+ * may make assumptions about the return value that it might get.
+ */
+static int bio_read_intern(BIO *b, void *data, size_t dlen, size_t *readbytes)
 {
     int ret;
 
     if ((b == NULL) || (b->method == NULL) || (b->method->bread == NULL)) {
-        BIOerr(BIO_F_BIO_READ_EX, BIO_R_UNSUPPORTED_METHOD);
-        return (-2);
+        BIOerr(BIO_F_BIO_READ_INTERN, BIO_R_UNSUPPORTED_METHOD);
+        return -2;
     }
 
     if ((b->callback != NULL || b->callback_ex != NULL) &&
-        ((ret = (int)bio_call_callback(b, BIO_CB_READ, out, outl, 0, 0L, 1L,
-                                       read)) <= 0))
+        ((ret = (int)bio_call_callback(b, BIO_CB_READ, data, dlen, 0, 0L, 1L,
+                                       NULL)) <= 0))
         return ret;
 
     if (!b->init) {
-        BIOerr(BIO_F_BIO_READ_EX, BIO_R_UNINITIALIZED);
+        BIOerr(BIO_F_BIO_READ_INTERN, BIO_R_UNINITIALIZED);
         return -2;
     }
 
-    ret = b->method->bread(b, out, outl, read);
+    ret = b->method->bread(b, data, dlen, readbytes);
 
     if (ret > 0)
-        b->num_read += (uint64_t)*read;
+        b->num_read += (uint64_t)*readbytes;
 
     if (b->callback != NULL || b->callback_ex != NULL)
-        ret = (int)bio_call_callback(b, BIO_CB_READ | BIO_CB_RETURN, out, outl,
-                                     0, 0L, ret, read);
+        ret = (int)bio_call_callback(b, BIO_CB_READ | BIO_CB_RETURN, data,
+                                     dlen, 0, 0L, ret, readbytes);
+
+    /* Shouldn't happen */
+    if (ret > 0 && *readbytes > dlen) {
+        BIOerr(BIO_F_BIO_READ_INTERN, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
 
     return ret;
 }
 
-int BIO_write(BIO *b, const void *in, int inl)
+int BIO_read(BIO *b, void *data, int dlen)
 {
-    size_t written;
+    size_t readbytes;
     int ret;
 
-    if (inl < 0)
+    if (dlen < 0)
         return 0;
 
-    ret = BIO_write_ex(b, in, (size_t)inl, &written);
+    ret = bio_read_intern(b, data, (size_t)dlen, &readbytes);
 
     if (ret > 0) {
-        /* *written should always be <= inl */
-        ret = (int)written;
+        /* *readbytes should always be <= dlen */
+        ret = (int)readbytes;
     }
 
     return ret;
 }
 
-int BIO_write_ex(BIO *b, const void *in, size_t inl, size_t *written)
+int BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes)
+{
+    int ret;
+
+    ret = bio_read_intern(b, data, dlen, readbytes);
+
+    if (ret > 0)
+        ret = 1;
+    else
+        ret = 0;
+
+    return ret;
+}
+
+static int bio_write_intern(BIO *b, const void *data, size_t dlen,
+                            size_t *written)
 {
     int ret;
 
     if (b == NULL)
-        return (0);
+        return 0;
 
     if ((b->method == NULL) || (b->method->bwrite == NULL)) {
-        BIOerr(BIO_F_BIO_WRITE_EX, BIO_R_UNSUPPORTED_METHOD);
-        return (-2);
+        BIOerr(BIO_F_BIO_WRITE_INTERN, BIO_R_UNSUPPORTED_METHOD);
+        return -2;
     }
 
     if ((b->callback != NULL || b->callback_ex != NULL) &&
-        ((ret = (int)bio_call_callback(b, BIO_CB_WRITE, in, inl, 0, 0L, 1L,
-                                       written)) <= 0))
+        ((ret = (int)bio_call_callback(b, BIO_CB_WRITE, data, dlen, 0, 0L, 1L,
+                                       NULL)) <= 0))
         return ret;
 
     if (!b->init) {
-        BIOerr(BIO_F_BIO_WRITE_EX, BIO_R_UNINITIALIZED);
+        BIOerr(BIO_F_BIO_WRITE_INTERN, BIO_R_UNINITIALIZED);
         return -2;
     }
 
-    ret = b->method->bwrite(b, in, inl, written);
+    ret = b->method->bwrite(b, data, dlen, written);
 
     if (ret > 0)
         b->num_write += (uint64_t)*written;
 
     if (b->callback != NULL || b->callback_ex != NULL)
-        ret = (int)bio_call_callback(b, BIO_CB_WRITE | BIO_CB_RETURN, in, inl,
-                                     0, 0L, ret, written);
+        ret = (int)bio_call_callback(b, BIO_CB_WRITE | BIO_CB_RETURN, data,
+                                     dlen, 0, 0L, ret, written);
 
     return ret;
 }
 
-int BIO_puts(BIO *b, const char *in)
+int BIO_write(BIO *b, const void *data, int dlen)
 {
-    int ret;
     size_t written;
+    int ret;
+
+    if (dlen < 0)
+        return 0;
+
+    ret = bio_write_intern(b, data, (size_t)dlen, &written);
+
+    if (ret > 0) {
+        /* *written should always be <= dlen */
+        ret = (int)written;
+    }
+
+    return ret;
+}
+
+int BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written)
+{
+    int ret;
+
+    ret = bio_write_intern(b, data, dlen, written);
+
+    if (ret > 0)
+        ret = 1;
+    else
+        ret = 0;
+
+    return ret;
+}
+
+int BIO_puts(BIO *b, const char *buf)
+{
+    int ret;
+    size_t written = 0;
 
     if ((b == NULL) || (b->method == NULL) || (b->method->bputs == NULL)) {
         BIOerr(BIO_F_BIO_PUTS, BIO_R_UNSUPPORTED_METHOD);
@@ -356,7 +395,7 @@ int BIO_puts(BIO *b, const char *in)
     }
 
     if (b->callback != NULL || b->callback_ex != NULL) {
-        ret = (int)bio_call_callback(b, BIO_CB_PUTS, in, 0, 0, 0L, 1L, NULL);
+        ret = (int)bio_call_callback(b, BIO_CB_PUTS, buf, 0, 0, 0L, 1L, NULL);
         if (ret <= 0)
             return ret;
     }
@@ -366,7 +405,7 @@ int BIO_puts(BIO *b, const char *in)
         return -2;
     }
 
-    ret = b->method->bputs(b, in);
+    ret = b->method->bputs(b, buf);
 
     if (ret > 0) {
         b->num_write += (uint64_t)ret;
@@ -375,56 +414,64 @@ int BIO_puts(BIO *b, const char *in)
     }
 
     if (b->callback != NULL || b->callback_ex != NULL)
-        ret = (int)bio_call_callback(b, BIO_CB_PUTS | BIO_CB_RETURN, in, 0, 0,
+        ret = (int)bio_call_callback(b, BIO_CB_PUTS | BIO_CB_RETURN, buf, 0, 0,
                                      0L, ret, &written);
 
     if (ret > 0) {
-        if (written > INT_MAX)
+        if (written > INT_MAX) {
+            BIOerr(BIO_F_BIO_PUTS, BIO_R_LENGTH_TOO_LONG);
             ret = -1;
-        else
+        } else {
             ret = (int)written;
+        }
     }
 
     return ret;
 }
 
-int BIO_gets(BIO *b, char *out, int outl)
+int BIO_gets(BIO *b, char *buf, int size)
 {
     int ret;
-    size_t read;
+    size_t readbytes = 0;
 
     if ((b == NULL) || (b->method == NULL) || (b->method->bgets == NULL)) {
         BIOerr(BIO_F_BIO_GETS, BIO_R_UNSUPPORTED_METHOD);
-        return (-2);
+        return -2;
+    }
+
+    if (size < 0) {
+        BIOerr(BIO_F_BIO_GETS, BIO_R_INVALID_ARGUMENT);
+        return 0;
     }
 
     if (b->callback != NULL || b->callback_ex != NULL) {
-        ret = (int)bio_call_callback(b, BIO_CB_GETS, out, outl, 0, 0L, 1, NULL);
+        ret = (int)bio_call_callback(b, BIO_CB_GETS, buf, size, 0, 0L, 1, NULL);
         if (ret <= 0)
             return ret;
     }
 
     if (!b->init) {
         BIOerr(BIO_F_BIO_GETS, BIO_R_UNINITIALIZED);
-        return (-2);
+        return -2;
     }
 
-    ret = b->method->bgets(b, out, outl);
+    ret = b->method->bgets(b, buf, size);
 
     if (ret > 0) {
-        read = ret;
+        readbytes = ret;
         ret = 1;
     }
 
     if (b->callback != NULL || b->callback_ex != NULL)
-        ret = (int)bio_call_callback(b, BIO_CB_GETS | BIO_CB_RETURN, out, outl,
-                                     0, 0L, ret, &read);
+        ret = (int)bio_call_callback(b, BIO_CB_GETS | BIO_CB_RETURN, buf, size,
+                                     0, 0L, ret, &readbytes);
 
     if (ret > 0) {
-        if (read > INT_MAX)
+        /* Shouldn't happen */
+        if (readbytes > (size_t)size)
             ret = -1;
         else
-            ret = (int)read;
+            ret = (int)readbytes;
     }
 
     return ret;
@@ -447,7 +494,7 @@ long BIO_int_ctrl(BIO *b, int cmd, long larg, int iarg)
     int i;
 
     i = iarg;
-    return (BIO_ctrl(b, cmd, larg, (char *)&i));
+    return BIO_ctrl(b, cmd, larg, (char *)&i);
 }
 
 void *BIO_ptr_ctrl(BIO *b, int cmd, long larg)
@@ -455,9 +502,9 @@ void *BIO_ptr_ctrl(BIO *b, int cmd, long larg)
     void *p = NULL;
 
     if (BIO_ctrl(b, cmd, larg, (char *)&p) <= 0)
-        return (NULL);
+        return NULL;
     else
-        return (p);
+        return p;
 }
 
 long BIO_ctrl(BIO *b, int cmd, long larg, void *parg)
@@ -487,18 +534,17 @@ long BIO_ctrl(BIO *b, int cmd, long larg, void *parg)
     return ret;
 }
 
-long BIO_callback_ctrl(BIO *b, int cmd,
-                       void (*fp) (struct bio_st *, int, const char *, int,
-                                   long, long))
+long BIO_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
 {
     long ret;
 
     if (b == NULL)
-        return (0);
+        return 0;
 
-    if ((b->method == NULL) || (b->method->callback_ctrl == NULL)) {
+    if ((b->method == NULL) || (b->method->callback_ctrl == NULL)
+            || (cmd != BIO_CTRL_SET_CALLBACK)) {
         BIOerr(BIO_F_BIO_CALLBACK_CTRL, BIO_R_UNSUPPORTED_METHOD);
-        return (-2);
+        return -2;
     }
 
     if (b->callback != NULL || b->callback_ex != NULL) {
@@ -538,7 +584,7 @@ BIO *BIO_push(BIO *b, BIO *bio)
     BIO *lb;
 
     if (b == NULL)
-        return (bio);
+        return bio;
     lb = b;
     while (lb->next_bio != NULL)
         lb = lb->next_bio;
@@ -547,7 +593,7 @@ BIO *BIO_push(BIO *b, BIO *bio)
         bio->prev_bio = lb;
     /* called to do internal processing */
     BIO_ctrl(b, BIO_CTRL_PUSH, 0, lb);
-    return (b);
+    return b;
 }
 
 /* Remove the first and return the rest */
@@ -556,7 +602,7 @@ BIO *BIO_pop(BIO *b)
     BIO *ret;
 
     if (b == NULL)
-        return (NULL);
+        return NULL;
     ret = b->next_bio;
 
     BIO_ctrl(b, BIO_CTRL_POP, 0, b);
@@ -568,7 +614,7 @@ BIO *BIO_pop(BIO *b)
 
     b->next_bio = NULL;
     b->prev_bio = NULL;
-    return (ret);
+    return ret;
 }
 
 BIO *BIO_get_retry_BIO(BIO *bio, int *reason)
@@ -586,12 +632,12 @@ BIO *BIO_get_retry_BIO(BIO *bio, int *reason)
     }
     if (reason != NULL)
         *reason = last->retry_reason;
-    return (last);
+    return last;
 }
 
 int BIO_get_retry_reason(BIO *bio)
 {
-    return (bio->retry_reason);
+    return bio->retry_reason;
 }
 
 void BIO_set_retry_reason(BIO *bio, int reason)
@@ -612,13 +658,13 @@ BIO *BIO_find_type(BIO *bio, int type)
 
             if (!mask) {
                 if (mt & type)
-                    return (bio);
+                    return bio;
             } else if (mt == type)
-                return (bio);
+                return bio;
         }
         bio = bio->next_bio;
     } while (bio != NULL);
-    return (NULL);
+    return NULL;
 }
 
 BIO *BIO_next(BIO *b)
@@ -686,11 +732,11 @@ BIO *BIO_dup_chain(BIO *in)
             eoc = new_bio;
         }
     }
-    return (ret);
+    return ret;
  err:
     BIO_free_all(ret);
 
-    return (NULL);
+    return NULL;
 }
 
 void BIO_copy_next_retry(BIO *b)
@@ -701,12 +747,12 @@ void BIO_copy_next_retry(BIO *b)
 
 int BIO_set_ex_data(BIO *bio, int idx, void *data)
 {
-    return (CRYPTO_set_ex_data(&(bio->ex_data), idx, data));
+    return CRYPTO_set_ex_data(&(bio->ex_data), idx, data);
 }
 
-void *BIO_get_ex_data(BIO *bio, int idx)
+void *BIO_get_ex_data(const BIO *bio, int idx)
 {
-    return (CRYPTO_get_ex_data(&(bio->ex_data), idx));
+    return CRYPTO_get_ex_data(&(bio->ex_data), idx);
 }
 
 uint64_t BIO_number_read(BIO *bio)
@@ -738,3 +784,102 @@ void bio_cleanup(void)
     CRYPTO_THREAD_lock_free(bio_type_lock);
     bio_type_lock = NULL;
 }
+
+/* Internal variant of the below BIO_wait() not calling BIOerr() */
+static int bio_wait(BIO *bio, time_t max_time, unsigned int milliseconds)
+{
+#ifndef OPENSSL_NO_SOCK
+    int fd;
+#endif
+
+    if (max_time == 0)
+        return 1;
+
+#ifndef OPENSSL_NO_SOCK
+    if (BIO_get_fd(bio, &fd) > 0)
+        return BIO_socket_wait(fd, BIO_should_read(bio), max_time);
+#endif
+    if (milliseconds > 1000) {
+        long sec_diff = (long)(max_time - time(NULL)); /* might overflow */
+
+        if (sec_diff <= 0)
+            return 0; /* timeout */
+        if ((unsigned long)sec_diff < milliseconds / 1000)
+            milliseconds = (unsigned long)sec_diff * 1000;
+    }
+    ossl_sleep(milliseconds);
+    return 1;
+}
+
+/*
+ * Wait on (typically socket-based) BIO at most until max_time.
+ * Succeed immediately if max_time == 0. If sockets are not available succeed
+ * after waiting at most given milliseconds in order to avoid a tight busy loop.
+ * Call BIOerr(...) unless success.
+ * Returns -1 on error, 0 on timeout, and 1 on success.
+ */
+int BIO_wait(BIO *bio, time_t max_time, unsigned int milliseconds)
+{
+    int rv = bio_wait(bio, max_time, milliseconds);
+
+    if (rv <= 0)
+        BIOerr(0, rv == 0 ? BIO_R_TRANSFER_TIMEOUT : BIO_R_TRANSFER_ERROR);
+    return rv;
+}
+
+/*
+ * Connect via given BIO using BIO_do_handshake() until success/timeout/error.
+ * Parameter timeout == 0 means infinite, < 0 leads to immediate timeout error.
+ * Returns -1 on error, 0 on timeout, and 1 on success.
+ */
+int BIO_connect_retry(BIO *bio, int timeout)
+{
+    int blocking = timeout == 0;
+    time_t max_time = timeout > 0 ? time(NULL) + timeout : 0;
+    int rv;
+
+    if (bio == NULL) {
+        BIOerr(0, ERR_R_PASSED_NULL_PARAMETER);
+        return -1;
+    }
+
+    if (timeout < 0) {
+        BIOerr(0, BIO_R_CONNECT_TIMEOUT);
+        return 0;
+    }
+
+    if (!blocking)
+        BIO_set_nbio(bio, 1);
+
+ retry: /* it does not help here to set SSL_MODE_AUTO_RETRY */
+    rv = BIO_do_handshake(bio); /* This indirectly calls ERR_clear_error(); */
+
+    if (rv <= 0) {
+        if (get_last_sys_error() == ETIMEDOUT) {
+            /*
+             * if blocking, despite blocking BIO, BIO_do_handshake() timed out
+             * when non-blocking, BIO_do_handshake() timed out early
+             * with rv == -1 and get_last_sys_error() == 0
+             */
+            ERR_clear_error();
+            (void)BIO_reset(bio);
+            /*
+             * unless using BIO_reset(), blocking next connect() may crash and
+             * non-blocking next BIO_do_handshake() will fail
+             */
+            goto retry;
+        } else if (BIO_should_retry(bio)) {
+            /* will not actually wait if timeout == 0 (i.e., blocking BIO) */
+            rv = bio_wait(bio, max_time, 100 /* milliseconds */);
+            if (rv > 0)
+                goto retry;
+            BIOerr(0, rv == 0 ? BIO_R_CONNECT_TIMEOUT : BIO_R_CONNECT_ERROR);
+        } else {
+            rv = -1;
+            if (ERR_peek_error() == 0) /* missing error queue entry */
+                BIOerr(0, BIO_R_CONNECT_ERROR); /* workaround: general error */
+        }
+    }
+
+    return rv;
+}