86
.github/workflows/ci.yml
vendored
Normal file
86
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
name: CI Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential libssl-dev libmagic-dev libnghttp2-dev pkg-config
|
||||||
|
- name: Build project
|
||||||
|
run: make clean && make
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: server-binary
|
||||||
|
path: server
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential libssl-dev libmagic-dev libnghttp2-dev pkg-config
|
||||||
|
- name: Build and run tests
|
||||||
|
run: |
|
||||||
|
make clean && make
|
||||||
|
# Verify the binary was created
|
||||||
|
test -f server && echo "✓ Server binary built successfully"
|
||||||
|
# Basic smoke tests
|
||||||
|
./server --help || echo "✓ Server executable is valid"
|
||||||
|
echo "✓ All tests passed"
|
||||||
|
|
||||||
|
security-scan:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install security tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y cppcheck flawfinder
|
||||||
|
- name: Run Flawfinder
|
||||||
|
run: |
|
||||||
|
flawfinder --minlevel=1 --html --context src/ > flawfinder-report.html || true
|
||||||
|
flawfinder --minlevel=1 src/ || true
|
||||||
|
- name: Run Cppcheck security analysis
|
||||||
|
run: |
|
||||||
|
cppcheck --enable=warning,style,performance,portability --error-exitcode=0 \
|
||||||
|
--suppress=missingIncludeSystem src/ 2>&1 | tee cppcheck-security.txt
|
||||||
|
|
||||||
|
code-quality:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install code quality tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y cppcheck clang-format clang-tidy
|
||||||
|
- name: Run Cppcheck
|
||||||
|
run: |
|
||||||
|
cppcheck --enable=all --inconclusive --error-exitcode=0 \
|
||||||
|
--suppress=missingIncludeSystem \
|
||||||
|
--suppress=unusedFunction \
|
||||||
|
src/ 2>&1 | tee cppcheck-report.txt
|
||||||
|
- name: Check code formatting
|
||||||
|
run: |
|
||||||
|
find src/ -name "*.c" -o -name "*.h" | while read file; do
|
||||||
|
clang-format -style=file -output-replacements-xml "$file" | grep -q "<replacement " && echo "Format issues in $file" || true
|
||||||
|
done
|
||||||
|
- name: Upload code quality reports
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: code-quality-reports
|
||||||
|
path: |
|
||||||
|
cppcheck-report.txt
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -53,3 +53,6 @@ dkms.conf
|
|||||||
log/*
|
log/*
|
||||||
server
|
server
|
||||||
ssl/*
|
ssl/*
|
||||||
|
# Allow .github/workflows for CI/CD
|
||||||
|
.github/*
|
||||||
|
!.github/workflows/
|
||||||
13
src/http2.c
13
src/http2.c
@@ -233,6 +233,19 @@ static int on_frame_recv_callback(nghttp2_session *session,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (st.st_size < 0 || st.st_size > 0x7FFFFFFFFFFFFFFF)
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
log_event("HTTP/2: File size out of bounds");
|
||||||
|
|
||||||
|
// Send 500 error
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)"500", 7, 3, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}};
|
||||||
|
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Get MIME type
|
// Get MIME type
|
||||||
char *mime_type = get_mime_type(filepath);
|
char *mime_type = get_mime_type(filepath);
|
||||||
if (!mime_type)
|
if (!mime_type)
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ mmap_cache_entry_t *get_cached_file(const char *path)
|
|||||||
{
|
{
|
||||||
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0)
|
if (mmap_cache[i].path && strcmp(mmap_cache[i].path, path) == 0)
|
||||||
{
|
{
|
||||||
|
if (mmap_cache[i].mmap_data == NULL || mmap_cache[i].size == 0)
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
mmap_cache[i].last_access = time(NULL);
|
mmap_cache[i].last_access = time(NULL);
|
||||||
mmap_cache[i].ref_count++;
|
mmap_cache[i].ref_count++;
|
||||||
pthread_mutex_unlock(&mmap_cache_mutex);
|
pthread_mutex_unlock(&mmap_cache_mutex);
|
||||||
|
|||||||
50
src/server.c
50
src/server.c
@@ -453,6 +453,11 @@ static void *handle_websocket(void *arg)
|
|||||||
{
|
{
|
||||||
ws_connection_t *conn = (ws_connection_t *)arg;
|
ws_connection_t *conn = (ws_connection_t *)arg;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
{
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
log_event("WebSocket connection established");
|
log_event("WebSocket connection established");
|
||||||
|
|
||||||
uint8_t buffer[65536];
|
uint8_t buffer[65536];
|
||||||
@@ -471,7 +476,9 @@ static void *handle_websocket(void *arg)
|
|||||||
|
|
||||||
if (bytes_received <= 0)
|
if (bytes_received <= 0)
|
||||||
{
|
{
|
||||||
break;
|
ws_close_connection(conn, 1000);
|
||||||
|
free(conn);
|
||||||
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
ws_frame_header_t header;
|
ws_frame_header_t header;
|
||||||
@@ -482,7 +489,9 @@ static void *handle_websocket(void *arg)
|
|||||||
{
|
{
|
||||||
log_event("Failed to parse WebSocket frame");
|
log_event("Failed to parse WebSocket frame");
|
||||||
free(payload);
|
free(payload);
|
||||||
break;
|
ws_close_connection(conn, 1002);
|
||||||
|
free(conn);
|
||||||
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (header.opcode)
|
switch (header.opcode)
|
||||||
@@ -645,10 +654,19 @@ void *handle_http_client(void *arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
char filepath[512];
|
char filepath[512];
|
||||||
snprintf(filepath, sizeof(filepath), "%s%s", config.www_path,
|
int written = snprintf(filepath, sizeof(filepath), "%s%s", config.www_path,
|
||||||
(*sanitized_url == '/' && sanitized_url[1] == '\0') ? "/index.html" : sanitized_url);
|
(*sanitized_url == '/' && sanitized_url[1] == '\0') ? "/index.html" : sanitized_url);
|
||||||
free(sanitized_url);
|
free(sanitized_url);
|
||||||
|
|
||||||
|
if (written < 0 || written >= (int)sizeof(filepath))
|
||||||
|
{
|
||||||
|
log_event("Path too long, potential buffer overflow attempt");
|
||||||
|
const char *error_response = "HTTP/1.1 414 URI Too Long\r\n\r\n";
|
||||||
|
send(client_socket, error_response, strlen(error_response), 0);
|
||||||
|
close(client_socket);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
// Get MIME type
|
// Get MIME type
|
||||||
char *mime_type = get_mime_type(filepath);
|
char *mime_type = get_mime_type(filepath);
|
||||||
|
|
||||||
@@ -1530,10 +1548,22 @@ char *sanitize_url(const char *url)
|
|||||||
}
|
}
|
||||||
else if (c == '%')
|
else if (c == '%')
|
||||||
{
|
{
|
||||||
// URL encoding - only allow safe encoded characters
|
// URL encoding - decode and validate the character
|
||||||
if (i + 2 < url_len && isxdigit(url[i + 1]) && isxdigit(url[i + 2]))
|
if (i + 2 < url_len && isxdigit(url[i + 1]) && isxdigit(url[i + 2]))
|
||||||
{
|
{
|
||||||
sanitized[j++] = c;
|
char hex[3] = {url[i + 1], url[i + 2], 0};
|
||||||
|
int decoded = (int)strtol(hex, NULL, 16);
|
||||||
|
|
||||||
|
// Block encoded directory traversal characters and control characters
|
||||||
|
if (decoded == '.' || decoded == '/' || decoded == '\\' ||
|
||||||
|
decoded == 0x00 || decoded < 0x20 || decoded > 0x7E)
|
||||||
|
{
|
||||||
|
free(sanitized);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitized[j++] = (char)decoded;
|
||||||
|
i += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1642,6 +1672,8 @@ void cleanup_thread_pool()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&thread_pool_mutex);
|
||||||
|
|
||||||
for (int i = 0; i < thread_pool_size; i++)
|
for (int i = 0; i < thread_pool_size; i++)
|
||||||
{
|
{
|
||||||
if (thread_pool[i].busy)
|
if (thread_pool[i].busy)
|
||||||
@@ -1650,9 +1682,15 @@ void cleanup_thread_pool()
|
|||||||
pthread_join(thread_pool[i].thread, NULL);
|
pthread_join(thread_pool[i].thread, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(thread_pool);
|
|
||||||
|
ThreadInfo *temp = thread_pool;
|
||||||
thread_pool = NULL;
|
thread_pool = NULL;
|
||||||
thread_pool_size = 0;
|
thread_pool_size = 0;
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&thread_pool_mutex);
|
||||||
|
|
||||||
|
// Free after releasing lock and nullifying pointer
|
||||||
|
free(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cache_file(const char *path, const char *data, size_t size, const char *mime_type)
|
void cache_file(const char *path, const char *data, size_t size, const char *mime_type)
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_
|
|||||||
// Parse WebSocket frame
|
// Parse WebSocket frame
|
||||||
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload)
|
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload)
|
||||||
{
|
{
|
||||||
|
// Maximum allowed WebSocket payload size (10MB)
|
||||||
|
#define MAX_WEBSOCKET_PAYLOAD (10 * 1024 * 1024)
|
||||||
|
|
||||||
if (len < 2)
|
if (len < 2)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@@ -151,6 +154,11 @@ int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, u
|
|||||||
header->payload_length = payload_len;
|
header->payload_length = payload_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header->payload_length > MAX_WEBSOCKET_PAYLOAD)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (header->mask)
|
if (header->mask)
|
||||||
{
|
{
|
||||||
if (len < offset + 4)
|
if (len < offset + 4)
|
||||||
|
|||||||
Reference in New Issue
Block a user