/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc. See LICENSE. */ /* * lsquic_hspack_valid.c -- Handshake packet validator. * * We want to eliminate invalid packets as soon as we read them in and not * feed them to lsquic engine if we can avoid it. The handshake packet * possesses several characteristics which make it possible to detect * garbage packets. */ #include #include #include #include "lsquic.h" #include "lsquic_types.h" #include "lsquic_int_types.h" #include "lsquic_packet_common.h" #include "lsquic_packet_gquic.h" #include "lsquic_packet_ietf.h" #include "lsquic_mm.h" #include "lsquic_engine_public.h" #include "lsquic_version.h" #include "lsquic_parse_common.h" #define SMALLEST_GQUIC_OVERHEAD \ 1 /* Type */ \ + GQUIC_CID_LEN \ + sizeof(lsquic_ver_tag_t) \ + 1 /* Packet number */ \ + 1 /* Stream frame */ \ + 1 /* Stream ID */ \ + 2 /* Data length */ \ + 12 /* IV */ /* Note that we ignore nonce: even if the flag is set, we know that Chrome * does not actually include the 32-byte nonce. */ static int is_valid_gquic_hs_packet (const unsigned char *buf, size_t bufsz, lsquic_ver_tag_t *tag) { if (bufsz > GQUIC_MAX_PACKET_SZ || /* Data: HPACKed :method GET :path / is 2 bytes */ bufsz < SMALLEST_GQUIC_OVERHEAD + 2 || /* Check maximum packet number: */ buf[1 + GQUIC_CID_LEN + sizeof(lsquic_ver_tag_t)] > 64 || /* From [draft-hamilton-quic-transport-protocol-01]: * 0x80 is currently unused, and must be set to 0. * 0x40 = MULTIPATH. This bit is reserved for multipath use. * * 0x30 = Packet number length. We expect these bits to be * unset. * * The reference implementation checks that two high bits are not * set if version flag is not set or if the version is the same. * For our purposes, all GQUIC version we support so far have these * bits set to zero. * * Incoming handshake packets must have both connection ID and * version bits set. * * Nonce flag is ignored: Chrome sets it erronesously, but it may * not be true (a) in the future or (b) in other clients. */ ((buf[0] ^ ( /* These should be unset: */ (~(0x80|0x40|0x30|PACKET_PUBLIC_FLAGS_RST)) & /* While these should be set: */ (PACKET_PUBLIC_FLAGS_VERSION| PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID) )) & /* Ignore this bit: */ ~PACKET_PUBLIC_FLAGS_NONCE) ) { return 0; } memcpy(tag, buf + 1 + 8, sizeof(*tag)); return 1; } int lsquic_is_valid_hs_packet (struct lsquic_engine *engine, const unsigned char *buf, size_t bufsz) { lsquic_ver_tag_t tag; int is_valid; if (bufsz < 1) return 0; switch (buf[0] & 0xF8) { /* Xs vary, Gs are iGnored: */ /* 1X11 XGGG: Q046 long header */ case 0x80|0x40|0x20|0x10|0x08: case 0x80|0x00|0x20|0x10|0x08: case 0x80|0x40|0x20|0x10|0x00: case 0x80|0x00|0x20|0x10|0x00: is_valid = lsquic_is_valid_iquic_hs_packet(buf, bufsz, &tag); break; /* 1X00 XGGG: ID-22 long header */ case 0x80|0x40|0x00|0x00|0x08: case 0x80|0x00|0x00|0x00|0x08: case 0x80|0x40|0x00|0x00|0x00: case 0x80|0x00|0x00|0x00|0x00: /* 1X01 XGGG: ID-22 long header */ case 0x80|0x40|0x00|0x10|0x08: case 0x80|0x00|0x00|0x10|0x08: case 0x80|0x40|0x00|0x10|0x00: case 0x80|0x00|0x00|0x10|0x00: /* 1X10 XGGG: ID-22 long header */ case 0x80|0x40|0x20|0x00|0x08: case 0x80|0x00|0x20|0x00|0x08: case 0x80|0x40|0x20|0x00|0x00: case 0x80|0x00|0x20|0x00|0x00: is_valid = lsquic_is_valid_ietf_v1_or_Q046plus_hs_packet(buf, bufsz, &tag); break; /* 01XX XGGG: ID-22 short header */ case 0x00|0x40|0x00|0x00|0x00: case 0x00|0x40|0x00|0x00|0x08: case 0x00|0x40|0x00|0x10|0x00: case 0x00|0x40|0x00|0x10|0x08: case 0x00|0x40|0x20|0x00|0x00: case 0x00|0x40|0x20|0x00|0x08: case 0x00|0x40|0x20|0x10|0x00: case 0x00|0x40|0x20|0x10|0x08: is_valid = 0; break; /* 00XX 0GGG: Q046 short header */ case 0x00|0x00|0x00|0x00|0x00: case 0x00|0x00|0x00|0x10|0x00: case 0x00|0x00|0x20|0x00|0x00: case 0x00|0x00|0x20|0x10|0x00: is_valid = 0; break; /* 00XX 1GGG: GQUIC */ case 0x00|0x00|0x00|0x00|0x08: case 0x00|0x00|0x00|0x10|0x08: case 0x00|0x00|0x20|0x00|0x08: case 0x00|0x00|0x20|0x10|0x08: is_valid = is_valid_gquic_hs_packet(buf, bufsz, &tag); break; default: /* gcc thinks this is possible?! */ assert(0); is_valid = 0; break; } if (is_valid) { return 1; } else return 0; }