Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ endif
EXE=build/tcpecho build/tcp_netcat_poll build/tcp_netcat_select \
build/test-evloop build/test-dns build/test-wolfssl-forwarding \
build/test-ttl-expired build/test-wolfssl build/test-httpd \
build/test-http-smuggle build/test-http-arg-oob \
Comment thread
danielinux marked this conversation as resolved.
build/test-posix-errno \
build/ipfilter-logger \
build/test-esp build/esp-server
ifeq ($(UNAME_S),Linux)
Expand Down Expand Up @@ -331,6 +333,17 @@ build/packet_ping: $(OBJ) build/port/posix/bsd_socket.o build/test/packet_ping.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)

# F-4950 regression: test_posix_errno.c #includes bsd_socket.c directly, so the
# shim object must not be linked again here.
build/test-posix-errno: $(OBJ) build/test/test_posix_errno.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)

.PHONY: posix-errno-test
posix-errno-test: build/test-posix-errno
@echo "[RUN] $<"
@./build/test-posix-errno


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

# Standalone regression test for HTTP request framing (F-5259). It #includes
# httpd.c directly to reach the static parser and stubs the wolfIP/wolfSSL I/O.
build/test-http-smuggle:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -DWOLFIP_ENABLE_HTTP -Isrc/http
build/test-http-smuggle: src/test/test_http_smuggle.c src/http/httpd.c
@mkdir -p build || true
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ src/test/test_http_smuggle.c $(LDFLAGS) -lwolfssl

# Standalone regression test for the httpd_get_request_arg OOB read (F-5258).
build/test-http-arg-oob:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -DWOLFIP_ENABLE_HTTP -Isrc/http
build/test-http-arg-oob: src/test/test_http_arg_oob.c src/http/httpd.c
@mkdir -p build || true
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ src/test/test_http_arg_oob.c $(LDFLAGS) -lwolfssl

build/%.o: src/%.c
@mkdir -p `dirname $@` || true
@echo "[CC] $<"
Expand Down
67 changes: 64 additions & 3 deletions src/http/httpd.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,35 @@ int http_url_encode(char *buf, size_t len, size_t max_len) {
return len;
}

/* Case-insensitive check that header line [s, s+len) begins with the field
* name `name` immediately followed by ':'. Returns a pointer to the value
* (past the colon and any leading spaces/tabs) on match, or NULL otherwise. */
static const char *http_header_value(const char *s, size_t len, const char *name) {
size_t nl = strlen(name);
size_t i;
if (len < nl + 1)
return NULL;
for (i = 0; i < nl; i++) {
if (tolower((unsigned char)s[i]) != tolower((unsigned char)name[i]))
return NULL;
}
if (s[nl] != ':')
return NULL;
i = nl + 1;
while (i < len && (s[i] == ' ' || s[i] == '\t'))
i++;
return s + i;
}

static int parse_http_request(struct http_client *hc, uint8_t *buf, size_t len) {
char *p = (char *) buf;
char *end = p + len;
char *q;
size_t n;
int ret;
int decoded_len;
long content_length = -1; /* -1: no Content-Length header seen */
int has_te = 0; /* Transfer-Encoding header present */
struct http_request req;
struct http_url *url = NULL;
memset(&req, 0, sizeof(struct http_request));
Expand Down Expand Up @@ -346,19 +368,56 @@ static int parse_http_request(struct http_client *hc, uint8_t *buf, size_t len)
goto bad_request;
n = q - p;
if (n == 0) {
p = q + 2; /* Skip the blank line; body (if any) starts here */
break; /* End of headers */
}
/* Enforce header maximum length */
if (n >= sizeof(req.headers))
goto bad_request;
/* Extract framing headers so the body length is derived from the
* declared Content-Length rather than from the recv buffer tail. */
{
const char *v = http_header_value(p, n, "content-length");
if (v) {
long cl = 0;
const char *d = v;
if (content_length >= 0) /* duplicate Content-Length */
goto bad_request;
if (d >= q || *d < '0' || *d > '9')
goto bad_request;
while (d < q && *d >= '0' && *d <= '9') {
cl = cl * 10 + (*d - '0');
if (cl > (long)sizeof(req.body)) /* too large / overflow */
goto bad_request;
d++;
}
if (d != q) /* trailing garbage after the number */
goto bad_request;
content_length = cl;
} else if (http_header_value(p, n, "transfer-encoding")) {
has_te = 1;
}
}
/* Copy header and terminate */
memcpy(req.headers, p, n);
req.headers[n] = '\0';
p = q + 2;
}
/* Parse the body */
if (p < end) {
n = end - p;
/* Parse the body. The body length is taken from the declared
* Content-Length; surplus bytes in the recv buffer are not part of this
* request. Transfer-Encoding (chunked) framing is not supported and a
* body present without a Content-Length is malformed - both are rejected
* to avoid request-smuggling (CL.0 / TE) ambiguity. */
n = end - p;
if (has_te)
goto bad_request;
if (content_length >= 0) {
if ((size_t)content_length != n)
goto bad_request;
} else if (n > 0) {
goto bad_request;
}
if (n > 0) {
if (n >= sizeof(req.body)) {
return -1;
}
Expand Down Expand Up @@ -501,6 +560,8 @@ int httpd_get_request_arg(struct http_request *req, const char *name, char *valu
return 0;
}
}
if (*q == '\0') // Reached the terminator: do not step past the buffer
break;
p = q + 1; // Move to next key-value pair
}
return -1; // Key not found
Expand Down
49 changes: 44 additions & 5 deletions src/port/posix/bsd_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ struct wolfip_fd_entry {
uint8_t in_use;
uint8_t pending_tokens; /* Bitset of queued event bytes in the pipe */
uint16_t events; /* Events armed for current poll/select */
uint32_t generation; /* Bumped on every alloc; detects slot reuse */
};

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

