Skip to content

Commit 57a879b

Browse files
authored
Merge pull request #128 from danielinux/fenrir-fixes-2026-06-05
Fenrir fixes 2026 06 05
2 parents bed7228 + 7a11ba8 commit 57a879b

8 files changed

Lines changed: 933 additions & 29 deletions

File tree

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ endif
182182
EXE=build/tcpecho build/tcp_netcat_poll build/tcp_netcat_select \
183183
build/test-evloop build/test-dns build/test-wolfssl-forwarding \
184184
build/test-ttl-expired build/test-wolfssl build/test-httpd \
185+
build/test-http-smuggle build/test-http-arg-oob \
186+
build/test-posix-errno \
185187
build/ipfilter-logger \
186188
build/test-esp build/esp-server
187189
ifeq ($(UNAME_S),Linux)
@@ -331,6 +333,17 @@ build/packet_ping: $(OBJ) build/port/posix/bsd_socket.o build/test/packet_ping.o
331333
@echo "[LD] $@"
332334
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
333335

336+
# F-4950 regression: test_posix_errno.c #includes bsd_socket.c directly, so the
337+
# shim object must not be linked again here.
338+
build/test-posix-errno: $(OBJ) build/test/test_posix_errno.o
339+
@echo "[LD] $@"
340+
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
341+
342+
.PHONY: posix-errno-test
343+
posix-errno-test: build/test-posix-errno
344+
@echo "[RUN] $<"
345+
@./build/test-posix-errno
346+
334347

335348
build/test-wolfssl:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP
336349
build/test-httpd:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -Isrc/http
@@ -395,6 +408,21 @@ build/test-httpd: $(OBJ) build/test/test_httpd.o build/port/wolfssl_io.o build/c
395408
@echo "[LD] $@"
396409
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) -lwolfssl $(END_GROUP)
397410

411+
# Standalone regression test for HTTP request framing (F-5259). It #includes
412+
# httpd.c directly to reach the static parser and stubs the wolfIP/wolfSSL I/O.
413+
build/test-http-smuggle:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -DWOLFIP_ENABLE_HTTP -Isrc/http
414+
build/test-http-smuggle: src/test/test_http_smuggle.c src/http/httpd.c
415+
@mkdir -p build || true
416+
@echo "[LD] $@"
417+
@$(CC) $(CFLAGS) -o $@ src/test/test_http_smuggle.c $(LDFLAGS) -lwolfssl
418+
419+
# Standalone regression test for the httpd_get_request_arg OOB read (F-5258).
420+
build/test-http-arg-oob:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -DWOLFIP_ENABLE_HTTP -Isrc/http
421+
build/test-http-arg-oob: src/test/test_http_arg_oob.c src/http/httpd.c
422+
@mkdir -p build || true
423+
@echo "[LD] $@"
424+
@$(CC) $(CFLAGS) -o $@ src/test/test_http_arg_oob.c $(LDFLAGS) -lwolfssl
425+
398426
build/%.o: src/%.c
399427
@mkdir -p `dirname $@` || true
400428
@echo "[CC] $<"

src/http/httpd.c

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,35 @@ int http_url_encode(char *buf, size_t len, size_t max_len) {
290290
return len;
291291
}
292292

