changeset 9035:3be953161026 quic

Merged with the default branch.
author Sergey Kandaurov <pluknet@nginx.com>
date Thu, 20 Oct 2022 16:41:36 +0400
parents 79cd6993a3e3 (current diff) 1ae25660c0c7 (diff)
children 6cf8ed15fd00
files src/event/ngx_event_openssl.c src/event/ngx_event_openssl.h src/http/modules/ngx_http_ssl_module.c src/stream/ngx_stream_ssl_module.c
diffstat 16 files changed, 904 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags
+++ b/.hgtags
@@ -469,3 +469,4 @@ d986378168fd4d70e0121cabac274c560cca9bdf
 714eb4b2c09e712fb2572a2164ce2bf67638ccac release-1.21.6
 5da2c0902e8e2aa4534008a582a60c61c135960e release-1.23.0
 a63d0a70afea96813ba6667997bc7d68b5863f0d release-1.23.1
+aa901551a7ebad1e8b0f8c11cb44e3424ba29707 release-1.23.2
--- a/auto/lib/openssl/makefile.msvc
+++ b/auto/lib/openssl/makefile.msvc
@@ -6,7 +6,7 @@
 all:
 	cd $(OPENSSL)
 
-	perl Configure VC-WIN32 no-shared				\
+	perl Configure VC-WIN32 no-shared no-threads			\
 		--prefix="%cd%/openssl" 				\
 		--openssldir="%cd%/openssl/ssl" 			\
 		$(OPENSSL_OPT)
--- a/docs/xml/nginx/changes.xml
+++ b/docs/xml/nginx/changes.xml
@@ -5,6 +5,120 @@
 <change_log title="nginx">
 
 
+<changes ver="1.23.2" date="2022-10-19">
+
+<change type="security">
+<para lang="ru">
+обработка специально созданного mp4-файла модулем ngx_http_mp4_module
+могла приводить к падению рабочего процесса,
+отправке клиенту части содержимого памяти рабочего процесса,
+а также потенциально могла иметь другие последствия
+(CVE-2022-41741, CVE-2022-41742).
+</para>
+<para lang="en">
+processing of a specially crafted mp4 file by the ngx_http_mp4_module
+might cause a worker process crash,
+worker process memory disclosure,
+or might have potential other impact
+(CVE-2022-41741, CVE-2022-41742).
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+переменные "$proxy_protocol_tlv_...".
+</para>
+<para lang="en">
+the "$proxy_protocol_tlv_..." variables.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+ключи шифрования TLS session tickets теперь автоматически меняются
+при использовании разделяемой памяти в ssl_session_cache.
+</para>
+<para lang="en">
+TLS session tickets encryption keys are now automatically rotated
+when using shared memory in the "ssl_session_cache" directive.
+</para>
+</change>
+
+<change type="change">
+<para lang="ru">
+уровень логгирования ошибок SSL "bad record type"
+понижен с уровня crit до info.<br/>
+Спасибо Murilo Andrade.
+</para>
+<para lang="en">
+the logging level of the "bad record type" SSL errors
+has been lowered from "crit" to "info".<br/>
+Thanks to Murilo Andrade.
+</para>
+</change>
+
+<change type="change">
+<para lang="ru">
+теперь при использовании разделяемой памяти в ssl_session_cache
+сообщения "could not allocate new session"
+логгируются на уровне warn вместо alert
+и не чаще одного раза в секунду.
+</para>
+<para lang="en">
+now when using shared memory in the "ssl_session_cache" directive
+the "could not allocate new session" errors
+are logged at the "warn" level instead of "alert"
+and not more often than once per second.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+nginx/Windows не собирался с OpenSSL 3.0.x.
+</para>
+<para lang="en">
+nginx/Windows could not be built with OpenSSL 3.0.x.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+в логгировании ошибок протокола PROXY.<br/>
+Спасибо Сергею Брестеру.
+</para>
+<para lang="en">
+in logging of the PROXY protocol errors.<br/>
+Thanks to Sergey Brester.
+</para>
+</change>
+
+<change type="workaround">
+<para lang="ru">
+при использовании TLSv1.3 с OpenSSL
+разделяемая память из ssl_session_cache расходовалась
+в том числе на сессии, использующие TLS session tickets.
+</para>
+<para lang="en">
+shared memory from the "ssl_session_cache" directive
+was spent on sessions using TLS session tickets
+when using TLSv1.3 with OpenSSL.
+</para>
+</change>
+
+<change type="workaround">
+<para lang="ru">
+таймаут, заданный с помощью директивы ssl_session_timeout,
+не работал при использовании TLSv1.3 с OpenSSL или BoringSSL.
+</para>
+<para lang="en">
+timeout specified with the "ssl_session_timeout" directive
+did not work when using TLSv1.3 with OpenSSL or BoringSSL.
+</para>
+</change>
+
+</changes>
+
+
 <changes ver="1.23.1" date="2022-07-19">
 
 <change type="feature">
--- a/src/core/nginx.h
+++ b/src/core/nginx.h
@@ -9,8 +9,8 @@
 #define _NGINX_H_INCLUDED_
 
 
-#define nginx_version      1023001
-#define NGINX_VERSION      "1.23.1"
+#define nginx_version      1023002
+#define NGINX_VERSION      "1.23.2"
 #define NGINX_VER          "nginx/" NGINX_VERSION
 
 #ifdef NGX_BUILD
--- a/src/core/ngx_proxy_protocol.c
+++ b/src/core/ngx_proxy_protocol.c
@@ -13,7 +13,15 @@
 #define NGX_PROXY_PROTOCOL_AF_INET6         2
 
 
-#define ngx_proxy_protocol_parse_uint16(p)  ((p)[0] << 8 | (p)[1])
+#define ngx_proxy_protocol_parse_uint16(p)                                    \
+    ( ((uint16_t) (p)[0] << 8)                                                \
+    + (           (p)[1]) )
+
+#define ngx_proxy_protocol_parse_uint32(p)                                    \
+    ( ((uint32_t) (p)[0] << 24)                                               \
+    + (           (p)[1] << 16)                                               \
+    + (           (p)[2] << 8)                                                \
+    + (           (p)[3]) )
 
 
 typedef struct {
@@ -40,12 +48,52 @@ typedef struct {
 } ngx_proxy_protocol_inet6_addrs_t;
 
 
+typedef struct {
+    u_char                                  type;
+    u_char                                  len[2];
+} ngx_proxy_protocol_tlv_t;
+
+
+typedef struct {
+    u_char                                  client;
+    u_char                                  verify[4];
+} ngx_proxy_protocol_tlv_ssl_t;
+
+
+typedef struct {
+    ngx_str_t                               name;
+    ngx_uint_t                              type;
+} ngx_proxy_protocol_tlv_entry_t;
+
+
 static u_char *ngx_proxy_protocol_read_addr(ngx_connection_t *c, u_char *p,
     u_char *last, ngx_str_t *addr);
 static u_char *ngx_proxy_protocol_read_port(u_char *p, u_char *last,
     in_port_t *port, u_char sep);
 static u_char *ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf,
     u_char *last);
