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/*
|
||||
server
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
char *mime_type = get_mime_type(filepath);
|
||||
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].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].ref_count++;
|
||||
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;
|
||||
|
||||
if (!conn)
|
||||
{
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
log_event("WebSocket connection established");
|
||||
|
||||
uint8_t buffer[65536];
|
||||
@@ -471,7 +476,9 @@ static void *handle_websocket(void *arg)
|
||||
|
||||
if (bytes_received <= 0)
|
||||
{
|
||||
break;
|
||||
ws_close_connection(conn, 1000);
|
||||
free(conn);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
ws_frame_header_t header;
|
||||
@@ -482,7 +489,9 @@ static void *handle_websocket(void *arg)
|
||||
{
|
||||
log_event("Failed to parse WebSocket frame");
|
||||
free(payload);
|
||||
break;
|
||||
ws_close_connection(conn, 1002);
|
||||
free(conn);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
switch (header.opcode)
|
||||
@@ -645,10 +654,19 @@ void *handle_http_client(void *arg)
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
char *mime_type = get_mime_type(filepath);
|
||||
|
||||
@@ -1530,10 +1548,22 @@ char *sanitize_url(const char *url)
|
||||
}
|
||||
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]))
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -1642,6 +1672,8 @@ void cleanup_thread_pool()
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&thread_pool_mutex);
|
||||
|
||||
for (int i = 0; i < thread_pool_size; i++)
|
||||
{
|
||||
if (thread_pool[i].busy)
|
||||
@@ -1650,9 +1682,15 @@ void cleanup_thread_pool()
|
||||
pthread_join(thread_pool[i].thread, NULL);
|
||||
}
|
||||
}
|
||||
free(thread_pool);
|
||||
|
||||
ThreadInfo *temp = thread_pool;
|
||||
thread_pool = NULL;
|
||||
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)
|
||||
|
||||
@@ -116,6 +116,9 @@ int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_
|
||||
// Parse WebSocket frame
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (header->payload_length > MAX_WEBSOCKET_PAYLOAD)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (header->mask)
|
||||
{
|
||||
if (len < offset + 4)
|
||||
|
||||
Reference in New Issue
Block a user