static struct wolfip_fd_entry wolfip_fd_entries[WOLFIP_MAX_PUBLIC_FDS];
static uint32_t wolfip_fd_generation; /* Monotonic; bumped under wolfIP_mutex on each alloc */
static int tcp_entry_for_slot[MAX_TCPSOCKETS];
static int udp_entry_for_slot[MAX_UDPSOCKETS];
static int icmp_entry_for_slot[MAX_ICMPSOCKETS];
Expand All @@ -196,6 +198,7 @@ enum wolfip_dns_wait_type {
struct wolfip_dns_wait_ctx {
pthread_mutex_t mutex;
pthread_cond_t cond;
int busy;
int pending;
enum wolfip_dns_wait_type type;
int status;
Expand All @@ -207,6 +210,7 @@ static struct wolfip_dns_wait_ctx dns_wait_ctx = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_COND_INITIALIZER,
0,
0,
DNS_WAIT_NONE,
0,
0,
Expand Down Expand Up @@ -393,6 +397,7 @@ static int wolfip_fd_alloc(int internal_fd, int nonblock)
}
idx = pipefds[0];
memset(&wolfip_fd_entries[idx], 0, sizeof(wolfip_fd_entries[idx]));
wolfip_fd_entries[idx].generation = ++wolfip_fd_generation;
wolfip_fd_entries[idx].internal_fd = internal_fd;
wolfip_fd_entries[idx].public_fd = pipefds[0];
wolfip_fd_entries[idx].pipe_write = pipefds[1];
Expand Down Expand Up @@ -449,9 +454,15 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
{
struct pollfd pfd;
char want;
uint32_t start_gen;
int start_fd;

if (!entry)
return -EINVAL;
/* Snapshot the slot identity so we can detect a concurrent close()/reuse
* across the mutex-drop window below. */
start_gen = entry->generation;
start_fd = entry->internal_fd;
want = (wait_events & POLLOUT) ? 'w' : 'r';
entry->events = (uint16_t)wait_events;
wolfIP_register_callback(IPSTACK, entry->internal_fd, poller_callback, IPSTACK);
Expand Down Expand Up @@ -481,6 +492,15 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
return -EINTR;
}
pthread_mutex_lock(&wolfIP_mutex);
/* While the mutex was dropped a concurrent close() may have released
* this slot, and a subsequent socket()/accept() may have reused the
* same public fd for an unrelated connection. The caller still holds
* the stale internal_fd it captured before blocking; signal EBADF so
* it does not read/write the reused slot's data. */
if (!entry->in_use || entry->generation != start_gen ||
entry->internal_fd != start_fd) {
return -EBADF;
}
if (poll_ret < 0) {
return -errno;
}
Expand Down Expand Up @@ -518,7 +538,7 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
if (__wolfip_internal >= 0) { \
int __wolfip_retval = wolfIP_sock_##call(IPSTACK, __wolfip_internal, ## __VA_ARGS__); \
if (__wolfip_retval < 0) { \
errno = __wolfip_retval; \
errno = -__wolfip_retval; \
pthread_mutex_unlock(&wolfIP_mutex); \
return -1; \
} \
Expand Down Expand Up @@ -565,7 +585,7 @@ static int wolfip_wait_for_event_locked(struct wolfip_fd_entry *entry, short wai
} \
} while (__wolfip_retval == -EAGAIN); \
if (__wolfip_retval < 0) { \
errno = __wolfip_retval; \
errno = -__wolfip_retval; \
pthread_mutex_unlock(&wolfIP_mutex); \
return -1; \
} \
Expand Down Expand Up @@ -715,10 +735,11 @@ static int wolfip_dns_error_to_eai(int err)
static int wolfip_dns_begin_wait(enum wolfip_dns_wait_type type)
{
pthread_mutex_lock(&dns_wait_ctx.mutex);
if (dns_wait_ctx.pending) {
if (dns_wait_ctx.busy) {
pthread_mutex_unlock(&dns_wait_ctx.mutex);
return EAI_AGAIN;
}
dns_wait_ctx.busy = 1;
dns_wait_ctx.pending = 1;
dns_wait_ctx.type = type;
dns_wait_ctx.status = EAI_FAIL;
Expand All @@ -731,6 +752,7 @@ static void wolfip_dns_abort_wait(int status)
{
pthread_mutex_lock(&dns_wait_ctx.mutex);
dns_wait_ctx.pending = 0;
dns_wait_ctx.busy = 0;
dns_wait_ctx.type = DNS_WAIT_NONE;
dns_wait_ctx.status = status;
pthread_cond_signal(&dns_wait_ctx.cond);
Expand All @@ -748,13 +770,16 @@ static int wolfip_dns_wait(enum wolfip_dns_wait_type type, uint32_t *ip_out, cha
int err = pthread_cond_timedwait(&dns_wait_ctx.cond, &dns_wait_ctx.mutex, &ts);
if (err == ETIMEDOUT) {
dns_wait_ctx.pending = 0;
dns_wait_ctx.busy = 0;
dns_wait_ctx.type = DNS_WAIT_NONE;
pthread_mutex_unlock(&dns_wait_ctx.mutex);
return EAI_AGAIN;
}
}
if (dns_wait_ctx.type != type) {
int status = dns_wait_ctx.status ? dns_wait_ctx.status : EAI_FAIL;
dns_wait_ctx.type = DNS_WAIT_NONE;
dns_wait_ctx.busy = 0;
pthread_mutex_unlock(&dns_wait_ctx.mutex);
return status;
}
Expand All @@ -766,6 +791,7 @@ static int wolfip_dns_wait(enum wolfip_dns_wait_type type, uint32_t *ip_out, cha
wolfip_strlcpy(name_out, dns_wait_ctx.name, name_len);
}
dns_wait_ctx.type = DNS_WAIT_NONE;
dns_wait_ctx.busy = 0;
pthread_mutex_unlock(&dns_wait_ctx.mutex);
return status;
}
Expand Down Expand Up @@ -1476,6 +1502,12 @@ static int wolfip_accept_common(int sockfd, struct sockaddr *addr, socklen_t *ad
if (entry) {
int internal_ret;
int public_fd;
uint32_t start_gen;
int start_fd;
/* Snapshot the listener slot's identity so we can detect a concurrent
* close()/reuse across the mutex-drop window in the poll loop below. */
start_gen = entry->generation;
start_fd = entry->internal_fd;
if (!want_nonblock)
want_nonblock = wolfip_fd_is_nonblock(sockfd);
do {
Expand All @@ -1494,8 +1526,15 @@ static int wolfip_accept_common(int sockfd, struct sockaddr *addr, socklen_t *ad
pthread_mutex_unlock(&wolfIP_mutex);
host_poll(&pfd, 1, -1);
pthread_mutex_lock(&wolfIP_mutex);
/* While the mutex was dropped a concurrent close() may have
* released this slot, and a subsequent socket()/accept() may
* have reused the same public fd for an unrelated connection.
* The bare in_use check below cannot tell the slot apart from
* the original listener, so verify the snapshotted identity to
* avoid accepting on the wrong internal socket. */
entry = wolfip_entry_from_public(sockfd);
if (!entry) {
if (!entry || entry->generation != start_gen ||
entry->internal_fd != start_fd) {
errno = EBADF;
pthread_mutex_unlock(&wolfIP_mutex);
return -1;
Expand All @@ -1505,7 +1544,7 @@ static int wolfip_accept_common(int sockfd, struct sockaddr *addr, socklen_t *ad
}
} while (internal_ret == -EAGAIN);
if (internal_ret < 0) {
errno = internal_ret;
errno = -internal_ret;
pthread_mutex_unlock(&wolfIP_mutex);
return -1;
}
Expand Down
Loading
Loading