+static ngx_int_t ngx_proxy_protocol_lookup_tlv(ngx_connection_t *c,
+    ngx_str_t *tlvs, ngx_uint_t type, ngx_str_t *value);
+
+
+static ngx_proxy_protocol_tlv_entry_t  ngx_proxy_protocol_tlv_entries[] = {
+    { ngx_string("alpn"),       0x01 },
+    { ngx_string("authority"),  0x02 },
+    { ngx_string("unique_id"),  0x05 },
+    { ngx_string("ssl"),        0x20 },
+    { ngx_string("netns"),      0x30 },
+    { ngx_null_string,          0x00 }
+};
+
+
+static ngx_proxy_protocol_tlv_entry_t  ngx_proxy_protocol_tlv_ssl_entries[] = {
+    { ngx_string("version"),    0x21 },
+    { ngx_string("cn"),         0x22 },
+    { ngx_string("cipher"),     0x23 },
+    { ngx_string("sig_alg"),    0x24 },
+    { ngx_string("key_alg"),    0x25 },
+    { ngx_null_string,          0x00 }
+};
 
 
 u_char *
@@ -139,8 +187,14 @@ skip:
 
 invalid:
 
+    for (p = buf; p < last; p++) {
+        if (*p == CR || *p == LF) {
+            break;
+        }
+    }
+
     ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                  "broken header: \"%*s\"", (size_t) (last - buf), buf);
+                  "broken header: \"%*s\"", (size_t) (p - buf), buf);
 
     return NULL;
 }
@@ -412,11 +466,147 @@ ngx_proxy_protocol_v2_read(ngx_connectio
                    &pp->src_addr, pp->src_port, &pp->dst_addr, pp->dst_port);
 
     if (buf < end) {
-        ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
-                       "PROXY protocol v2 %z bytes of tlv ignored", end - buf);
+        pp->tlvs.data = ngx_pnalloc(c->pool, end - buf);
+        if (pp->tlvs.data == NULL) {
+            return NULL;
+        }
+
+        ngx_memcpy(pp->tlvs.data, buf, end - buf);
+        pp->tlvs.len = end - buf;
     }
 
     c->proxy_protocol = pp;
 
     return end;
 }
+
+
+ngx_int_t
+ngx_proxy_protocol_get_tlv(ngx_connection_t *c, ngx_str_t *name,
+    ngx_str_t *value)
+{
+    u_char                          *p;
+    size_t                           n;
+    uint32_t                         verify;
+    ngx_str_t                        ssl, *tlvs;
+    ngx_int_t                        rc, type;
+    ngx_proxy_protocol_tlv_ssl_t    *tlv_ssl;
+    ngx_proxy_protocol_tlv_entry_t  *te;
+
+    if (c->proxy_protocol == NULL) {
+        return NGX_DECLINED;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "PROXY protocol v2 get tlv \"%V\"", name);
+
+    te = ngx_proxy_protocol_tlv_entries;
+    tlvs = &c->proxy_protocol->tlvs;
+
+    p = name->data;
+    n = name->len;
+
+    if (n >= 4 && p[0] == 's' && p[1] == 's' && p[2] == 'l' && p[3] == '_') {
+
+        rc = ngx_proxy_protocol_lookup_tlv(c, tlvs, 0x20, &ssl);
+        if (rc != NGX_OK) {
+            return rc;
+        }
+
+        if (ssl.len < sizeof(ngx_proxy_protocol_tlv_ssl_t)) {
+            return NGX_ERROR;
+        }
+
+        p += 4;
+        n -= 4;
+
+        if (n == 6 && ngx_strncmp(p, "verify", 6) == 0) {
+
+            tlv_ssl = (ngx_proxy_protocol_tlv_ssl_t *) ssl.data;
+            verify = ngx_proxy_protocol_parse_uint32(tlv_ssl->verify);
+
+            value->data = ngx_pnalloc(c->pool, NGX_INT32_LEN);
+            if (value->data == NULL) {
+                return NGX_ERROR;
+            }
+
+            value->len = ngx_sprintf(value->data, "%uD", verify)
+                         - value->data;
+            return NGX_OK;
+        }
+
+        ssl.data += sizeof(ngx_proxy_protocol_tlv_ssl_t);
+        ssl.len -= sizeof(ngx_proxy_protocol_tlv_ssl_t);
+
+        te = ngx_proxy_protocol_tlv_ssl_entries;
+        tlvs = &ssl;
+    }
+
+    if (n >= 2 && p[0] == '0' && p[1] == 'x') {
+
+        type = ngx_hextoi(p + 2, n - 2);
+        if (type == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                          "invalid PROXY protocol TLV \"%V\"", name);
+            return NGX_ERROR;
+        }
+
+        return ngx_proxy_protocol_lookup_tlv(c, tlvs, type, value);
+    }
+
+    for ( /* void */ ; te->type; te++) {
+        if (te->name.len == n && ngx_strncmp(te->name.data, p, n) == 0) {
+            return ngx_proxy_protocol_lookup_tlv(c, tlvs, te->type, value);
+        }
+    }
+
+    ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                  "unknown PROXY protocol TLV \"%V\"", name);
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_proxy_protocol_lookup_tlv(ngx_connection_t *c, ngx_str_t *tlvs,
+    ngx_uint_t type, ngx_str_t *value)
+{
+    u_char                    *p;
+    size_t                     n, len;
+    ngx_proxy_protocol_tlv_t  *tlv;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,
+                   "PROXY protocol v2 lookup tlv:%02xi", type);
+
+    p = tlvs->data;
+    n = tlvs->len;
+
+    while (n) {
+        if (n < sizeof(ngx_proxy_protocol_tlv_t)) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0, "broken PROXY protocol TLV");
+            return NGX_ERROR;
+        }
+
+        tlv = (ngx_proxy_protocol_tlv_t *) p;
+        len = ngx_proxy_protocol_parse_uint16(tlv->len);
+
+        p += sizeof(ngx_proxy_protocol_tlv_t);
+        n -= sizeof(ngx_proxy_protocol_tlv_t);
+
+        if (n < len) {
+            ngx_log_error(NGX_LOG_ERR, c->log, 0, "broken PROXY protocol TLV");
+            return NGX_ERROR;
+        }
+
+        if (tlv->type == type) {
+            value->data = p;
+            value->len = len;
+            return NGX_OK;
+        }
+
+        p += len;
+        n -= len;
+    }
+
+    return NGX_DECLINED;
+}
--- a/src/core/ngx_proxy_protocol.h
+++ b/src/core/ngx_proxy_protocol.h
@@ -21,6 +21,7 @@ struct ngx_proxy_protocol_s {
     ngx_str_t           dst_addr;
     in_port_t           src_port;
     in_port_t           dst_port;
+    ngx_str_t           tlvs;
 };
 
 
