Files
ESP32_Serial_Swiss_Army_Knife/tests/sdk_security_overrides/ws.c
T
Commander1024 797d2681ac Migrate to IDF 5.5.3 candidate
Pin PlatformIO packages and toolchains, rebase protected SDK
overrides, and add WebSocket receive regression coverage. Document
isolated candidate validation, archive provenance, and remaining gates.
2026-09-18 14:23:13 +02:00

216 lines
8.6 KiB
C

/* SPDX-License-Identifier: GPL-3.0-only */
#define _GNU_SOURCE
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
typedef int esp_err_t;
typedef void *httpd_handle_t;
typedef int httpd_ws_type_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_INVALID_ARG -2
#define ESP_ERR_INVALID_STATE -3
#define ESP_ERR_INVALID_SIZE -4
#define HTTPD_SOCK_ERR_TIMEOUT -3
#define HTTPD_WS_FIN_BIT 0x80
#define HTTPD_WS_MASK_BIT 0x80
#define HTTPD_WS_LENGTH_BITS 0x7f
#define HTTPD_WS_OPCODE_BITS 0x0f
#define HTTPD_WS_TYPE_CLOSE 8
#define HTTPD_WS_TYPE_PING 9
#define HTTPD_WS_TYPE_PONG 10
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define ESP_LOGW(...) ((void)0)
#define ESP_LOGD(...) ((void)0)
/* SDK_OPTIONS */
struct sock_db {
httpd_handle_t handle;
int fd;
bool ws_handshake_done, ws_control_frames;
unsigned char pending_data[32];
size_t pending_len;
int (*recv_fn)(httpd_handle_t, int, char *, size_t, int);
};
struct httpd_req_aux {
struct sock_db *sd;
bool ws_final;
httpd_ws_type_t ws_type;
unsigned char before[8], mask_key[4], after[8];
};
typedef struct { void *aux; httpd_handle_t handle; } httpd_req_t;
typedef struct {
bool final, fragmented;
httpd_ws_type_t type;
unsigned char *payload;
size_t len;
} httpd_ws_frame_t;
static unsigned char *wire;
static size_t position, limit, chunk;
static int terminal_result, terminal_calls, sends, sent_type;
static size_t sent_len;
static unsigned char sent_payload[128];
static int receive(httpd_handle_t h, int fd, char *buf, size_t len, int flags)
{
(void)h; (void)fd; (void)flags;
assert(len > 0);
if (position == limit) {
/* Any continuation past failed framing is an observable failure. */
assert(terminal_calls++ == 0);
return terminal_result;
}
size_t n = MIN(len, MIN(chunk, limit - position));
memcpy(buf, wire + position, n);
position += n;
return (int)n;
}
static int httpd_req_to_sockfd(httpd_req_t *req) { (void)req; return 7; }
static esp_err_t httpd_ws_send_frame_async(httpd_handle_t h, int fd, httpd_ws_frame_t *f)
{
(void)h; assert(fd == 7); assert(f->len <= sizeof(sent_payload));
sends++; sent_type = f->type; sent_len = f->len;
if (f->len) memcpy(sent_payload, f->payload, f->len);
return ESP_OK;
}
/* SDK_FUNCTIONS */
static struct sock_db sd;
static struct httpd_req_aux aux;
static httpd_req_t req;
static unsigned char packet[32];
static size_t packet_len, header_len;
static const unsigned char plain[] = {0, 0xff, 0x80};
static void setup(int encoding, int opcode, size_t available, int result, size_t split, size_t pending)
{
memset(&sd, 0, sizeof(sd)); memset(&aux, 0, sizeof(aux));
memset(aux.before, 0xa5, sizeof(aux.before));
memset(aux.mask_key, 0xcc, sizeof(aux.mask_key));
memset(aux.after, 0x5a, sizeof(aux.after));
sd.ws_handshake_done = true; sd.recv_fn = receive; aux.sd = &sd;
aux.ws_type = 2; req.aux = &aux;
size_t n = 0;
packet[n++] = 0x80 | opcode;
packet[n++] = 0x80 | (encoding ? (encoding == 2 ? 126 : 127) : sizeof(plain));
for (int i = 0; i < encoding; i++) packet[n++] = i == encoding - 1 ? sizeof(plain) : 0;
for (int i = 0; i < 4; i++) packet[n++] = (unsigned char)(0x10 + i);
header_len = n;
for (size_t i = 0; i < sizeof(plain); i++) packet[n++] = plain[i] ^ (0x10 + i);
packet_len = n;
limit = MIN(available, n); position = pending; chunk = split;
assert(pending <= limit && pending <= sizeof(sd.pending_data));
memcpy(sd.pending_data + sizeof(sd.pending_data) - pending, packet, pending);
sd.pending_len = pending;
/* Input ends exactly at an inaccessible page, including partial headers. */
long page = sysconf(_SC_PAGESIZE);
wire = mmap(NULL, (size_t)page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
assert(wire != MAP_FAILED);
assert(mprotect(wire + page, (size_t)page, PROT_NONE) == 0);
wire += page - limit;
memcpy(wire, packet, limit);
terminal_result = result; terminal_calls = sends = 0;
}
static void finish(void)
{
for (size_t i = 0; i < 8; i++) assert(aux.before[i] == 0xa5 && aux.after[i] == 0x5a);
long page = sysconf(_SC_PAGESIZE);
assert(munmap(wire + limit - page, (size_t)page * 2) == 0);
}
static unsigned checks;
static void failure(int encoding, int stage, size_t partial, int error, bool pending, int opcode)
{
/* stage: first byte, second byte, extended length, mask. */
size_t start = stage == 0 ? 0 : stage == 1 ? 1 : stage == 2 ? 2 : 2 + encoding;
setup(encoding, opcode, start + partial, error, 1, pending ? start + partial : 0);
unsigned char output[5]; memset(output, 0xab, sizeof(output));
httpd_ws_frame_t frame = {.payload = output + 1};
esp_err_t ret = httpd_ws_get_frame_type(&req);
if (stage == 0) {
assert(ret == ESP_OK && aux.ws_final && aux.ws_type == HTTPD_WS_TYPE_CLOSE);
assert(frame.len == 0);
} else if (opcode == 2) {
assert(ret == ESP_OK && aux.ws_type == 2 && aux.ws_final);
assert(httpd_ws_recv_frame(&req, &frame, 3) == ESP_FAIL);
assert(frame.type == 2 && frame.final);
assert(frame.len == (stage == 3 ? 3u : 0u));
} else {
assert(ret == ESP_ERR_INVALID_STATE);
assert(aux.ws_type == opcode && aux.ws_final);
}
assert(position == limit && terminal_calls == 1 && sends == 0);
assert(sd.pending_len == 0 && sd.ws_handshake_done && !sd.ws_control_frames);
assert(req.aux == &aux && aux.sd == &sd);
for (size_t i = 0; i < sizeof(output); i++) assert(output[i] == 0xab);
for (size_t i = 0; i < sizeof(aux.mask_key); i++)
assert(aux.mask_key[i] == (stage == 3 && i < partial ? 0x10 + i : 0xcc));
finish(); checks++;
}
static void valid(int encoding, size_t split, size_t pending, int opcode, bool probe)
{
setup(encoding, opcode, 32, 0, split, pending);
unsigned char output[5]; memset(output, 0xab, sizeof(output));
httpd_ws_frame_t frame = {.payload = output + 1};
assert(httpd_ws_get_frame_type(&req) == ESP_OK);
if (opcode == 2) {
if (probe) {
assert(httpd_ws_recv_frame(&req, &frame, 0) == ESP_OK);
assert(frame.len == 3 && position == header_len);
assert(output[1] == 0xab);
}
assert(httpd_ws_recv_frame(&req, &frame, 3) == ESP_OK);
assert(frame.len == 3 && frame.type == 2 && frame.final);
assert(memcmp(output + 1, plain, 3) == 0 && sends == 0);
} else {
assert(sends == 1 && sent_type == (opcode == 9 ? 10 : 8));
assert(sent_len == (opcode == 9 ? 3u : 0u));
if (opcode == 9) assert(memcmp(sent_payload, plain, 3) == 0);
}
assert(output[0] == 0xab && output[4] == 0xab);
assert(position == packet_len && sd.pending_len == 0 && terminal_calls == 0);
finish(); checks++;
}
static void extended_length(int encoding, uint64_t length)
{
setup(encoding, 2, (size_t)(6 + encoding), 0, 1, 0);
for (int i = 0; i < encoding; i++)
wire[2 + i] = (unsigned char)(length >> (8 * (encoding - i - 1)));
httpd_ws_frame_t frame = {0};
assert(httpd_ws_get_frame_type(&req) == ESP_OK);
assert(httpd_ws_recv_frame(&req, &frame, 0) == ESP_OK);
assert(frame.len == length && frame.type == 2 && frame.final);
assert(position == header_len && sends == 0 && terminal_calls == 0);
finish(); checks++;
}
int main(void)
{
const int errors[] = {-1, HTTPD_SOCK_ERR_TIMEOUT, 0};
const int encodings[] = {0, 2, 8};
const int opcodes[] = {2, 9, 8};
for (size_t e = 0; e < 3; e++) {
int encoding = encodings[e];
for (size_t o = 0; o < 3; o++) {
for (int stage = 0; stage < 4; stage++) {
if (stage == 2 && encoding == 0) continue;
size_t extent = stage < 2 ? 1 : stage == 2 ? (size_t)encoding : 4;
for (size_t part = 0; part < extent; part++)
for (size_t err = 0; err < 3; err++)
for (int pending = 0; pending < 2; pending++)
failure(encoding, stage, part, errors[err], pending, opcodes[o]);
}
for (size_t split = 1; split <= 16; split *= 2)
for (size_t pending = 0; pending <= (size_t)(6 + encoding); pending++)
valid(encoding, split, pending, opcodes[o], false);
valid(encoding, 1, 0, opcodes[o], true);
}
}
extended_length(2, 126);
extended_length(2, 65535);
extended_length(8, 65536);
extended_length(8, UINT64_C(0x0102030405060708));
printf("WS actual full receive/blocking/pending functions: %u guard-page/canary cases PASS\n", checks);
return 0;
}