]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
QUIC Conformance: Frame Handling Tests
authorHugo Landau <hlandau@openssl.org>
Tue, 6 Jun 2023 15:25:12 +0000 (16:25 +0100)
committerPauli <pauli@openssl.org>
Sun, 16 Jul 2023 22:17:57 +0000 (08:17 +1000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21135)

ssl/quic/quic_channel.c
ssl/quic/quic_rx_depack.c
ssl/quic/quic_wire.c
test/build.info
test/helpers/quictestlib.c
test/helpers/quictestlib.h
test/quic_multistream_test.c

index c0ce222d86c946ea10f792a95a25283df4ab9979..30b5faf24b1c38558eb417ba02160398308fa194 100644 (file)
@@ -2629,7 +2629,6 @@ void ossl_quic_channel_on_new_conn_id(QUIC_CHANNEL *ch,
         return;
 
     /* We allow only two active connection ids; first check some constraints */
-
     if (ch->cur_remote_dcid.id_len == 0) {
         /* Changing from 0 length connection id is disallowed */
         ossl_quic_channel_raise_protocol_error(ch,
index 11404f31461112a53cf05d37358dc7fe37c090c9..58d2fc459631a48038f6d5f6c2a10bb04bec229f 100644 (file)
@@ -515,21 +515,6 @@ static int depack_do_frame_stream(PACKET *pkt, QUIC_CHANNEL *ch,
         return 0;
     }
 
-    /*
-     * RFC 9000 s. 19.8: "The largest offset delivered on a stream -- the sum of
-     * the offset and data length -- cannot exceed 2**62 - 1, as it is not
-     * possible to provide flow control credit for that data. Receipt of a frame
-     * that exceeds this limit MUST be treated as a connection error of type
-     * FRAME_ENCODING_ERROR or FLOW_CONTROL_ERROR."
-     */
-    if (frame_data.offset + frame_data.len > (((uint64_t)1) << 62) - 1) {
-        ossl_quic_channel_raise_protocol_error(ch,
-                                               QUIC_ERR_FRAME_ENCODING_ERROR,
-                                               frame_type,
-                                               "oversize stream");
-        return 0;
-    }
-
     switch (stream->recv_state) {
     case QUIC_RSTREAM_STATE_RECV:
     case QUIC_RSTREAM_STATE_SIZE_KNOWN:
index 2412e9afa5c6cfea58a1f1d93b314f0eb58b1551..22214069ec2d12cd6f9c963e5896da2c1fb2be59 100644 (file)
@@ -318,7 +318,8 @@ int ossl_quic_wire_encode_frame_streams_blocked(WPACKET *pkt,
 int ossl_quic_wire_encode_frame_new_conn_id(WPACKET *pkt,
                                             const OSSL_QUIC_FRAME_NEW_CONN_ID *f)
 {
-    if (f->conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
+    if (f->conn_id.id_len < 1
+        || f->conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
         return 0;
 
     if (!encode_frame_hdr(pkt, OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID)
@@ -683,6 +684,14 @@ int ossl_quic_wire_decode_frame_stream(PACKET *pkt,
             f->len = PACKET_remaining(pkt);
     }
 
+    /*
+     * RFC 9000 s. 19.8: "The largest offset delivered on a stream -- the sum of
+     * the offset and data length -- cannot exceed 2**62 - 1, as it is not
+     * possible to provide flow control credit for that data."
+     */
+    if (f->offset + f->len > (((uint64_t)1) << 62) - 1)
+        return 0;
+
     if (nodata) {
         f->data = NULL;
     } else {
@@ -774,6 +783,7 @@ int ossl_quic_wire_decode_frame_new_conn_id(PACKET *pkt,
             || !PACKET_get_quic_vlint(pkt, &f->retire_prior_to)
             || f->seq_num < f->retire_prior_to
             || !PACKET_get_1(pkt, &len)
+            || len < 1
             || len > QUIC_MAX_CONN_ID_LEN)
         return 0;
 
index d61d3a5f471af4d19d6e92254ac72077623d9e2e..57bf3cea6396bed8596e39635697f2db52007059 100644 (file)
@@ -339,7 +339,7 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[quic_client_test]=../include ../apps/include
   DEPEND[quic_client_test]=../libcrypto.a ../libssl.a libtestutil.a
 
-  SOURCE[quic_multistream_test]=quic_multistream_test.c
+  SOURCE[quic_multistream_test]=quic_multistream_test.c helpers/ssltestlib.c helpers/quictestlib.c
   INCLUDE[quic_multistream_test]=../include ../apps/include
   DEPEND[quic_multistream_test]=../libcrypto.a ../libssl.a libtestutil.a
 
index fcff11727aa74826419e01ab881e215c5adb5c02..0d99d4556c4b9c22f9c7215225d0f13eca04c31a 100644 (file)
@@ -64,8 +64,6 @@ struct qtest_fault {
 static void packet_plain_finish(void *arg);
 static void handshake_finish(void *arg);
 
-static BIO_METHOD *get_bio_method(void);
-
 static OSSL_TIME fake_now;
 
 static OSSL_TIME fake_now_cb(void *arg)
@@ -155,7 +153,7 @@ int qtest_create_quic_objects(OSSL_LIB_CTX *libctx, SSL_CTX *clientctx,
             goto err;
     }
 
-    fisbio = BIO_new(get_bio_method());
+    fisbio = BIO_new(qtest_get_bio_method());
     if (!TEST_ptr(fisbio))
         goto err;
 
@@ -206,6 +204,19 @@ void qtest_add_time(uint64_t millis)
     fake_now = ossl_time_add(fake_now, ossl_ms2time(millis));
 }
 
+QTEST_FAULT *qtest_create_injector(QUIC_TSERVER *ts)
+{
+    QTEST_FAULT *f;
+
+    f = OPENSSL_zalloc(sizeof(*f));
+    if (f == NULL)
+        return NULL;
+
+    f->qtserv = ts;
+    return f;
+
+}
+
 int qtest_supports_blocking(void)
 {
 #if !defined(OPENSSL_NO_POSIX_IO) && defined(OPENSSL_THREADS) && !defined(CRYPTO_TDEBUG)
@@ -487,7 +498,7 @@ int qtest_fault_resize_plain_packet(QTEST_FAULT *fault, size_t newlen)
  * Prepend frame data into a packet. To be called from a packet_plain_listener
  * callback
  */
-int qtest_fault_prepend_frame(QTEST_FAULT *fault, unsigned char *frame,
+int qtest_fault_prepend_frame(QTEST_FAULT *fault, const unsigned char *frame,
                               size_t frame_len)
 {
     unsigned char *buf;
@@ -819,7 +830,7 @@ static long pcipher_ctrl(BIO *b, int cmd, long larg, void *parg)
     return BIO_ctrl(next, cmd, larg, parg);
 }
 
-static BIO_METHOD *get_bio_method(void)
+BIO_METHOD *qtest_get_bio_method(void)
 {
     BIO_METHOD *tmp;
 
index 8b24ab600963ef67a27f31ca088ba6f7cb5a9ca5..ee047bde0b5feeaa035631319f6b88e125789869 100644 (file)
@@ -44,6 +44,10 @@ int qtest_create_quic_objects(OSSL_LIB_CTX *libctx, SSL_CTX *clientctx,
 /* Where QTEST_FLAG_FAKE_TIME is used, add millis to the current time */
 void qtest_add_time(uint64_t millis);
 
+QTEST_FAULT *qtest_create_injector(QUIC_TSERVER *ts);
+
+BIO_METHOD *qtest_get_bio_method(void);
+
 /*
  * Free up a Fault Injector instance
  */
@@ -108,7 +112,7 @@ int qtest_fault_resize_plain_packet(QTEST_FAULT *fault, size_t newlen);
  * Prepend frame data into a packet. To be called from a packet_plain_listener
  * callback
  */
-int qtest_fault_prepend_frame(QTEST_FAULT *fault, unsigned char *frame,
+int qtest_fault_prepend_frame(QTEST_FAULT *fault, const unsigned char *frame,
                               size_t frame_len);
 
 /*
index ed17ebd33d7acc83087458e80f20980747ed0395..b0d0e85a4ae734560749d711006e8a46800f7a5d 100644 (file)
@@ -12,7 +12,9 @@
 #include <openssl/lhash.h>
 #include "internal/quic_tserver.h"
 #include "internal/quic_ssl.h"
+#include "internal/quic_error.h"
 #include "testutil.h"
+#include "helpers/quictestlib.h"
 #if defined(OPENSSL_THREADS)
 # include "internal/thread_arch.h"
 #endif
@@ -42,7 +44,7 @@ DEFINE_LHASH_OF_EX(STREAM_INFO);
 
 struct helper {
     int                     s_fd;
-    BIO                     *s_net_bio, *s_net_bio_own;
+    BIO                     *s_net_bio, *s_net_bio_own, *s_qtf_wbio, *s_qtf_wbio_own;
     BIO_ADDR                *s_net_bio_addr;
     QUIC_TSERVER            *s;
     LHASH_OF(STREAM_INFO)   *s_streams;
@@ -68,8 +70,14 @@ struct helper {
     CRYPTO_RWLOCK   *time_lock;
     OSSL_TIME       time_slip; /* protected by time_lock */
 
+    QTEST_FAULT     *qtf;
+
     int             init, blocking, check_spin_again;
-    int             free_order;
+    int             free_order, need_injector;
+
+    int (*qtf_packet_plain_cb)(struct helper *h, QUIC_PKT_HDR *hdr,
+                               unsigned char *buf, size_t buf_len);
+    uint64_t inject_word0, inject_word1;
 };
 
 struct helper_local {
@@ -85,6 +93,8 @@ struct script_op {
     int             (*check_func)(struct helper *h, const struct script_op *op);
     const char      *stream_name;
     uint64_t        arg2;
+    int             (*qtf_packet_plain_cb)(struct helper *h, QUIC_PKT_HDR *hdr,
+                                           unsigned char *buf, size_t buf_len);
 };
 
 #define OPK_END                                     0
@@ -129,6 +139,8 @@ struct script_op {
 #define OPK_EXPECT_ERR_LIB                          39
 #define OPK_SLEEP                                   40
 #define OPK_S_READ_FAIL                             41
+#define OPK_S_SET_INJECT_PLAIN                      42
+#define OPK_SET_INJECT_WORD                         43
 
 #define EXPECT_CONN_CLOSE_APP       (1U << 0)
 #define EXPECT_CONN_CLOSE_REMOTE    (1U << 1)
@@ -152,6 +164,8 @@ struct script_op {
     {OPK_C_SET_ALPN, (alpn), 0, NULL, NULL},
 #define OP_C_CONNECT_WAIT() \
     {OPK_C_CONNECT_WAIT, NULL, 0, NULL, NULL},
+#define OP_C_CONNECT_WAIT_OR_FAIL() \
+    {OPK_C_CONNECT_WAIT, NULL, 1, NULL, NULL},
 #define OP_C_WRITE(stream_name, buf, buf_len)   \
     {OPK_C_WRITE, (buf), (buf_len), NULL, #stream_name},
 #define OP_S_WRITE(stream_name, buf, buf_len)   \
@@ -238,6 +252,10 @@ struct script_op {
     {OPK_EXPECT_ERR_LIB, NULL, (lib)},
 #define OP_SLEEP(ms) \
     {OPK_SLEEP, NULL, 0, NULL, NULL, (ms)},
+#define OP_S_SET_INJECT_PLAIN(f) \
+    {OPK_S_SET_INJECT_PLAIN, NULL, 0, NULL, NULL, 0, (f)},
+#define OP_SET_INJECT_WORD(w0, w1) \
+    {OPK_SET_INJECT_WORD, NULL, (w0), NULL, NULL, (w1), NULL},
 
 static OSSL_TIME get_time(void *arg)
 {
@@ -437,6 +455,9 @@ static void helper_cleanup(struct helper *h)
     BIO_free(h->c_net_bio_own);
     h->c_net_bio_own = NULL;
 
+    BIO_free(h->s_qtf_wbio_own);
+    h->s_qtf_wbio_own = NULL;
+
     if (h->s_fd >= 0) {
         BIO_closesocket(h->s_fd);
         h->s_fd = -1;
@@ -457,7 +478,7 @@ static void helper_cleanup(struct helper *h)
     h->time_lock = NULL;
 }
 
-static int helper_init(struct helper *h, int free_order)
+static int helper_init(struct helper *h, int free_order, int need_injector)
 {
     short port = 8186;
     struct in_addr ina = {0};
@@ -467,6 +488,7 @@ static int helper_init(struct helper *h, int free_order)
     h->c_fd = -1;
     h->s_fd = -1;
     h->free_order = free_order;
+    h->need_injector = need_injector;
     h->time_slip = ossl_time_zero();
 
     if (!TEST_ptr(h->time_lock = CRYPTO_THREAD_lock_new()))
@@ -508,8 +530,20 @@ static int helper_init(struct helper *h, int free_order)
     if (!BIO_up_ref(h->s_net_bio))
         goto err;
 
+    if (need_injector) {
+        h->s_qtf_wbio = h->s_qtf_wbio_own = BIO_new(qtest_get_bio_method());
+        if (!TEST_ptr(h->s_qtf_wbio))
+            goto err;
+
+        if (!TEST_ptr(BIO_push(h->s_qtf_wbio, h->s_net_bio)))
+            goto err;
+
+        s_args.net_wbio = h->s_qtf_wbio;
+    } else {
+        s_args.net_wbio = h->s_net_bio;
+    }
+
     s_args.net_rbio     = h->s_net_bio;
-    s_args.net_wbio     = h->s_net_bio;
     s_args.alpn         = NULL;
     s_args.now_cb       = get_time;
     s_args.now_cb_arg   = h;
@@ -517,7 +551,16 @@ static int helper_init(struct helper *h, int free_order)
     if (!TEST_ptr(h->s = ossl_quic_tserver_new(&s_args, certfile, keyfile)))
         goto err;
 
-    h->s_net_bio_own = NULL;
+    if (need_injector) {
+        h->qtf = qtest_create_injector(h->s);
+        if (!TEST_ptr(h->qtf))
+            goto err;
+
+        BIO_set_data(h->s_qtf_wbio, h->qtf);
+    }
+
+    h->s_net_bio_own    = NULL;
+    h->s_qtf_wbio_own   = NULL;
 
     h->c_fd = BIO_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0);
     if (!TEST_int_ge(h->c_fd, 0))
@@ -532,7 +575,6 @@ static int helper_init(struct helper *h, int free_order)
     if (!TEST_true(BIO_dgram_set_peer(h->c_net_bio, h->s_net_bio_addr)))
         goto err;
 
-
     if (!TEST_ptr(h->c_ctx = SSL_CTX_new(OSSL_QUIC_client_method())))
         goto err;
 
@@ -683,6 +725,15 @@ static uint64_t helper_get_s_stream(struct helper *h, const char *stream_name)
     return info->s_stream_id;
 }
 
+static int helper_packet_plain_listener(QTEST_FAULT *qtf, QUIC_PKT_HDR *hdr,
+                                        unsigned char *buf, size_t buf_len,
+                                        void *arg)
+{
+    struct helper *h = arg;
+
+    return h->qtf_packet_plain_cb(h, hdr, buf, buf_len);
+}
+
 static int is_want(SSL *s, int ret)
 {
     int ec = SSL_get_error(s, ret);
@@ -883,11 +934,11 @@ static int run_script_worker(struct helper *h, const struct script_op *script,
                 connect_started = 1;
 
                 ret = SSL_connect(h->c_conn);
-                if (!TEST_true(ret == 1
+                if (!TEST_true((ret == 1 || op->arg1 > 0)
                                || (!h->blocking && is_want(h->c_conn, ret))))
                     goto out;
 
-                if (!h->blocking && ret != 1)
+                if (!h->blocking && ret < 0)
                     SPIN_AGAIN();
             }
             break;
@@ -1423,6 +1474,22 @@ static int run_script_worker(struct helper *h, const struct script_op *script,
             }
             break;
 
+        case OPK_S_SET_INJECT_PLAIN:
+            h->qtf_packet_plain_cb = op->qtf_packet_plain_cb;
+
+            if (!TEST_true(qtest_fault_set_packet_plain_listener(h->qtf,
+                                                                 h->qtf_packet_plain_cb != NULL ?
+                                                                 helper_packet_plain_listener : NULL,
+                                                                 h)))
+                goto out;
+
+            break;
+
+        case OPK_SET_INJECT_WORD:
+            h->inject_word0 = op->arg1;
+            h->inject_word1 = op->arg2;
+            break;
+
         default:
             TEST_error("unknown op");
             goto out;
@@ -1453,7 +1520,7 @@ static int run_script(const struct script_op *script, int free_order)
     int testresult = 0;
     struct helper h;
 
-    if (!TEST_true(helper_init(&h, free_order)))
+    if (!TEST_true(helper_init(&h, free_order, 1)))
         goto out;
 
     if (!TEST_true(run_script_worker(&h, script, -1)))
@@ -2116,6 +2183,589 @@ static const struct script_op script_20[] = {
     OP_END
 };
 
+/* 21. Fault injection - unknown frame in 1-RTT packet */
+static int script_21_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    WPACKET wpkt;
+    unsigned char frame_buf[8];
+    size_t written;
+
+    if (h->inject_word0 == 0 || hdr->type != h->inject_word0)
+        return 1;
+
+    if (!TEST_true(WPACKET_init_static_len(&wpkt, frame_buf,
+                                           sizeof(frame_buf), 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_quic_write_vlint(&wpkt, h->inject_word1)))
+        return 0;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &written)))
+        return 0;
+
+    if (!qtest_fault_prepend_frame(h->qtf, frame_buf, written))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_21[] = {
+    OP_S_SET_INJECT_PLAIN   (script_21_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (QUIC_PKT_TYPE_1RTT, OSSL_QUIC_VLINT_MAX)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
+/* 22. Fault injection - non-zero packet header reserved bits */
+static int script_22_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    if (h->inject_word0 == 0)
+        return 1;
+
+    hdr->reserved = 1;
+    return 1;
+}
+
+static const struct script_op script_22[] = {
+    OP_S_SET_INJECT_PLAIN   (script_22_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (1, 0)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_PROTOCOL_VIOLATION,0,0)
+
+    OP_END
+};
+
+/* 23. Fault injection - empty NEW_TOKEN */
+static int script_23_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    WPACKET wpkt;
+    unsigned char frame_buf[16];
+    size_t written;
+
+    if (h->inject_word0 == 0)
+        return 1;
+
+    if (!TEST_true(WPACKET_init_static_len(&wpkt, frame_buf,
+                                           sizeof(frame_buf), 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_quic_write_vlint(&wpkt, OSSL_QUIC_FRAME_TYPE_NEW_TOKEN))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &written)))
+        return 0;
+
+    if (!qtest_fault_prepend_frame(h->qtf, frame_buf, written))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_23[] = {
+    OP_S_SET_INJECT_PLAIN   (script_23_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (1, 0)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
+/* 24. Fault injection - excess value of MAX_STREAMS_BIDI */
+static int script_24_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    WPACKET wpkt;
+    unsigned char frame_buf[16];
+    size_t written;
+
+    if (h->inject_word0 == 0)
+        return 1;
+
+    if (!TEST_true(WPACKET_init_static_len(&wpkt, frame_buf,
+                                           sizeof(frame_buf), 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_quic_write_vlint(&wpkt, h->inject_word1))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, (((uint64_t)1) << 60) + 1)))
+        return 0;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &written)))
+        return 0;
+
+    if (!qtest_fault_prepend_frame(h->qtf, frame_buf, written))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_24[] = {
+    OP_S_SET_INJECT_PLAIN   (script_24_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (1, OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
+/* 25. Fault injection - excess value of MAX_STREAMS_UNI */
+static const struct script_op script_25[] = {
+    OP_S_SET_INJECT_PLAIN   (script_24_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (1, OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
+/* 26. Fault injection - excess value of STREAMS_BLOCKED_BIDI */
+static const struct script_op script_26[] = {
+    OP_S_SET_INJECT_PLAIN   (script_24_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (1, OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_BIDI)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_LIMIT_ERROR,0,0)
+
+    OP_END
+};
+
+/* 27. Fault injection - excess value of STREAMS_BLOCKED_UNI */
+static const struct script_op script_27[] = {
+    OP_S_SET_INJECT_PLAIN   (script_24_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (1, OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_UNI)
+
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_LIMIT_ERROR,0,0)
+
+    OP_END
+};
+
+/* 28. Fault injection - received RESET_STREAM for send-only stream */
+static int script_28_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    WPACKET wpkt;
+    unsigned char frame_buf[32];
+    size_t written;
+
+    if (h->inject_word0 == 0)
+        return 1;
+
+    if (!TEST_true(WPACKET_init_static_len(&wpkt, frame_buf,
+                                           sizeof(frame_buf), 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_quic_write_vlint(&wpkt, h->inject_word1))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, /* stream ID */
+                                               h->inject_word0 - 1))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, 123))
+        || (h->inject_word1 == OSSL_QUIC_FRAME_TYPE_RESET_STREAM
+           && !TEST_true(WPACKET_quic_write_vlint(&wpkt, 5)))) /* final size */
+        return 0;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &written)))
+        return 0;
+
+    if (!qtest_fault_prepend_frame(h->qtf, frame_buf, written))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_28[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_C_NEW_STREAM_UNI     (a, C_UNI_ID(0))
+    OP_C_WRITE              (a, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_UNI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_UNI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_RESET_STREAM)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 29. Fault injection - received RESET_STREAM for nonexistent send-only stream */
+static const struct script_op script_29[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_C_NEW_STREAM_UNI     (a, C_UNI_ID(0))
+    OP_C_WRITE              (a, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_UNI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_UNI_ID(1) + 1, OSSL_QUIC_FRAME_TYPE_RESET_STREAM)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 30. Fault injection - received STOP_SENDING for receive-only stream */
+static const struct script_op script_30[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_S_NEW_STREAM_UNI     (a, S_UNI_ID(0))
+    OP_S_WRITE              (a, "apple", 5)
+
+    OP_C_ACCEPT_STREAM_WAIT (a)
+    OP_C_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (S_UNI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_STOP_SENDING)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 31. Fault injection - received STOP_SENDING for nonexistent receive-only stream */
+static const struct script_op script_31[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_S_NEW_STREAM_UNI     (a, S_UNI_ID(0))
+    OP_S_WRITE              (a, "apple", 5)
+
+    OP_C_ACCEPT_STREAM_WAIT (a)
+    OP_C_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_UNI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_STOP_SENDING)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 32. Fault injection - STREAM frame for nonexistent stream */
+static int script_32_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    WPACKET wpkt;
+    unsigned char frame_buf[64];
+    size_t written;
+    uint64_t type = OSSL_QUIC_FRAME_TYPE_STREAM_OFF_LEN, offset, flen, i;
+
+    switch (h->inject_word1) {
+    default:
+        return 0;
+    case 0:
+        return 1;
+    case 1:
+        offset  = 0;
+        flen     = 0;
+        break;
+    case 2:
+        offset  = (((uint64_t)1)<<62) - 1;
+        flen     = 5;
+        break;
+    case 3:
+        offset  = 1 * 1024 * 1024 * 1024; /* 1G */
+        flen     = 5;
+        break;
+    }
+
+    if (!TEST_true(WPACKET_init_static_len(&wpkt, frame_buf,
+                                           sizeof(frame_buf), 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_quic_write_vlint(&wpkt, type))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, /* stream ID */
+                                               h->inject_word0 - 1))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, offset))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, flen)))
+        return 0;
+
+    for (i = 0; i < flen; ++i)
+        if (!TEST_true(WPACKET_put_bytes_u8(&wpkt, 0x42)))
+            return 0;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &written)))
+        return 0;
+
+    if (!qtest_fault_prepend_frame(h->qtf, frame_buf, written))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_32[] = {
+    OP_S_SET_INJECT_PLAIN   (script_32_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_S_NEW_STREAM_UNI     (a, S_UNI_ID(0))
+    OP_S_WRITE              (a, "apple", 5)
+
+    OP_C_ACCEPT_STREAM_WAIT (a)
+    OP_C_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_UNI_ID(0) + 1, 1)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 33. Fault injection - STREAM frame with illegal offset */
+static const struct script_op script_33[] = {
+    OP_S_SET_INJECT_PLAIN   (script_32_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_C_NEW_STREAM_BIDI    (a, C_BIDI_ID(0))
+    OP_C_WRITE              (a, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_BIDI_ID(0) + 1, 2)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
+/* 34. Fault injection - STREAM frame which exceeds FC */
+static const struct script_op script_34[] = {
+    OP_S_SET_INJECT_PLAIN   (script_32_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_C_NEW_STREAM_BIDI    (a, C_BIDI_ID(0))
+    OP_C_WRITE              (a, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_BIDI_ID(0) + 1, 3)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FLOW_CONTROL_ERROR,0,0)
+
+    OP_END
+};
+
+/* 35. Fault injection - MAX_STREAM_DATA for receive-only stream */
+static const struct script_op script_35[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_S_NEW_STREAM_UNI     (a, S_UNI_ID(0))
+    OP_S_WRITE              (a, "apple", 5)
+
+    OP_C_ACCEPT_STREAM_WAIT (a)
+    OP_C_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (S_UNI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 36. Fault injection - MAX_STREAM_DATA for nonexistent stream */
+static const struct script_op script_36[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_S_NEW_STREAM_UNI     (a, S_UNI_ID(0))
+    OP_S_WRITE              (a, "apple", 5)
+
+    OP_C_ACCEPT_STREAM_WAIT (a)
+    OP_C_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (C_BIDI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA)
+    OP_S_WRITE              (a, "orange", 6)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 37. Fault injection - STREAM_DATA_BLOCKED for send-only stream */
+static const struct script_op script_37[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_C_NEW_STREAM_UNI     (a, C_UNI_ID(0))
+    OP_C_WRITE              (a, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_UNI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_S_NEW_STREAM_UNI     (b, S_UNI_ID(0))
+    OP_SET_INJECT_WORD      (C_UNI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_STREAM_DATA_BLOCKED)
+    OP_S_WRITE              (b, "orange", 5)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 38. Fault injection - STREAM_DATA_BLOCKED for non-existent stream */
+static const struct script_op script_38[] = {
+    OP_S_SET_INJECT_PLAIN   (script_28_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_S_NEW_STREAM_UNI     (b, S_UNI_ID(0))
+    OP_SET_INJECT_WORD      (C_BIDI_ID(0) + 1, OSSL_QUIC_FRAME_TYPE_STREAM_DATA_BLOCKED)
+    OP_S_WRITE              (b, "orange", 5)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_STREAM_STATE_ERROR,0,0)
+
+    OP_END
+};
+
+/* 39. Fault injection - NEW_CONN_ID with zero-len CID */
+static int script_39_inject_plain(struct helper *h, QUIC_PKT_HDR *hdr,
+                                  unsigned char *buf, size_t len)
+{
+    WPACKET wpkt;
+    unsigned char frame_buf[64];
+    size_t i, written;
+
+    if (h->inject_word1 == 0)
+        return 1;
+
+    if (!TEST_true(WPACKET_init_static_len(&wpkt, frame_buf,
+                                           sizeof(frame_buf), 0)))
+        return 0;
+
+    if (!TEST_true(WPACKET_quic_write_vlint(&wpkt, OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID))
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, 0)) /* seq no */
+        || !TEST_true(WPACKET_quic_write_vlint(&wpkt, 0)) /* retire prior to */
+        || !TEST_true(WPACKET_put_bytes_u8(&wpkt, 0))) /* len */
+        return 0;
+
+    for (i = 0; i < QUIC_STATELESS_RESET_TOKEN_LEN; ++i)
+        if (!TEST_true(WPACKET_put_bytes_u8(&wpkt, 0x42)))
+            return 0;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &written)))
+        return 0;
+
+    if (!qtest_fault_prepend_frame(h->qtf, frame_buf, written))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_39[] = {
+    OP_S_SET_INJECT_PLAIN   (script_39_inject_plain)
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+    OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+    OP_C_NEW_STREAM_BIDI    (a, C_BIDI_ID(0))
+    OP_C_WRITE              (a, "apple", 5)
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_SET_INJECT_WORD      (0, 1)
+    OP_S_WRITE              (a, "orange", 5)
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
 static const struct script_op *const scripts[] = {
     script_1,
     script_2,
@@ -2137,6 +2787,24 @@ static const struct script_op *const scripts[] = {
     script_18,
     script_19,
     script_20,
+    script_21,
+    script_22,
+    script_23,
+    script_24,
+    script_25,
+    script_26,
+    script_27,
+    script_28,
+    script_29,
+    script_30,
+    script_31,
+    script_32,
+    script_33,
+    script_34,
+    script_35,
+    script_36,
+    script_37,
+    script_38,
 };
 
 static int test_script(int idx)
@@ -2148,6 +2816,86 @@ static int test_script(int idx)
     return run_script(scripts[script_idx], free_order);
 }
 
+/* Dynamically generated tests. */
+static struct script_op dyn_frame_types_script[] = {
+    OP_S_SET_INJECT_PLAIN   (script_19_inject_plain)
+    OP_SET_INJECT_WORD      (0, 0) /* dynamic */
+
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT_OR_FAIL()
+
+    OP_C_EXPECT_CONN_CLOSE_INFO(QUIC_ERR_FRAME_ENCODING_ERROR,0,0)
+
+    OP_END
+};
+
+struct forbidden_frame_type {
+    uint64_t pkt_type, frame_type, expected_err;
+};
+
+static const struct forbidden_frame_type forbidden_frame_types[] = {
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_VLINT_MAX, QUIC_ERR_FRAME_ENCODING_ERROR },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_VLINT_MAX, QUIC_ERR_FRAME_ENCODING_ERROR },
+    { QUIC_PKT_TYPE_1RTT, OSSL_QUIC_VLINT_MAX, QUIC_ERR_FRAME_ENCODING_ERROR },
+
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_STREAM, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_RESET_STREAM, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_STOP_SENDING, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_NEW_TOKEN, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_MAX_DATA, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_DATA_BLOCKED, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_STREAM_DATA_BLOCKED, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_BIDI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_UNI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_RETIRE_CONN_ID, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_PATH_CHALLENGE, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_APP, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_INITIAL, OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE, QUIC_ERR_PROTOCOL_VIOLATION },
+
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_STREAM, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_RESET_STREAM, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_STOP_SENDING, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_NEW_TOKEN, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_MAX_DATA, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_DATA_BLOCKED, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_STREAM_DATA_BLOCKED, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_BIDI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_STREAMS_BLOCKED_UNI, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_RETIRE_CONN_ID, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_PATH_CHALLENGE, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_APP, QUIC_ERR_PROTOCOL_VIOLATION },
+    { QUIC_PKT_TYPE_HANDSHAKE, OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE, QUIC_ERR_PROTOCOL_VIOLATION },
+
+    /* Client uses a zero-length CID so this is not allowed. */
+    { QUIC_PKT_TYPE_1RTT, OSSL_QUIC_FRAME_TYPE_RETIRE_CONN_ID, QUIC_ERR_PROTOCOL_VIOLATION },
+};
+
+static ossl_unused int test_dyn_frame_types(int idx)
+{
+    size_t i;
+    struct script_op *s = dyn_frame_types_script;
+
+    for (i = 0; i < OSSL_NELEM(dyn_frame_types_script); ++i)
+        if (s[i].op == OPK_SET_INJECT_WORD) {
+            s[i].arg1 = forbidden_frame_types[idx].pkt_type;
+            s[i].arg2 = forbidden_frame_types[idx].frame_type;
+        } else if (s[i].op == OPK_C_EXPECT_CONN_CLOSE_INFO) {
+            s[i].arg2 = forbidden_frame_types[idx].expected_err;
+        }
+
+    return run_script(dyn_frame_types_script, 0);
+}
+
 OPT_TEST_DECLARE_USAGE("certfile privkeyfile\n")
 
 int setup_tests(void)
@@ -2161,6 +2909,7 @@ int setup_tests(void)
         || !TEST_ptr(keyfile = test_get_argument(1)))
         return 0;
 
+    ADD_ALL_TESTS(test_dyn_frame_types, OSSL_NELEM(forbidden_frame_types));
     ADD_ALL_TESTS(test_script, OSSL_NELEM(scripts) * 2);
     return 1;
 }