@@ -28,6 +29,8 @@ u_char *ngx_proxy_protocol_read(ngx_conn
     u_char *last);
 u_char *ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf,
     u_char *last);
+ngx_int_t ngx_proxy_protocol_get_tlv(ngx_connection_t *c, ngx_str_t *name,
+    ngx_str_t *value);
 
 
 #endif /* _NGX_PROXY_PROTOCOL_H_INCLUDED_ */
--- a/src/event/modules/ngx_iocp_module.c
+++ b/src/event/modules/ngx_iocp_module.c
@@ -231,9 +231,8 @@ ngx_iocp_del_connection(ngx_connection_t
 }
 
 
-static
-ngx_int_t ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
-    ngx_uint_t flags)
+static ngx_int_t
+ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
 {
     int                rc;
     u_int              key;
@@ -356,7 +355,7 @@ ngx_iocp_create_conf(ngx_cycle_t *cycle)
 
     cf = ngx_palloc(cycle->pool, sizeof(ngx_iocp_conf_t));
     if (cf == NULL) {
-        return NGX_CONF_ERROR;
+        return NULL;
     }
 
     cf->threads = NGX_CONF_UNSET;
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -71,10 +71,11 @@ static void ngx_ssl_session_rbtree_inser
     ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
 
 #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
-static int ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+static int ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
     unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
     HMAC_CTX *hctx, int enc);
-static void ngx_ssl_session_ticket_keys_cleanup(void *data);
+static ngx_int_t ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log);
+static void ngx_ssl_ticket_keys_cleanup(void *data);
 #endif
 
 #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
@@ -131,7 +132,7 @@ ngx_module_t  ngx_openssl_module = {
 int  ngx_ssl_connection_index;
 int  ngx_ssl_server_conf_index;
 int  ngx_ssl_session_cache_index;
-int  ngx_ssl_session_ticket_keys_index;
+int  ngx_ssl_ticket_keys_index;
 int  ngx_ssl_ocsp_index;
 int  ngx_ssl_certificate_index;
 int  ngx_ssl_next_certificate_index;
@@ -208,9 +209,9 @@ ngx_ssl_init(ngx_log_t *log)
         return NGX_ERROR;
     }
 
-    ngx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL,
-                                                                 NULL, NULL);
-    if (ngx_ssl_session_ticket_keys_index == -1) {
+    ngx_ssl_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                         NULL);
+    if (ngx_ssl_ticket_keys_index == -1) {
         ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                       "SSL_CTX_get_ex_new_index() failed");
         return NGX_ERROR;
@@ -1085,6 +1086,53 @@ ngx_ssl_info_callback(const ngx_ssl_conn
 
 #endif
 
+#ifdef TLS1_3_VERSION
+
+    if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP
+        && SSL_version(ssl_conn) == TLS1_3_VERSION)
+    {
+        time_t        now, time, timeout, conf_timeout;
+        SSL_SESSION  *sess;
+
+        /*
+         * OpenSSL with TLSv1.3 updates the session creation time on
+         * session resumption and keeps the session timeout unmodified,
+         * making it possible to maintain the session forever, bypassing
+         * client certificate expiration and revocation.  To make sure
+         * session timeouts are actually used, we now update the session
+         * creation time and reduce the session timeout accordingly.
+         *
+         * BoringSSL with TLSv1.3 ignores configured session timeouts
+         * and uses a hardcoded timeout instead, 7 days.  So we update
+         * session timeout to the configured value as soon as a session
+         * is created.
+         */
+
+        c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+        sess = SSL_get0_session(ssl_conn);
+
+        if (!c->ssl->session_timeout_set && sess) {
+            c->ssl->session_timeout_set = 1;
+
+            now = ngx_time();
+            time = SSL_SESSION_get_time(sess);
+            timeout = SSL_SESSION_get_timeout(sess);
+            conf_timeout = SSL_CTX_get_timeout(c->ssl->session_ctx);
+
+            timeout = ngx_min(timeout, conf_timeout);
+
+            if (now - time >= timeout) {
+                SSL_SESSION_set1_id_context(sess, (unsigned char *) "", 0);
+
+            } else {
+                SSL_SESSION_set_time(sess, now);
+                SSL_SESSION_set_timeout(sess, timeout - (now - time));
+            }
+        }
+    }
+
+#endif
+
     if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
         c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
 
@@ -1426,9 +1474,9 @@ ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_s
 
     SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
 
-#if SSL_CTRL_SET_ECDH_AUTO
+#ifdef SSL_CTRL_SET_ECDH_AUTO
     /* not needed in OpenSSL 1.1.0+ */
