return queue_put(&c2s, b, l);
}
+static void write_u16(uint8_t *p, uint16_t val)
+{
+ p[0] = val >> 8;
+ p[1] = val & 0xff;
+}
+
+static void write_u24(uint8_t *p, uint32_t val)
+{
+ p[0] = (val >> 16) & 0xff;
+ p[1] = (val >> 8) & 0xff;
+ p[2] = val & 0xff;
+}
+
+static void write_u48(uint8_t *p, uint64_t seq)
+{
+ int i;
+ for (i = 5; i >= 0; i--) {
+ p[i] = seq & 0xff;
+ seq >>= 8;
+ }
+}
+
+static uint64_t read_u48(const uint8_t *p)
+{
+ uint64_t seq = 0;
+ int i;
+ for (i = 5; i >= 0; i--) {
+ seq <<= 8;
+ seq |= p[i];
+ }
+ return seq;
+}
+
static void test(gnutls_push_func client_push)
{
gnutls_session_t client, server;
gnutls_certificate_free_credentials(scred);
}
+static ssize_t queue_put_renumbered(queue_t *q, const uint8_t *data, size_t l,
+ int delta_n)
+{
+ if (delta_n == 0 || l < 13 || data[3] != 0 || data[4] != 0)
+ return queue_put(&c2s, data, l);
+
+ uint8_t *p = malloc(l);
+ assert(p);
+ memcpy(p, data, l);
+ write_u48(p + 5, read_u48(p + 5) + delta_n);
+ ssize_t ret = queue_put(q, p, l);
+ free(p);
+ return ret;
+}
+
+static void split_client_hello(const uint8_t *data, size_t len, uint8_t **frag1,
+ size_t *frag1_len, uint8_t **frag2,
+ size_t *frag2_len)
+{
+ size_t body_size = len - 25;
+ *frag1_len = 13 + 12 + 1;
+ *frag2_len = 13 + 12 + (body_size - 1);
+
+ *frag1 = malloc(13 + 12 + 1);
+ assert(*frag1);
+ *frag2 = malloc(13 + 12 + body_size - 1);
+ assert(*frag2);
+
+ /* first fragment: record header + handshake header + first body byte */
+ memcpy(*frag1, data, 13); /* record header */
+ write_u16(*frag1 + 11, 12 + 1); /* record length */
+ memcpy(*frag1 + 13, data + 13, 12); /* handshake header */
+ write_u24(*frag1 + 19, 0); /* fragment_offset = 0 */
+ write_u24(*frag1 + 22, 1); /* fragment_length = 1 */
+ (*frag1)[25] = data[25]; /* first body byte */
+
+ /* second fragment: record header + handshake header + remaining body */
+ memcpy(*frag2, data, 13); /* record header */
+ write_u16(*frag2 + 11, *frag2_len - 13); /* record length */
+ write_u48(*frag2 + 5, read_u48(*frag2 + 5) + 1); /* sequence number */
+ memcpy(*frag2 + 13, data + 13, 12); /* handshake header */
+ write_u24(*frag2 + 19, 1); /* fragment_offset = 1 */
+ write_u24(*frag2 + 22, body_size - 1); /* shortened fragment_length */
+ memcpy(*frag2 + 25, data + 26, body_size - 1); /* remaining body */
+}
+
+static ssize_t client_push_split_hello(gnutls_transport_ptr_t tr, const void *b,
+ size_t l)
+{
+ static int seq_offset = 0; /* for renumbering follow-up epoch0 ones */
+
+ const uint8_t *data = (const uint8_t *)b;
+ uint8_t *frag1, *frag2;
+ size_t frag1_len, frag2_len;
+
+ /* Pass through anything that isn't an epoch0 ClientHello with body */
+ if (l < 13 + 12 + 1 || /* too short for DTLS record header */
+ data[0] != 22 || /* not a handshake record */
+ data[3] != 0 || data[4] != 0 || /* not epoch 0 */
+ data[13] != 1) /* not ClientHello */
+ return queue_put_renumbered(&c2s, b, l, seq_offset);
+
+ /* epoch0 Client Hello: special treatment of splitting into fragments */
+ split_client_hello(data, l, &frag1, &frag1_len, &frag2, &frag2_len);
+ queue_put(&c2s, frag1, frag1_len);
+ queue_put(&c2s, frag2, frag2_len);
+ free(frag1);
+ free(frag2);
+ seq_offset++;
+ return l;
+}
+
void doit(void)
{
global_init();
test(client_push_normal);
success("malicious reassembly bug exploitation (#1816):\n");
test_malicious1816();
+ success("split client hello smoke-test\n");
+ test(client_push_split_hello);
gnutls_global_deinit();
}