Skip to content

Commit 3d4f59f

Browse files
committed
websocket: Implement 64-bit frames, enable split frames.
This fixes several assertion errors that were found in fuzz testing, for unimplemented portions of the spec. The assertions were either turned into Python exceptions, or the missing functionality was implemented. Frames larger than 4GB are unsupported. Initial reception of such a frame will result in OSError(EIO) and subsequent operations on the same websocket will fail because framing has been lost. Transmitting frames larger than 64kB is unsupported. Attempting to transmit such a frame will result in OSError(ENOBUFS). Subsequent operations on the websocket are possible. Signed-off-by: Jeff Epler <jepler@gmail.com>
1 parent bb205f0 commit 3d4f59f

3 files changed

Lines changed: 58 additions & 16 deletions

File tree

extmod/modwebsocket.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ typedef struct _mp_obj_websocket_t {
4747
byte to_recv;
4848
byte mask_pos;
4949
byte buf_pos;
50-
byte buf[6];
50+
byte buf[12];
5151
byte opts;
5252
// Copy of last data frame flags
5353
byte ws_flags;
@@ -76,6 +76,7 @@ static mp_obj_t websocket_make_new(const mp_obj_type_t *type, size_t n_args, siz
7676

7777
static mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
7878
mp_obj_websocket_t *self = MP_OBJ_TO_PTR(self_in);
79+
7980
const mp_stream_p_t *stream_p = mp_get_stream(self->sock);
8081
while (1) {
8182
if (self->to_recv != 0) {
@@ -93,9 +94,6 @@ static mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int
9394

9495
switch (self->state) {
9596
case FRAME_HEADER: {
96-
// TODO: Split frame handling below is untested so far, so conservatively disable it
97-
assert(self->buf[0] & 0x80);
98-
9997
// "Control frames MAY be injected in the middle of a fragmented message."
10098
// So, they must be processed before data frames (and not alter
10199
// self->ws_flags)
@@ -121,13 +119,12 @@ static mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int
121119
to_recv += 2;
122120
} else if (sz == 127) {
123121
// Msg size is next 8 bytes
124-
assert(0);
122+
to_recv += 8;
125123
}
126124
if (self->buf[1] & 0x80) {
127125
// Next 4 bytes is mask
128126
to_recv += 4;
129127
}
130-
131128
self->buf_pos = 0;
132129
self->to_recv = to_recv;
133130
self->msg_sz = sz; // May be overridden by FRAME_OPT
@@ -144,11 +141,24 @@ static mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int
144141
}
145142

146143
case FRAME_OPT: {
147-
if ((self->buf_pos & 3) == 2) {
144+
if (self->buf_pos & 2) { // to_recv was 2 or 6
145+
assert(self->buf_pos == 2 || self->buf_pos == 6);
148146
// First two bytes are message length
149147
self->msg_sz = (self->buf[0] << 8) | self->buf[1];
148+
} else if (self->buf_pos & 8) { // to_recv was 8 or 12
149+
assert(self->buf_pos == 8 || self->buf_pos == 12);
150+
// First eight bytes are message length
151+
if (self->buf[0] || self->buf[1] || self->buf[2] || self->buf[3]) {
152+
mp_stream_close(self->sock); // cannot recover from framing error
153+
*errcode = MP_EIO;
154+
return MP_STREAM_ERROR;
155+
}
156+
self->msg_sz = ((uint32_t)self->buf[4] << 24)
157+
| ((uint32_t)self->buf[5] << 16)
158+
| ((uint32_t)self->buf[6] << 8)
159+
| (uint32_t)self->buf[7];
150160
}
151-
if (self->buf_pos >= 4) {
161+
if (self->buf_pos & 4) {
152162
// Last 4 bytes is mask
153163
memcpy(self->mask, self->buf + self->buf_pos - 4, 4);
154164
}
@@ -218,7 +228,10 @@ static mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int
218228

219229
static mp_uint_t websocket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
220230
mp_obj_websocket_t *self = MP_OBJ_TO_PTR(self_in);
221-
assert(size < 0x10000);
231+
if (size >= 0x10000) {
232+
*errcode = MP_ENOBUFS;
233+
return MP_STREAM_ERROR;
234+
}
222235
byte header[4] = {0x80 | (self->opts & FRAME_OPCODE_MASK)};
223236
int hdr_sz;
224237
if (size < 126) {

tests/extmod/websocket_basic.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ def ws_read(msg, sz):
1313
return ws.read(sz)
1414

1515

16+
# put raw data in the stream and do a series of websocket read
17+
def ws_readn(msg, *args):
18+
ws = websocket.websocket(io.BytesIO(msg))
19+
for sz in args:
20+
yield ws.read(sz)
21+
22+
1623
# do a websocket write and then return the raw data from the stream
1724
def ws_write(msg, sz):
1825
s = io.BytesIO()
@@ -24,18 +31,33 @@ def ws_write(msg, sz):
2431

2532
# basic frame
2633
print(ws_read(b"\x81\x04ping", 4))
27-
print(ws_read(b"\x80\x04ping", 4)) # FRAME_CONT
2834
print(ws_write(b"pong", 6))
2935

30-
# split frames are not supported
31-
# print(ws_read(b"\x01\x04ping", 4))
36+
# split frames and irregular size reads
37+
for s in ws_readn(b"\x01\x04ping\x00\x04Ping\x80\x04PING", 6, 4, 2, 2):
38+
print(s)
3239

3340
# extended payloads
3441
print(ws_read(b"\x81~\x00\x80" + b"ping" * 32, 128))
3542
print(ws_write(b"pong" * 32, 132))
3643

37-
# mask (returned data will be 'mask' ^ 'mask')
38-
print(ws_read(b"\x81\x84maskmask", 4))
44+
# 64-bit payload size (but small payload -- appears permitted by spec)
45+
print(ws_read(b"\x81\x7f\x00\x00\x00\x00\x00\x00\x00\x80" + b"ping" * 32, 128))
46+
47+
# >4GB payload size, unsupported by micropython implementation. Framing is lost.
48+
msg = b"\x81\x7f\x01\x00\x00\x00\x00\x00\x00\x80" + b"ping" * 32
49+
ws = websocket.websocket(io.BytesIO(msg))
50+
try:
51+
print(ws.read(1))
52+
except OSError as e:
53+
print("ioctl: EIO:", e.errno == errno.EIO)
54+
55+
# mask (returned data will be 'maskmask' ^ 'maskMASK')
56+
print(ws_read(b"\x81\x88maskmaskMASK", 8))
57+
# mask w/2-byte payload len (returned data will be 'maskmask' ^ 'maskMASK')
58+
print(ws_read(b"\x81\xfe\x00\x08maskmaskMASK", 8))
59+
# mask w/8-byte payload len (returned data will be 'maskmask' ^ 'maskMASK')
60+
print(ws_read(b"\x81\xff\x00\x00\x00\x00\x00\x00\x00\x08maskmaskMASK", 8))
3961

4062
# close control frame
4163
s = io.BytesIO(b"\x88\x00") # FRAME_CLOSE

tests/extmod/websocket_basic.py.exp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
b'ping'
2-
b'ping'
32
b'\x81\x04pong'
3+
b'pingPi'
4+
b'ngPI'
5+
b'NG'
6+
b''
47
b'pingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingping'
58
b'\x81~\x00\x80pongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpongpong'
6-
b'\x00\x00\x00\x00'
9+
b'pingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingpingping'
10+
ioctl: EIO: True
11+
b'\x00\x00\x00\x00 '
12+
b'\x00\x00\x00\x00 '
13+
b'\x00\x00\x00\x00 '
714
b''
815
b'\x88\x00'
916
b'ping'

0 commit comments

Comments
 (0)