-    SSL_CTX_set_ecdh_auto(ssl->ctx, 1);
+    (void) SSL_CTX_set_ecdh_auto(ssl->ctx, 1);
 #endif
 
     if (ngx_strcmp(name->data, "auto") == 0) {
@@ -1769,7 +1817,7 @@ ngx_ssl_handshake(ngx_connection_t *c)
 #endif
 #endif
 
-#ifdef BIO_get_ktls_send
+#if (defined BIO_get_ktls_send && !NGX_WIN32)
 
         if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) {
             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -1914,7 +1962,7 @@ ngx_ssl_try_early_data(ngx_connection_t 
         c->read->ready = 1;
         c->write->ready = 1;
 
-#ifdef BIO_get_ktls_send
+#if (defined BIO_get_ktls_send && !NGX_WIN32)
 
         if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) {
             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -2943,7 +2991,7 @@ ngx_ssl_write_early(ngx_connection_t *c,
 static ssize_t
 ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size)
 {
-#ifdef BIO_get_ktls_send
+#if (defined BIO_get_ktls_send && !NGX_WIN32)
 
     int        sslerr, flags;
     ssize_t    n;
@@ -3430,6 +3478,9 @@ ngx_ssl_connection_error(ngx_connection_
 #ifdef SSL_R_VERSION_TOO_LOW
             || n == SSL_R_VERSION_TOO_LOW                            /*  396 */
 #endif
+#ifdef SSL_R_BAD_RECORD_TYPE
+            || n == SSL_R_BAD_RECORD_TYPE                            /*  443 */
+#endif
             || n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */
 #ifdef SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE
             || n == SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE             /* 1010 */
@@ -3774,6 +3825,12 @@ ngx_ssl_session_cache_init(ngx_shm_zone_
 
     ngx_queue_init(&cache->expire_queue);
 
+    cache->ticket_keys[0].expire = 0;
+    cache->ticket_keys[1].expire = 0;
+    cache->ticket_keys[2].expire = 0;
+
+    cache->fail_time = 0;
+
     len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
 
     shpool->log_ctx = ngx_slab_alloc(shpool, len);
@@ -3792,16 +3849,16 @@ ngx_ssl_session_cache_init(ngx_shm_zone_
 
 /*
  * The length of the session id is 16 bytes for SSLv2 sessions and
- * between 1 and 32 bytes for SSLv3/TLSv1, typically 32 bytes.
- * It seems that the typical length of the external ASN1 representation
- * of a session is 118 or 119 bytes for SSLv3/TSLv1.
+ * between 1 and 32 bytes for SSLv3 and TLS, typically 32 bytes.
+ * Typical length of the external ASN1 representation of a session
+ * is about 150 bytes plus SNI server name.
  *
- * Thus on 32-bit platforms we allocate separately an rbtree node,
- * a session id, and an ASN1 representation, they take accordingly
- * 64, 32, and 128 bytes.
+ * On 32-bit platforms we allocate an rbtree node, a session id, and
+ * an ASN1 representation in a single allocation, it typically takes
+ * 256 bytes.
  *
  * On 64-bit platforms we allocate separately an rbtree node + session_id,
- * and an ASN1 representation, they take accordingly 128 and 128 bytes.
+ * and an ASN1 representation, they take accordingly 128 and 256 bytes.
  *
  * OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
  * so they are outside the code locked by shared pool mutex
@@ -3811,7 +3868,8 @@ static int
 ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
 {
     int                       len;
-    u_char                   *p, *id, *cached_sess, *session_id;
+    u_char                   *p, *session_id;
+    size_t                    n;
     uint32_t                  hash;
     SSL_CTX                  *ssl_ctx;
     unsigned int              session_id_length;
@@ -3822,17 +3880,42 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
     ngx_ssl_session_cache_t  *cache;
     u_char                    buf[NGX_SSL_MAX_SESSION_SIZE];
 
+#ifdef TLS1_3_VERSION
+
+    /*
+     * OpenSSL tries to save TLSv1.3 sessions into session cache
+     * even when using tickets for stateless session resumption,
+     * "because some applications just want to know about the creation
+     * of a session"; do not cache such sessions
+     */
+
+    if (SSL_version(ssl_conn) == TLS1_3_VERSION
+        && (SSL_get_options(ssl_conn) & SSL_OP_NO_TICKET) == 0)
+    {
+        return 0;
+    }
+
+#endif
+
     len = i2d_SSL_SESSION(sess, NULL);
 
     /* do not cache too big session */
 
-    if (len > (int) NGX_SSL_MAX_SESSION_SIZE) {
+    if (len > NGX_SSL_MAX_SESSION_SIZE) {
         return 0;
     }
 
     p = buf;
     i2d_SSL_SESSION(sess, &p);
 
+    session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
+
+    /* do not cache sessions with too long session id */
+
+    if (session_id_length > 32) {
+        return 0;
+    }
+
     c = ngx_ssl_get_connection(ssl_conn);
 
     ssl_ctx = c->ssl->session_ctx;
@@ -3846,23 +3929,13 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
     /* drop one or two expired sessions */
     ngx_ssl_expire_sessions(cache, shpool, 1);
 
-    cached_sess = ngx_slab_alloc_locked(shpool, len);
-
-    if (cached_sess == NULL) {
-
-        /* drop the oldest non-expired session and try once more */
-
-        ngx_ssl_expire_sessions(cache, shpool, 0);
-
-        cached_sess = ngx_slab_alloc_locked(shpool, len);
-
-        if (cached_sess == NULL) {
-            sess_id = NULL;
-            goto failed;
-        }
-    }
-
-    sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+#if (NGX_PTR_SIZE == 8)
+    n = sizeof(ngx_ssl_sess_id_t);
+#else
+    n = offsetof(ngx_ssl_sess_id_t, session) + len;
+#endif
+
+    sess_id = ngx_slab_alloc_locked(shpool, n);
 
     if (sess_id == NULL) {
 
@@ -3870,41 +3943,34 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
 
         ngx_ssl_expire_sessions(cache, shpool, 0);
 
-        sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+        sess_id = ngx_slab_alloc_locked(shpool, n);
 
         if (sess_id == NULL) {
             goto failed;
         }
     }
 
-    session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
-
 #if (NGX_PTR_SIZE == 8)
 
-    id = sess_id->sess_id;
-
-#else
-
-    id = ngx_slab_alloc_locked(shpool, session_id_length);
-
-    if (id == NULL) {
+    sess_id->session = ngx_slab_alloc_locked(shpool, len);
+
+    if (sess_id->session == NULL) {
 
         /* drop the oldest non-expired session and try once more */
 
         ngx_ssl_expire_sessions(cache, shpool, 0);
 
-        id = ngx_slab_alloc_locked(shpool, session_id_length);
-
-        if (id == NULL) {
+        sess_id->session = ngx_slab_alloc_locked(shpool, len);
+
+        if (sess_id->session == NULL) {
             goto failed;
         }
     }
 
 #endif
 
-    ngx_memcpy(cached_sess, buf, len);
-
-    ngx_memcpy(id, session_id, session_id_length);
+    ngx_memcpy(sess_id->session, buf, len);
+    ngx_memcpy(sess_id->id, session_id, session_id_length);
 
     hash = ngx_crc32_short(session_id, session_id_length);
 
@@ -3914,9 +3980,7 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
 
     sess_id->node.key = hash;
     sess_id->node.data = (u_char) session_id_length;
-    sess_id->id = id;
     sess_id->len = len;
-    sess_id->session = cached_sess;
 
     sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
 
@@ -3930,18 +3994,17 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
 
 failed:
 
-    if (cached_sess) {
-        ngx_slab_free_locked(shpool, cached_sess);
-    }
-
     if (sess_id) {
         ngx_slab_free_locked(shpool, sess_id);
     }
 
     ngx_shmtx_unlock(&shpool->mutex);
 
-    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
-                  "could not allocate new session%s", shpool->log_ctx);
+    if (cache->fail_time != ngx_time()) {
+        cache->fail_time = ngx_time();
+        ngx_log_error(NGX_LOG_WARN, c->log, 0,
+                      "could not allocate new session%s", shpool->log_ctx);
+    }
 
     return 0;
 }
@@ -4027,9 +4090,10 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_
 
             ngx_rbtree_delete(&cache->session_rbtree, node);
 
+            ngx_explicit_memzero(sess_id->session, sess_id->len);
+
+#if (NGX_PTR_SIZE == 8)
             ngx_slab_free_locked(shpool, sess_id->session);
-#if (NGX_PTR_SIZE == 4)
-            ngx_slab_free_locked(shpool, sess_id->id);
 #endif
             ngx_slab_free_locked(shpool, sess_id);
 
@@ -4117,9 +4181,10 @@ ngx_ssl_remove_session(SSL_CTX *ssl, ngx
 
             ngx_rbtree_delete(&cache->session_rbtree, node);
 
+            ngx_explicit_memzero(sess_id->session, sess_id->len);
+
+#if (NGX_PTR_SIZE == 8)
             ngx_slab_free_locked(shpool, sess_id->session);
-#if (NGX_PTR_SIZE == 4)
-            ngx_slab_free_locked(shpool, sess_id->id);
 #endif
             ngx_slab_free_locked(shpool, sess_id);
 
@@ -4166,9 +4231,10 @@ ngx_ssl_expire_sessions(ngx_ssl_session_
 
         ngx_rbtree_delete(&cache->session_rbtree, &sess_id->node);
 
+        ngx_explicit_memzero(sess_id->session, sess_id->len);
+
+#if (NGX_PTR_SIZE == 8)
         ngx_slab_free_locked(shpool, sess_id->session);
-#if (NGX_PTR_SIZE == 4)
-        ngx_slab_free_locked(shpool, sess_id->id);
 #endif
         ngx_slab_free_locked(shpool, sess_id);
     }
@@ -4222,23 +4288,25 @@ ngx_ssl_session_rbtree_insert_value(ngx_
 ngx_int_t
 ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
 {
-    u_char                         buf[80];
-    size_t                         size;
-    ssize_t                        n;
-    ngx_str_t                     *path;
-    ngx_file_t                     file;
-    ngx_uint_t                     i;
-    ngx_array_t                   *keys;
-    ngx_file_info_t                fi;
-    ngx_pool_cleanup_t            *cln;
-    ngx_ssl_session_ticket_key_t  *key;
-
-    if (paths == NULL) {
+    u_char                 buf[80];
+    size_t                 size;
+    ssize_t                n;
+    ngx_str_t             *path;
+    ngx_file_t             file;
+    ngx_uint_t             i;
+    ngx_array_t           *keys;
+    ngx_file_info_t        fi;
+    ngx_pool_cleanup_t    *cln;
+    ngx_ssl_ticket_key_t  *key;
+
+    if (paths == NULL
+        && SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL)
+    {
         return NGX_OK;
     }
 
-    keys = ngx_array_create(cf->pool, paths->nelts,
-                            sizeof(ngx_ssl_session_ticket_key_t));
+    keys = ngx_array_create(cf->pool, paths ? paths->nelts : 3,
+                            sizeof(ngx_ssl_ticket_key_t));
     if (keys == NULL) {
         return NGX_ERROR;
     }
@@ -4248,9 +4316,41 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *
         return NGX_ERROR;
     }
 
-    cln->handler = ngx_ssl_session_ticket_keys_cleanup;
+    cln->handler = ngx_ssl_ticket_keys_cleanup;
     cln->data = keys;
 
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ticket_keys_index, keys) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx, ngx_ssl_ticket_key_callback)
+        == 0)
+    {
+        ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+                      "nginx was built with Session Tickets support, however, "
+                      "now it is linked dynamically to an OpenSSL library "
+                      "which has no tlsext support, therefore Session Tickets "
+                      "are not available");
+        return NGX_OK;
+    }
+
+    if (paths == NULL) {
+
+        /* placeholder for keys in shared memory */
+
+        key = ngx_array_push_n(keys, 3);
+        key[0].shared = 1;
+        key[0].expire = 0;
+        key[1].shared = 1;
+        key[1].expire = 0;
+        key[2].shared = 1;
+        key[2].expire = 0;
+
+        return NGX_OK;
+    }
+
     path = paths->elts;
     for (i = 0; i < paths->nelts; i++) {
 
@@ -4305,6 +4405,9 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *
             goto failed;
         }
 
+        key->shared = 0;
+        key->expire = 1;
+
         if (size == 48) {
             key->size = 48;
             ngx_memcpy(key->name, buf, 16);
@@ -4326,25 +4429,6 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *
         ngx_explicit_memzero(&buf, 80);
     }
 
-    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_ticket_keys_index, keys)
-        == 0)
-    {
-        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
-                      "SSL_CTX_set_ex_data() failed");
-        return NGX_ERROR;
-    }
-
-    if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx,
-                                         ngx_ssl_session_ticket_key_callback)
-        == 0)
-    {
-        ngx_log_error(NGX_LOG_WARN, cf->log, 0,
-                      "nginx was built with Session Tickets support, however, "
-                      "now it is linked dynamically to an OpenSSL library "
-                      "which has no tlsext support, therefore Session Tickets "
-                      "are not available");
-    }
-
     return NGX_OK;
 
 failed:
@@ -4361,29 +4445,33 @@ failed:
 
 
 static int
-ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
     unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
     HMAC_CTX *hctx, int enc)
 {
-    size_t                         size;
-    SSL_CTX                       *ssl_ctx;
-    ngx_uint_t                     i;
-    ngx_array_t                   *keys;
-    ngx_connection_t              *c;
-    ngx_ssl_session_ticket_key_t  *key;
-    const EVP_MD                  *digest;
-    const EVP_CIPHER              *cipher;
+    size_t                 size;
+    SSL_CTX               *ssl_ctx;
+    ngx_uint_t             i;
+    ngx_array_t           *keys;
+    ngx_connection_t      *c;
+    ngx_ssl_ticket_key_t  *key;
+    const EVP_MD          *digest;
+    const EVP_CIPHER      *cipher;
 
     c = ngx_ssl_get_connection(ssl_conn);
     ssl_ctx = c->ssl->session_ctx;
 
+    if (ngx_ssl_rotate_ticket_keys(ssl_ctx, c->log) != NGX_OK) {
+        return -1;
+    }
+
 #ifdef OPENSSL_NO_SHA256
     digest = EVP_sha1();
 #else
     digest = EVP_sha256();
 #endif
 
-    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_ticket_keys_index);
+    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
     if (keys == NULL) {
         return -1;
     }