293+
/* Case-insensitive check that header line [s, s+len) begins with the field
294+
* name `name` immediately followed by ':'. Returns a pointer to the value
295+
* (past the colon and any leading spaces/tabs) on match, or NULL otherwise. */
296+
static const char *http_header_value(const char *s, size_t len, const char *name) {
297+
size_t nl = strlen(name);
298+
size_t i;
299+
if (len < nl + 1)
300+
return NULL;
301+
for (i = 0; i < nl; i++) {
302+
if (tolower((unsigned char)s[i]) != tolower((unsigned char)name[i]))
303+
return NULL;
304+
}
305+
if (s[nl] != ':')
306+
return NULL;
307+
i = nl + 1;
308+
while (i < len && (s[i] == ' ' || s[i] == '\t'))
309+
i++;
310+
return s + i;
311+
}
312+
293313
static int parse_http_request(struct http_client *hc, uint8_t *buf, size_t len) {
294314
char *p = (char *) buf;
295315
char *end = p + len;
296316
char *q;
297317
size_t n;
298318
int ret;
299319
int decoded_len;
320+
long content_length = -1; /* -1: no Content-Length header seen */
321+
int has_te = 0; /* Transfer-Encoding header present */
300322
struct http_request req;
301323
struct http_url *url = NULL;
302324
memset(&req, 0, sizeof(struct http_request));
@@ -346,19 +368,56 @@ static int parse_http_request(struct http_client *hc, uint8_t *buf, size_t len)
346368
goto bad_request;
347369
n = q - p;
348370
if (n == 0) {
371+
p = q + 2; /* Skip the blank line; body (if any) starts here */
349372
break; /* End of headers */
350373
}
351374
/* Enforce header maximum length */
352375
if (n >= sizeof(req.headers))
353376
goto bad_request;
377+
/* Extract framing headers so the body length is derived from the
378+
* declared Content-Length rather than from the recv buffer tail. */
379+
{
380+
const char *v = http_header_value(p, n, "content-length");
381+
if (v) {
382+
long cl = 0;
383+
const char *d = v;
384+
if (content_length >= 0) /* duplicate Content-Length */
385+
goto bad_request;
386+
if (d >= q || *d < '0' || *d > '9')
387+
goto bad_request;
388+
while (d < q && *d >= '0' && *d <= '9') {
389+
cl = cl * 10 + (*d - '0');
390+
if (cl > (long)sizeof(req.body)) /* too large / overflow */
391+
goto bad_request;
392+
d++;
393+
}
394+
if (d != q) /* trailing garbage after the number */
395+
goto bad_request;
396+
content_length = cl;
397+
} else if (http_header_value(p, n, "transfer-encoding")) {
398+
has_te = 1;
399+
}
400+
}
354401
/* Copy header and terminate */
355402
memcpy(req.headers, p, n);
356403
req.headers[n] = '\0';
357404
p = q + 2;
358405
}
359-
/* Parse the body */
360-
if (p < end) {
361-
n = end - p;
406+
/* Parse the body. The body length is taken from the declared
407+
* Content-Length; surplus bytes in the recv buffer are not part of this
408+
* request. Transfer-Encoding (chunked) framing is not supported and a
409+
* body present without a Content-Length is malformed - both are rejected
410+
* to avoid request-smuggling (CL.0 / TE) ambiguity. */
411+
n = end - p;
412+
if (has_te)
413+
goto bad_request;
414+
if (content_length >= 0) {
415+
if ((size_t)content_length != n)
416+
goto bad_request;
417+
} else if (n > 0) {
418+
goto bad_request;
419+
}
420+
if (n > 0) {
362421
if (n >= sizeof(req.body)) {
363422
return -1;
364423
}
@@ -501,6 +560,8 @@ int httpd_get_request_arg(struct http_request *req, const char *name, char *valu
501560
return 0;
502561
}
503562
}
563+
if (*q == '\0') // Reached the terminator: do not step past the buffer
564+
break;
504565
p = q + 1; // Move to next key-value pair
505566
}
506567
return -1; // Key not found

src/port/posix/bsd_socket.c

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ struct wolfip_fd_entry {
142142
uint8_t in_use;
143143
uint8_t pending_tokens; /* Bitset of queued event bytes in the pipe */
144144
uint16_t events; /* Events armed for current poll/select */
145+
uint32_t generation; /* Bumped on every alloc; detects slot reuse */
145146
};
146147

147148
#define WOLFIP_TOKEN_R (1u << 0)
@@ -177,6 +178,7 @@ static void wolfip_drain_pipe_locked(struct wolfip_fd_entry *entry)
177178
}
178179

