Skip to content

Commit 00611ac

Browse files
committed
Fix unreachable deep-URI reject in clean_uri.
Compare `argc` against the array's element count.
1 parent b869801 commit 00611ac

2 files changed

Lines changed: 248 additions & 3 deletions

File tree

src/kws.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,10 +1807,11 @@ static ks_status_t clean_uri(char *uri)
18071807
int argc;
18081808
char *argv[64];
18091809
int last, i, len, uri_len = 0;
1810+
const unsigned int max_segments = sizeof(argv) / sizeof(argv[0]);
18101811

1811-
argc = ks_separate_string(uri, '/', argv, sizeof(argv) / sizeof(argv[0]));
1812+
argc = ks_separate_string(uri, '/', argv, max_segments);
18121813

1813-
if (argc == sizeof(argv)) { /* too deep */
1814+
if ((unsigned int)argc == max_segments) { /* too deep */
18141815
return KS_STATUS_FAIL;
18151816
}
18161817

tests/testhttp.c

Lines changed: 245 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,249 @@ static int test_keepalive(char *ip)
692692
return r;
693693
}
694694

695+
/*
696+
* URI sanitization tests for kws_parse_header → clean_uri.
697+
*
698+
* Drives a parallel HTTP server that echoes request->uri (after
699+
* kws_parse_header has normalized it) back to the client as the
700+
* response body "URI=<value>". The client sends a table of probes
701+
* over one keepalive connection and asserts on the echoed value.
702+
*/
703+
704+
struct uri_case {
705+
const char *send;
706+
const char *expect;
707+
};
708+
709+
static const struct uri_case g_uri_cases[] = {
710+
/* /../ at root is clamped — cannot escape document root */
711+
{ "/../../etc/passwd", "/etc/passwd" },
712+
{ "/foo/../../bar", "/bar" },
713+
/* dot and empty segments are collapsed */
714+
{ "/./a", "/a" },
715+
{ "//a//b", "/a/b" },
716+
{ "/foo/.", "/foo" },
717+
/* URL-encoded dots are NOT decoded; the literal %2E survives */
718+
{ "/%2E%2E/%2E%2E/etc/passwd", "/%2E%2E/%2E%2E/etc/passwd" },
719+
{ NULL, NULL }
720+
};
721+
722+
static void make_deep_uri(char *buf, size_t bufsz, int segments)
723+
{
724+
size_t off = 0;
725+
int i;
726+
for (i = 0; i < segments && off + 2 < bufsz; i++) {
727+
buf[off++] = '/';
728+
buf[off++] = 'a';
729+
}
730+
buf[off] = '\0';
731+
}
732+
733+
static void uri_sanitize_server_callback(ks_socket_t server_sock, ks_socket_t client_sock, ks_sockaddr_t *addr, void *user_data)
734+
{
735+
struct tcp_data *tcp_data = (struct tcp_data *) user_data;
736+
kws_t *kws = NULL;
737+
kws_request_t *request = NULL;
738+
int flags = KWS_BLOCK | KWS_STAY_OPEN | KWS_HTTP;
739+
int keepalive = 0;
740+
741+
if (kws_init(&kws, client_sock, NULL, NULL, flags, tcp_data->pool) != KS_STATUS_SUCCESS) {
742+
goto end;
743+
}
744+
745+
new_req:
746+
if (kws_parse_header(kws, &request) != KS_STATUS_SUCCESS) {
747+
/* Expected for the >=64-segment probe: clean_uri rejects, connection drops. */
748+
goto end;
749+
}
750+
751+
if (!strncmp(request->method, "GET", 3)) {
752+
char body[2048];
753+
char hdr[256];
754+
int blen = ks_snprintf(body, sizeof(body), "URI=%s", request->uri);
755+
ks_snprintf(hdr, sizeof(hdr),
756+
"HTTP/1.1 200 OK\r\n"
757+
"Content-Length: %d\r\n"
758+
"Content-Type: text/plain\r\n"
759+
"Connection: keep-alive\r\n"
760+
"Server: libks-uri-test\r\n\r\n",
761+
blen);
762+
kws_raw_write(kws, hdr, strlen(hdr));
763+
kws_raw_write(kws, body, blen);
764+
}
765+
766+
end:
767+
if (request) {
768+
keepalive = request->keepalive;
769+
kws_request_free(&request);
770+
}
771+
772+
if (keepalive) {
773+
int pflags = kws_wait_sock(kws, 1000, KS_POLL_READ);
774+
if (pflags > 0 && (pflags & KS_POLL_READ)) {
775+
if (kws_keepalive(kws) == KS_STATUS_SUCCESS) {
776+
goto new_req;
777+
}
778+
}
779+
}
780+
781+
ks_socket_close(&client_sock);
782+
kws_destroy(&kws);
783+
}
784+
785+
static void *uri_sanitize_tcp_sock_server(ks_thread_t *thread, void *thread_data)
786+
{
787+
struct tcp_data *tcp_data = (struct tcp_data *) thread_data;
788+
789+
tcp_data->ready = 1;
790+
ks_listen_sock(tcp_data->sock, &tcp_data->addr, 0, uri_sanitize_server_callback, tcp_data);
791+
792+
return NULL;
793+
}
794+
795+
static int extract_echoed_uri(const char *response, ks_size_t response_len, char *out, size_t outsz)
796+
{
797+
const char *body;
798+
799+
if (response_len == 0) return 0;
800+
801+
body = strstr(response, "\r\n\r\n");
802+
if (!body) return 0;
803+
body += 4;
804+
805+
if (strncmp(body, "URI=", 4) != 0) return 0;
806+
body += 4;
807+
808+
ks_snprintf(out, outsz, "%s", body);
809+
return 1;
810+
}
811+
812+
static int test_uri_sanitize(char *ip)
813+
{
814+
ks_thread_t *thread_p = NULL;
815+
ks_pool_t *pool;
816+
ks_sockaddr_t addr;
817+
int family = AF_INET;
818+
ks_socket_t cl_sock = KS_SOCK_INVALID;
819+
struct tcp_data tcp_data = { 0 };
820+
int r = 1, sanity = 100;
821+
int i;
822+
823+
ks_pool_open(&pool);
824+
tcp_data.pool = pool;
825+
826+
if (strchr(ip, ':')) family = AF_INET6;
827+
828+
if (ks_addr_set(&tcp_data.addr, ip, tcp_port, family) != KS_STATUS_SUCCESS) {
829+
r = 0;
830+
printf("URI CLIENT Can't set ADDR\n");
831+
goto end;
832+
}
833+
834+
if ((tcp_data.sock = socket(family, SOCK_STREAM, IPPROTO_TCP)) == KS_SOCK_INVALID) {
835+
r = 0;
836+
printf("URI CLIENT Can't create sock family %d\n", family);
837+
goto end;
838+
}
839+
840+
ks_socket_option(tcp_data.sock, SO_REUSEADDR, KS_TRUE);
841+
ks_socket_option(tcp_data.sock, TCP_NODELAY, KS_TRUE);
842+
tcp_data.ip = ip;
843+
844+
ks_thread_create(&thread_p, uri_sanitize_tcp_sock_server, &tcp_data, pool);
845+
846+
while (!tcp_data.ready && --sanity > 0) {
847+
ks_sleep(10000);
848+
}
849+
850+
ks_addr_set(&addr, ip, tcp_port, family);
851+
cl_sock = ks_socket_connect(SOCK_STREAM, IPPROTO_TCP, &addr);
852+
853+
for (i = 0; g_uri_cases[i].send; i++) {
854+
char req[2048];
855+
char response[4096] = { 0 };
856+
char echoed[1024] = { 0 };
857+
ks_size_t len;
858+
const char *send_uri = g_uri_cases[i].send;
859+
const char *expect = g_uri_cases[i].expect;
860+
861+
ks_snprintf(req, sizeof(req),
862+
"GET %s HTTP/1.1\r\n"
863+
"HOST: localhost\r\n"
864+
"Connection: keep-alive\r\n\r\n",
865+
send_uri);
866+
867+
len = strlen(req);
868+
ks_socket_send(cl_sock, req, &len);
869+
ks_sleep_ms(200);
870+
871+
len = sizeof(response) - 1;
872+
if (ks_socket_recv(cl_sock, response, &len) != KS_STATUS_SUCCESS || len == 0) {
873+
printf("URI sanitize [fail]: %s -> no response\n", send_uri);
874+
r = 0;
875+
continue;
876+
}
877+
response[len] = '\0';
878+
879+
if (!extract_echoed_uri(response, len, echoed, sizeof(echoed))) {
880+
printf("URI sanitize [fail]: %s -> response missing URI= marker\n", send_uri);
881+
r = 0;
882+
continue;
883+
}
884+
885+
printf("URI sanitize: %-40s -> %-40s (expect %s)\n", send_uri, echoed, expect);
886+
887+
if (strcmp(echoed, expect) != 0) {
888+
printf("URI sanitize [fail]: %s -> got %s, expected %s\n", send_uri, echoed, expect);
889+
r = 0;
890+
}
891+
}
892+
893+
/* Deep-URI probe: >64 segments must hit clean_uri's reject branch. Server
894+
closes without responding; client recv should not yield a body. */
895+
{
896+
char deep[2048];
897+
char req[4096];
898+
char buf[1024];
899+
ks_size_t len;
900+
ks_status_t recv_status;
901+
902+
make_deep_uri(deep, sizeof(deep), 70);
903+
ks_snprintf(req, sizeof(req),
904+
"GET %s HTTP/1.1\r\n"
905+
"HOST: localhost\r\n"
906+
"Connection: keep-alive\r\n\r\n",
907+
deep);
908+
len = strlen(req);
909+
ks_socket_send(cl_sock, req, &len);
910+
ks_sleep_ms(200);
911+
912+
len = sizeof(buf);
913+
recv_status = ks_socket_recv(cl_sock, buf, &len);
914+
if (recv_status == KS_STATUS_SUCCESS && len > 0) {
915+
printf("URI sanitize [fail]: deep URI got %zu-byte response\n", (size_t)len);
916+
r = 0;
917+
} else {
918+
printf("URI sanitize: deep URI correctly rejected (status %d)\n", (int)recv_status);
919+
}
920+
}
921+
922+
end:
923+
if (tcp_data.sock != KS_SOCK_INVALID) {
924+
ks_socket_shutdown(tcp_data.sock, 2);
925+
ks_socket_close(&tcp_data.sock);
926+
}
927+
928+
if (thread_p) {
929+
ks_thread_join(thread_p);
930+
}
931+
932+
ks_socket_close(&cl_sock);
933+
ks_pool_close(&pool);
934+
935+
return r;
936+
}
937+
695938
int main(void)
696939
{
697940
int have_v4 = 0, have_v6 = 0;
@@ -704,7 +947,7 @@ int main(void)
704947
have_v4 = ks_zstr_buf(v4) ? 0 : 1;
705948
// have_v6 = ks_zstr_buf(v6) ? 0 : 1;
706949

707-
plan((have_v4 * 5) + (have_v6 * 4) + 1);
950+
plan((have_v4 * 6) + (have_v6 * 4) + 1);
708951

709952
ok(have_v4 || have_v6);
710953

@@ -718,6 +961,7 @@ int main(void)
718961
ok(test_post(v4));
719962
ok(test_keepalive(v4));
720963
ok(test_post_json_read_buffer(v4));
964+
ok(test_uri_sanitize(v4));
721965
}
722966

723967
if (have_v6) {

0 commit comments

Comments
 (0)