@@ -4394,7 +4482,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
         /* encrypt session ticket */
 
         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "ssl session ticket encrypt, key: \"%*xs\" (%s session)",
+                       "ssl ticket encrypt, key: \"%*xs\" (%s session)",
                        (size_t) 16, key[0].name,
                        SSL_session_reused(ssl_conn) ? "reused" : "new");
 
@@ -4441,7 +4529,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
         }
 
         ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "ssl session ticket decrypt, key: \"%*xs\" not found",
+                       "ssl ticket decrypt, key: \"%*xs\" not found",
                        (size_t) 16, name);
 
         return 0;
@@ -4449,7 +4537,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
     found:
 
         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "ssl session ticket decrypt, key: \"%*xs\"%s",
+                       "ssl ticket decrypt, key: \"%*xs\"%s",
                        (size_t) 16, key[i].name, (i == 0) ? " (default)" : "");
 
         if (key[i].size == 48) {
@@ -4486,7 +4574,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
 
         /* renew if non-default key */
 
-        if (i != 0) {
+        if (i != 0 && key[i].expire) {
             return 2;
         }
 
@@ -4495,13 +4583,142 @@ ngx_ssl_session_ticket_key_callback(ngx_
 }
 
 
+static ngx_int_t
+ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log)
+{
+    time_t                    now, expire;
+    ngx_array_t              *keys;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_slab_pool_t          *shpool;
+    ngx_ssl_ticket_key_t     *key;
+    ngx_ssl_session_cache_t  *cache;
+    u_char                    buf[80];
+
+    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
+    if (keys == NULL) {
+        return NGX_OK;
+    }
+
+    key = keys->elts;
+
+    if (!key[0].shared) {
+        return NGX_OK;
+    }
+
+    /*
+     * if we don't need to update expiration of the current key
+     * and the previous key is still needed, don't sync with shared
+     * memory to save some work; in the worst case other worker process
+     * will switch to the next key, but this process will still be able
+     * to decrypt tickets encrypted with it
+     */
+
+    now = ngx_time();
+    expire = now + SSL_CTX_get_timeout(ssl_ctx);
+
+    if (key[0].expire >= expire && key[1].expire >= now) {
+        return NGX_OK;
+    }
+
+    shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    key = cache->ticket_keys;
+
+    if (key[0].expire == 0) {
+
+        /* initialize the current key */
+
+        if (RAND_bytes(buf, 80) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
+            ngx_shmtx_unlock(&shpool->mutex);
+            return NGX_ERROR;
+        }
+
+        key[0].shared = 1;
+        key[0].expire = expire;
+        key[0].size = 80;
+        ngx_memcpy(key[0].name, buf, 16);
+        ngx_memcpy(key[0].hmac_key, buf + 16, 32);
+        ngx_memcpy(key[0].aes_key, buf + 48, 32);
+
+        ngx_explicit_memzero(&buf, 80);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "ssl ticket key: \"%*xs\"",
+                       (size_t) 16, key[0].name);
+
+        /*
+         * copy the current key to the next key, as initialization of
+         * the previous key will replace the current key with the next
+         * key
+         */
+
+        key[2] = key[0];
+    }
+
+    if (key[1].expire < now) {
+
+        /*
+         * if the previous key is no longer needed (or not initialized),
+         * replace it with the current key, replace the current key with
+         * the next key, and generate new next key
+         */
+
+        key[1] = key[0];
+        key[0] = key[2];
+
+        if (RAND_bytes(buf, 80) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
+            ngx_shmtx_unlock(&shpool->mutex);
+            return NGX_ERROR;
+        }
+
+        key[2].shared = 1;
+        key[2].expire = 0;
+        key[2].size = 80;
+        ngx_memcpy(key[2].name, buf, 16);
+        ngx_memcpy(key[2].hmac_key, buf + 16, 32);
+        ngx_memcpy(key[2].aes_key, buf + 48, 32);
+
+        ngx_explicit_memzero(&buf, 80);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "ssl ticket key: \"%*xs\"",
+                       (size_t) 16, key[2].name);
+    }
+
+    /*
+     * update expiration of the current key: it is going to be needed
+     * at least till the session being created expires
+     */
+
+    if (expire > key[0].expire) {
+        key[0].expire = expire;
+    }
+
+    /* sync keys to the worker process memory */
+
+    ngx_memcpy(keys->elts, cache->ticket_keys,
+               2 * sizeof(ngx_ssl_ticket_key_t));
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    return NGX_OK;
+}
+
+
 static void