179180
static struct wolfip_fd_entry wolfip_fd_entries[WOLFIP_MAX_PUBLIC_FDS];
181+
static uint32_t wolfip_fd_generation; /* Monotonic; bumped under wolfIP_mutex on each alloc */
180182
static int tcp_entry_for_slot[MAX_TCPSOCKETS];
181183
static int udp_entry_for_slot[MAX_UDPSOCKETS];
182184
static int icmp_entry_for_slot[MAX_ICMPSOCKETS];
@@ -196,6 +198,7 @@ enum wolfip_dns_wait_type {
196198
struct wolfip_dns_wait_ctx {
197199
pthread_mutex_t mutex;
198200
pthread_cond_t cond;
201+
int busy;
199202
int pending;
200203
enum wolfip_dns_wait_type type;
201204
int status;
@@ -207,6 +210,7 @@ static struct wolfip_dns_wait_ctx dns_wait_ctx = {
207210
PTHREAD_MUTEX_INITIALIZER,
208211
PTHREAD_COND_INITIALIZER,
209212
0,
213+
0,
210214
DNS_WAIT_NONE,
211215
0,
212216
0,
@@ -393,6 +397,7 @@ static int wolfip_fd_alloc(int internal_fd, int nonblock)
393397
}
394398
idx = pipefds[0];
395399
memset(&wolfip_fd_entries[idx], 0, sizeof(wolfip_fd_entries[idx]));
400+
wolfip_fd_entries[idx].generation = ++wolfip_fd_generation;
396401
wolfip_fd_entries[idx].internal_fd = internal_fd;
397402
wolfip_fd_entries[idx].public_fd = pipefds[0];
398403
wolfip_fd_entries[idx].pipe_write = pipefds[1];
@@ -449,9 +454,15 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
449454
{
450455
struct pollfd pfd;
451456
char want;
457+
uint32_t start_gen;
458+
int start_fd;
452459

453460
if (!entry)
454461
return -EINVAL;
462+
/* Snapshot the slot identity so we can detect a concurrent close()/reuse
463+
* across the mutex-drop window below. */
464+
start_gen = entry->generation;
465+
start_fd = entry->internal_fd;
455466
want = (wait_events & POLLOUT) ? 'w' : 'r';
456467
entry->events = (uint16_t)wait_events;
457468
wolfIP_register_callback(IPSTACK, entry->internal_fd, poller_callback, IPSTACK);
@@ -481,6 +492,15 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
481492
return -EINTR;
482493
}
483494
pthread_mutex_lock(&wolfIP_mutex);
495+
/* While the mutex was dropped a concurrent close() may have released
496+
* this slot, and a subsequent socket()/accept() may have reused the
497+
* same public fd for an unrelated connection. The caller still holds
498+
* the stale internal_fd it captured before blocking; signal EBADF so
499+
* it does not read/write the reused slot's data. */
500+
if (!entry->in_use || entry->generation != start_gen ||
501+
entry->internal_fd != start_fd) {
502+
return -EBADF;
503+
}
484504
if (poll_ret < 0) {
485505
return -errno;
486506
}
@@ -518,7 +538,7 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
518538
if (__wolfip_internal >= 0) { \
519539
int __wolfip_retval = wolfIP_sock_##call(IPSTACK, __wolfip_internal, ## __VA_ARGS__); \
520540
if (__wolfip_retval < 0) { \
521-
errno = __wolfip_retval; \
541+
errno = -__wolfip_retval; \
522542
pthread_mutex_unlock(&wolfIP_mutex); \
523543
return -1; \
524544
} \
@@ -565,7 +585,7 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
565585
} \
566586
} while (__wolfip_retval == -EAGAIN); \
567587
if (__wolfip_retval < 0) { \
568-
errno = __wolfip_retval; \
588+
errno = -__wolfip_retval; \
569589
pthread_mutex_unlock(&wolfIP_mutex); \
570590
return -1; \
571591
} \
@@ -715,10 +735,11 @@ static int wolfip_dns_error_to_eai(int err)
715735
static int wolfip_dns_begin_wait(enum wolfip_dns_wait_type type)
716736
{
717737
pthread_mutex_lock(&dns_wait_ctx.mutex);
718-
if (dns_wait_ctx.pending) {
738+
if (dns_wait_ctx.busy) {
719739
pthread_mutex_unlock(&dns_wait_ctx.mutex);
720740
return EAI_AGAIN;
721741
}
742+
dns_wait_ctx.busy = 1;
722743
dns_wait_ctx.pending = 1;
723744
dns_wait_ctx.type = type;
724745
dns_wait_ctx.status = EAI_FAIL;
@@ -731,6 +752,7 @@ static void wolfip_dns_abort_wait(int status)
731752
{
732753
pthread_mutex_lock(&dns_wait_ctx.mutex);
733754
dns_wait_ctx.pending = 0;
755+
dns_wait_ctx.busy = 0;
734756
dns_wait_ctx.type = DNS_WAIT_NONE;
735757
dns_wait_ctx.status = status;
736758
pthread_cond_signal(&dns_wait_ctx.cond);
@@ -748,13 +770,16 @@ static int wolfip_dns_wait(enum wolfip_dns_wait_type type, uint32_t *ip_out, cha
748770
int err = pthread_cond_timedwait(&dns_wait_ctx.cond, &dns_wait_ctx.mutex, &ts);
749771
if (err == ETIMEDOUT) {
750772
dns_wait_ctx.pending = 0;
773+
dns_wait_ctx.busy = 0;
751774
dns_wait_ctx.type = DNS_WAIT_NONE;
752775
pthread_mutex_unlock(&dns_wait_ctx.mutex);
753776
return EAI_AGAIN;
754777
}
755778
}
756779
if (dns_wait_ctx.type != type) {
757780
int status = dns_wait_ctx.status ? dns_wait_ctx.status : EAI_FAIL;
781+
dns_wait_ctx.type = DNS_WAIT_NONE;
782+
dns_wait_ctx.busy = 0;
758783
pthread_mutex_unlock(&dns_wait_ctx.mutex);
759784
return status;
760785
}
@@ -766,6 +791,7 @@ static int wolfip_dns_wait(enum wolfip_dns_wait_type type, uint32_t *ip_out, cha
766791
wolfip_strlcpy(name_out, dns_wait_ctx.name, name_len);
767792
}
768793
dns_wait_ctx.type = DNS_WAIT_NONE;
794+
dns_wait_ctx.busy = 0;
769795
pthread_mutex_unlock(&dns_wait_ctx.mutex);
770796
return status;
771797
}
@@ -1476,6 +1502,12 @@ static int wolfip_accept_common(int sockfd, struct sockaddr *addr, socklen_t *ad
14761502
if (entry) {
14771503
int internal_ret;
14781504
int public_fd;
1505+
uint32_t start_gen;
1506+
int start_fd;
1507+
/* Snapshot the listener slot's identity so we can detect a concurrent
1508+
* close()/reuse across the mutex-drop window in the poll loop below. */
1509+
start_gen = entry->generation;
1510+
start_fd = entry->internal_fd;
14791511
if (!want_nonblock)
14801512
want_nonblock = wolfip_fd_is_nonblock(sockfd);
14811513
do {
@@ -1494,8 +1526,15 @@ static int wolfip_accept_common(int sockfd, struct sockaddr *addr, socklen_t *ad
14941526
pthread_mutex_unlock(&wolfIP_mutex);
14951527
host_poll(&pfd, 1, -1);
14961528
pthread_mutex_lock(&wolfIP_mutex);
1529+
/* While the mutex was dropped a concurrent close() may have
1530+
* released this slot, and a subsequent socket()/accept() may
1531+
* have reused the same public fd for an unrelated connection.
1532+
* The bare in_use check below cannot tell the slot apart from
1533+
* the original listener, so verify the snapshotted identity to
1534+
* avoid accepting on the wrong internal socket. */
14971535
entry = wolfip_entry_from_public(sockfd);
1498-
if (!entry) {
1536+
if (!entry || entry->generation != start_gen ||
1537+
entry->internal_fd != start_fd) {
14991538
errno = EBADF;
15001539
pthread_mutex_unlock(&wolfIP_mutex);
15011540
return -1;
@@ -1505,7 +1544,7 @@ static int wolfip_accept_common(int sockfd, struct sockaddr *addr, socklen_t *ad
15051544
}
15061545
} while (internal_ret == -EAGAIN);
15071546
if (internal_ret < 0) {
1508-
errno = internal_ret;
1547+
errno = -internal_ret;
15091548
pthread_mutex_unlock(&wolfIP_mutex);
15101549
return -1;
15111550
}

0 commit comments

Comments
 (0)