-ngx_ssl_session_ticket_keys_cleanup(void *data)
+ngx_ssl_ticket_keys_cleanup(void *data)
 {
     ngx_array_t  *keys = data;
 
     ngx_explicit_memzero(keys->elts,
-                         keys->nelts * sizeof(ngx_ssl_session_ticket_key_t));
+                         keys->nelts * sizeof(ngx_ssl_ticket_key_t));
 }
 
 #else
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -122,6 +122,7 @@ struct ngx_ssl_connection_s {
     unsigned                    no_send_shutdown:1;
     unsigned                    shutdown_without_free:1;
     unsigned                    handshake_buffer_set:1;
+    unsigned                    session_timeout_set:1;
     unsigned                    try_early_data:1;
     unsigned                    in_early:1;
     unsigned                    in_ocsp:1;
@@ -142,37 +143,37 @@ typedef struct ngx_ssl_sess_id_s  ngx_ss
 
 struct ngx_ssl_sess_id_s {
     ngx_rbtree_node_t           node;
-    u_char                     *id;
     size_t                      len;
-    u_char                     *session;
     ngx_queue_t                 queue;
     time_t                      expire;
+    u_char                      id[32];
 #if (NGX_PTR_SIZE == 8)
-    void                       *stub;
-    u_char                      sess_id[32];
+    u_char                     *session;
+#else
+    u_char                      session[1];
 #endif
 };
 
 
 typedef struct {
+    u_char                      name[16];
+    u_char                      hmac_key[32];
+    u_char                      aes_key[32];
+    time_t                      expire;
+    unsigned                    size:8;
+    unsigned                    shared:1;
+} ngx_ssl_ticket_key_t;
+
+
+typedef struct {
     ngx_rbtree_t                session_rbtree;
     ngx_rbtree_node_t           sentinel;
     ngx_queue_t                 expire_queue;
+    ngx_ssl_ticket_key_t        ticket_keys[3];
+    time_t                      fail_time;
 } ngx_ssl_session_cache_t;
 
 
-#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
-
-typedef struct {
-    size_t                      size;
-    u_char                      name[16];
-    u_char                      hmac_key[32];
-    u_char                      aes_key[32];
-} ngx_ssl_session_ticket_key_t;
-
-#endif
-
-
 #define NGX_SSL_SSLv2    0x0002
 #define NGX_SSL_SSLv3    0x0004
 #define NGX_SSL_TLSv1    0x0008
@@ -212,10 +213,12 @@ ngx_int_t ngx_ssl_ocsp(ngx_conf_t *cf, n
     ngx_uint_t depth, ngx_shm_zone_t *shm_zone);
 ngx_int_t ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
+
 ngx_int_t ngx_ssl_ocsp_validate(ngx_connection_t *c);
 ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s);
 void ngx_ssl_ocsp_cleanup(ngx_connection_t *c);
 ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data);
+
 ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file);
 ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf,
     ngx_array_t *passwords);
@@ -322,7 +325,7 @@ void ngx_ssl_cleanup_ctx(void *data);
 extern int  ngx_ssl_connection_index;
 extern int  ngx_ssl_server_conf_index;
 extern int  ngx_ssl_session_cache_index;
-extern int  ngx_ssl_session_ticket_keys_index;
+extern int  ngx_ssl_ticket_keys_index;
 extern int  ngx_ssl_ocsp_index;
 extern int  ngx_ssl_certificate_index;
 extern int  ngx_ssl_next_certificate_index;
--- a/src/http/modules/ngx_http_mp4_module.c
+++ b/src/http/modules/ngx_http_mp4_module.c
@@ -1121,6 +1121,12 @@ ngx_http_mp4_read_ftyp_atom(ngx_http_mp4
         return NGX_ERROR;
     }
 
+    if (mp4->ftyp_atom.buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 ftyp atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
 
     ftyp_atom = ngx_palloc(mp4->request->pool, atom_size);
@@ -1179,6 +1185,12 @@ ngx_http_mp4_read_moov_atom(ngx_http_mp4
         return NGX_DECLINED;
     }
 
+    if (mp4->moov_atom.buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 moov atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
 
     if (atom_data_size > mp4->buffer_size) {
@@ -1246,6 +1258,12 @@ ngx_http_mp4_read_mdat_atom(ngx_http_mp4
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom");
 
+    if (mp4->mdat_atom.buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 mdat atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     data = &mp4->mdat_data_buf;
     data->file = &mp4->file;
     data->in_file = 1;
@@ -1372,6 +1390,12 @@ ngx_http_mp4_read_mvhd_atom(ngx_http_mp4
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom");
 
+    if (mp4->mvhd_atom.buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 mvhd atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom_header = ngx_mp4_atom_header(mp4);
     mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header;
     mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header;
@@ -1637,6 +1661,13 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4
     atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 tkhd atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->tkhd_size = atom_size;
     trak->movie_duration = duration;
 
@@ -1676,6 +1707,12 @@ ngx_http_mp4_read_mdia_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 mdia atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->mdia_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -1799,6 +1836,13 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4
     atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 mdhd atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->mdhd_size = atom_size;
     trak->timescale = timescale;
     trak->duration = duration;
@@ -1862,6 +1906,12 @@ ngx_http_mp4_read_hdlr_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 hdlr atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->hdlr_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -1890,6 +1940,12 @@ ngx_http_mp4_read_minf_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_MINF_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 minf atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->minf_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -1933,6 +1989,15 @@ ngx_http_mp4_read_vmhd_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf
+        || trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 vmhd/smhd atom in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->vmhd_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -1964,6 +2029,15 @@ ngx_http_mp4_read_smhd_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf
+        || trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 vmhd/smhd atom in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->smhd_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -1995,6 +2069,12 @@ ngx_http_mp4_read_dinf_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_DINF_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 dinf atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->dinf_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -2023,6 +2103,12 @@ ngx_http_mp4_read_stbl_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_STBL_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stbl atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->stbl_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -2144,6 +2230,12 @@ ngx_http_mp4_read_stsd_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
 
+    if (trak->out[NGX_HTTP_MP4_STSD_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stsd atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     atom = &trak->stsd_atom_buf;
     atom->temporary = 1;
     atom->pos = atom_header;
@@ -2212,6 +2304,13 @@ ngx_http_mp4_read_stts_atom(ngx_http_mp4
     atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_STTS_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stts atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->time_to_sample_entries = entries;
 
     atom = &trak->stts_atom_buf;
@@ -2480,6 +2579,13 @@ ngx_http_mp4_read_stss_atom(ngx_http_mp4
                    "sync sample entries:%uD", entries);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_STSS_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stss atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->sync_samples_entries = entries;
 
     atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t);
@@ -2678,6 +2784,13 @@ ngx_http_mp4_read_ctts_atom(ngx_http_mp4
                    "composition offset entries:%uD", entries);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 ctts atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->composition_offset_entries = entries;
 
     atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t);
@@ -2881,6 +2994,13 @@ ngx_http_mp4_read_stsc_atom(ngx_http_mp4
     atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_STSC_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stsc atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->sample_to_chunk_entries = entries;
 
     atom = &trak->stsc_atom_buf;
@@ -3213,6 +3333,13 @@ ngx_http_mp4_read_stsz_atom(ngx_http_mp4
                    "sample uniform size:%uD, entries:%uD", size, entries);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf) {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stsz atom in \"%s\"", mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->sample_sizes_entries = entries;
 
     atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t);
@@ -3396,6 +3523,16 @@ ngx_http_mp4_read_stco_atom(ngx_http_mp4
     atom_end = atom_table + entries * sizeof(uint32_t);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_STCO_ATOM].buf
+        || trak->out[NGX_HTTP_MP4_CO64_ATOM].buf)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stco/co64 atom in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->chunks = entries;
 
     atom = &trak->stco_atom_buf;
@@ -3602,6 +3739,16 @@ ngx_http_mp4_read_co64_atom(ngx_http_mp4
     atom_end = atom_table + entries * sizeof(uint64_t);
 
     trak = ngx_mp4_last_trak(mp4);
+
+    if (trak->out[NGX_HTTP_MP4_STCO_ATOM].buf
+        || trak->out[NGX_HTTP_MP4_CO64_ATOM].buf)
+    {
+        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                      "duplicate mp4 stco/co64 atom in \"%s\"",
+                      mp4->file.name.data);
+        return NGX_ERROR;
+    }
+
     trak->chunks = entries;
 
     atom = &trak->co64_atom_buf;
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -1116,7 +1116,7 @@ ngx_http_ssl_session_cache(ngx_conf_t *c
                 len++;
             }
 
-            if (len == 0) {
+            if (len == 0 || j == value[i].len) {
                 goto invalid;
             }
 
@@ -1206,7 +1206,7 @@ ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, 
         len++;
     }
 
-    if (len == 0) {
+    if (len == 0 || j == value[1].len) {
         goto invalid;
     }
 
--- a/src/http/ngx_http_variables.c
+++ b/src/http/ngx_http_variables.c
@@ -61,6 +61,8 @@ static ngx_int_t ngx_http_variable_proxy
     ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_proxy_protocol_tlv(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r,
@@ -214,6 +216,10 @@ static ngx_http_variable_t  ngx_http_cor
       ngx_http_variable_proxy_protocol_port,
       offsetof(ngx_proxy_protocol_t, dst_port), 0, 0 },
 
+    { ngx_string("proxy_protocol_tlv_"), NULL,
+      ngx_http_variable_proxy_protocol_tlv,
+      0, NGX_HTTP_VAR_PREFIX, 0 },
+
     { ngx_string("server_addr"), NULL, ngx_http_variable_server_addr, 0, 0, 0 },
 
     { ngx_string("server_port"), NULL, ngx_http_variable_server_port, 0, 0, 0 },
@@ -1387,6 +1393,39 @@ ngx_http_variable_proxy_protocol_port(ng
 
 
 static ngx_int_t
+ngx_http_variable_proxy_protocol_tlv(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t *name = (ngx_str_t *) data;
+
+    ngx_int_t  rc;
+    ngx_str_t  tlv, value;
+
+    tlv.len = name->len - (sizeof("proxy_protocol_tlv_") - 1);
+    tlv.data = name->data + sizeof("proxy_protocol_tlv_") - 1;
+
+    rc = ngx_proxy_protocol_get_tlv(r->connection, &tlv, &value);
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_DECLINED) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->len = value.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = value.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
 ngx_http_variable_server_addr(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data)
 {
--- a/src/mail/ngx_mail_ssl_module.c
+++ b/src/mail/ngx_mail_ssl_module.c
@@ -682,7 +682,7 @@ ngx_mail_ssl_session_cache(ngx_conf_t *c
                 len++;
             }
 
-            if (len == 0) {
+            if (len == 0 || j == value[i].len) {
                 goto invalid;
             }
 
--- a/src/os/win32/ngx_win32_config.h
+++ b/src/os/win32/ngx_win32_config.h
@@ -80,8 +80,6 @@ typedef long  time_t;
 
 #pragma warning(default:4201)
 
-/* disable some "-W4" level warnings */
-
 /* 'type cast': from function pointer to data pointer */
 #pragma warning(disable:4054)
 
@@ -106,6 +104,9 @@ typedef long  time_t;
 /* array is too small to include a terminating null character */
 #pragma warning(disable:4295)
 
+/* conversion from 'type1' to 'type2' of greater size */
+#pragma warning(disable:4306)
+
 #endif
 
 
--- a/src/stream/ngx_stream_ssl_module.c
+++ b/src/stream/ngx_stream_ssl_module.c
@@ -1073,7 +1073,7 @@ ngx_stream_ssl_session_cache(ngx_conf_t 
                 len++;
             }
 
-            if (len == 0) {
+            if (len == 0 || j == value[i].len) {
                 goto invalid;
             }
 
--- a/src/stream/ngx_stream_variables.c
+++ b/src/stream/ngx_stream_variables.c
@@ -23,6 +23,8 @@ static ngx_int_t ngx_stream_variable_pro
     ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_stream_variable_proxy_protocol_port(
     ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_stream_variable_proxy_protocol_tlv(
+    ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_stream_variable_server_addr(ngx_stream_session_t *s,
     ngx_stream_variable_value_t *v, uintptr_t data);
 static ngx_int_t ngx_stream_variable_server_port(ngx_stream_session_t *s,
@@ -79,6 +81,10 @@ static ngx_stream_variable_t  ngx_stream
       ngx_stream_variable_proxy_protocol_port,
       offsetof(ngx_proxy_protocol_t, dst_port), 0, 0 },
 
+    { ngx_string("proxy_protocol_tlv_"), NULL,
+      ngx_stream_variable_proxy_protocol_tlv,
+      0, NGX_STREAM_VAR_PREFIX, 0 },
+
     { ngx_string("server_addr"), NULL,
       ngx_stream_variable_server_addr, 0, 0, 0 },
 
@@ -622,6 +628,39 @@ ngx_stream_variable_proxy_protocol_port(
 
 
 static ngx_int_t
+ngx_stream_variable_proxy_protocol_tlv(ngx_stream_session_t *s,
+    ngx_stream_variable_value_t *v, uintptr_t data)
+{
+    ngx_str_t *name = (ngx_str_t *) data;
+
+    ngx_int_t  rc;
+    ngx_str_t  tlv, value;
+
+    tlv.len = name->len - (sizeof("proxy_protocol_tlv_") - 1);
+    tlv.data = name->data + sizeof("proxy_protocol_tlv_") - 1;
+
+    rc = ngx_proxy_protocol_get_tlv(s->connection, &tlv, &value);
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_DECLINED) {
+        v->not_found = 1;
+        return NGX_OK;
+    }
+
+    v->len = value.len;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+    v->data = value.data;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
 ngx_stream_variable_server_addr(ngx_stream_session_t *s,
     ngx_stream_variable_value_t *v, uintptr_t data)
 {