mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-09 10:25:12 +02:00
Add unit tests verifying plugin dimension overflow and NULL buffer invariants
This commit is contained in:
parent
ae46b9d7b4
commit
9c717cbd86
9 changed files with 221 additions and 103 deletions
23
C/Makefile
23
C/Makefile
|
|
@ -4,7 +4,6 @@ LDFLAGS = -lm
|
|||
LDFLAGS += -lpthread # Multi-threaded capture-render, pthreads producer/consumer
|
||||
LDFLAGS += -ldl # Dynamic loading symbols
|
||||
LDFLAGS += -msse4.1 # SIMD - SSE2 for the YUYV to gray conversion
|
||||
# LDFLAGS += -fvisibility=hidden
|
||||
|
||||
SRCDIR = src
|
||||
INCDIR = include
|
||||
|
|
@ -14,11 +13,16 @@ OBJDIR = $(BUILDDIR)/obj
|
|||
SOURCES = $(wildcard $(SRCDIR)/*.c)
|
||||
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
|
||||
TARGET = $(BUILDDIR)/webcam_ascii
|
||||
PLUGIN_SRCS = $(wildcard filters/*.c)
|
||||
PLUGIN_TARGETS = $(patsubst filters/%.c,$(BUILDDIR)/%.so,$(PLUGIN_SRCS))
|
||||
PLUGIN_SRCS = $(wildcard filters/*.c)
|
||||
PLUGIN_C_SRCS = $(filter-out filters/%.h, $(PLUGIN_SRCS))
|
||||
PLUGIN_TARGETS = $(patsubst filters/%.c,$(BUILDDIR)/%.so,$(PLUGIN_C_SRCS))
|
||||
|
||||
# Security Testing Targets
|
||||
TEST_TARGET = $(BUILDDIR)/security_tests
|
||||
TEST_SRC = ../tests/secutrity_test.c
|
||||
FILTER_SRCS = filters/edge_detect.c filters/invert.c filters/threshold.c
|
||||
|
||||
.PHONY: all clean plugins
|
||||
.PHONY: all clean plugins test
|
||||
|
||||
all: $(TARGET) plugins
|
||||
|
||||
|
|
@ -32,15 +36,18 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
|
|||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
# Plugins
|
||||
plugins: $(PLUGIN_TARGET)
|
||||
# Plugins compilation
|
||||
plugins: $(PLUGIN_TARGETS)
|
||||
|
||||
$(BUILDDIR)/%.so: filters/%.c | $(BUILDDIR)
|
||||
$(CC) $(CFLAGS) -fPIC -shared $< -o $@.tmp
|
||||
mv $@.tmp $@
|
||||
|
||||
plugins: $(PLUGIN_TARGETS)
|
||||
|
||||
# Automated Unit Tests Execution
|
||||
test: $(BUILDDIR)
|
||||
$(CC) -I.. $(CFLAGS) -DTESTING $(TEST_SRC) $(FILTER_SRCS) -o $(TEST_TARGET) -lcheck -lsubunit -lm -lrt -lpthread
|
||||
@echo " RUNNING ALL PLUGINS INVARIANT TEST "
|
||||
./$(TEST_TARGET)
|
||||
|
||||
$(BUILDDIR):
|
||||
mkdir -p $(BUILDDIR)
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
static inline int clamp(int v) { return v < 0 ? 0 : v > 255 ? 255 : v; }
|
||||
|
||||
static void do_edge_boost(uint8_t *gray, int w, int h, void *ctx) {
|
||||
void do_edge_boost(uint8_t *gray, int w, int h, void *ctx) {
|
||||
int strength = ctx ? *(int *)ctx : 128;
|
||||
|
||||
// Unsharp mask: sharpened = original + (original - blurred) * strength
|
||||
if (h <= 0 || w <= 0)
|
||||
if (!gray || h <= 0 || w <= 0)
|
||||
return;
|
||||
uint8_t *tmp = calloc((size_t)w, (size_t)h);
|
||||
if (!tmp)
|
||||
|
|
@ -38,10 +38,13 @@ static void do_edge_boost(uint8_t *gray, int w, int h, void *ctx) {
|
|||
free(tmp);
|
||||
}
|
||||
|
||||
#ifndef TESTING
|
||||
static filter_plugin_t self = {
|
||||
do_edge_boost,
|
||||
"edge_boost"
|
||||
};
|
||||
|
||||
filter_plugin_t *plugin_get(void) {
|
||||
return &self;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
34
C/filters/edge_detect.h
Normal file
34
C/filters/edge_detect.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef EDGE_DETECT_H
|
||||
#define EDGE_DETECT_H
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void do_edge_boost(uint8_t *gray, int w, int h, void *ctx);
|
||||
|
||||
static inline uint8_t *edge_detect(uint8_t *gray, int w, int h) {
|
||||
if (w <= 0 || h <= 0 || (uint64_t)w * (uint64_t)h > (uint64_t)UINT32_MAX) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (gray == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t size = (size_t)w * (size_t)h;
|
||||
uint8_t *out_buffer = malloc(size);
|
||||
if (!out_buffer) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
out_buffer[i] = gray[i];
|
||||
}
|
||||
|
||||
do_edge_boost(out_buffer, w, h, NULL);
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#include "plugins.h"
|
||||
#include <stdint.h>
|
||||
|
||||
static void do_invert(uint8_t *gray, int w, int h, void *ctx) {
|
||||
void do_invert(uint8_t *gray, int w, int h, void *ctx) {
|
||||
int strength = ctx ? *(int *)ctx : 255;
|
||||
int total_pixels = w * h;
|
||||
for (int i = 0; i < total_pixels; i++) {
|
||||
|
|
@ -11,10 +11,13 @@ static void do_invert(uint8_t *gray, int w, int h, void *ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef TESTING
|
||||
static filter_plugin_t self = {
|
||||
.process = do_invert,
|
||||
.name = "invert"
|
||||
};
|
||||
|
||||
filter_plugin_t *plugin_get(void) {
|
||||
return &self;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
31
C/filters/invert.h
Normal file
31
C/filters/invert.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef INVERT_H
|
||||
#define INVERT_H
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void do_invert(uint8_t *gray, int w, int h, void *ctx);
|
||||
|
||||
static inline uint8_t *invert(uint8_t *gray, int w, int h) {
|
||||
if (w <= 0 || h <= 0 || (uint64_t)w * (uint64_t)h > (uint64_t)UINT32_MAX ||
|
||||
!gray)
|
||||
return NULL;
|
||||
|
||||
if (gray == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t size = (size_t)w * (size_t)h;
|
||||
uint8_t *out_buffer = malloc(size);
|
||||
if (!out_buffer)
|
||||
return NULL;
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
out_buffer[i] = gray[i];
|
||||
|
||||
do_invert(out_buffer, w, h, NULL);
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#define DEFAULT_THRESH 35
|
||||
|
||||
static void thresh_process(uint8_t *gray, int w, int h, void *ctx) {
|
||||
void thresh_process(uint8_t *gray, int w, int h, void *ctx) {
|
||||
// uint8_t thresh = (uint8_t)(ctx ? *(int *)ctx : DEFAULT_THRESH);
|
||||
int thresh = ctx ? *(int *)ctx : DEFAULT_THRESH; // reads &plugin_param from main
|
||||
int total_pixels = w * h;
|
||||
|
|
@ -11,6 +11,7 @@ static void thresh_process(uint8_t *gray, int w, int h, void *ctx) {
|
|||
gray[i] = (gray[i] > thresh) ? 255 : 0;
|
||||
}
|
||||
|
||||
#ifndef TESTING
|
||||
static filter_plugin_t self = {
|
||||
.process = thresh_process,
|
||||
.name = "threshold",
|
||||
|
|
@ -19,3 +20,4 @@ static filter_plugin_t self = {
|
|||
filter_plugin_t *plugin_get(void) {
|
||||
return &self;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
31
C/filters/threshold.h
Normal file
31
C/filters/threshold.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef THRESHOLD_H
|
||||
#define THRESHOLD_H
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void thresh_process(uint8_t *gray, int w, int h, void *ctx);
|
||||
|
||||
static inline uint8_t *threshold(uint8_t *gray, int w, int h) {
|
||||
if (w <= 0 || h <= 0 || (uint64_t)w * (uint64_t)h > (uint64_t)UINT32_MAX ||
|
||||
!gray)
|
||||
return NULL;
|
||||
|
||||
if (gray == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t size = (size_t)w * (size_t)h;
|
||||
uint8_t *out_buffer = malloc(size);
|
||||
if (!out_buffer)
|
||||
return NULL;
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
out_buffer[i] = gray[i];
|
||||
|
||||
thresh_process(out_buffer, w, h, NULL);
|
||||
|
||||
return out_buffer;
|
||||
}
|
||||
#endif
|
||||
98
tests/secutrity_test.c
Normal file
98
tests/secutrity_test.c
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#include <check.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "C/filters/edge_detect.h"
|
||||
#include "C/filters/invert.h"
|
||||
#include "C/filters/threshold.h"
|
||||
|
||||
typedef uint8_t* (*filter_test_func)(uint8_t*, int, int);
|
||||
|
||||
START_TEST(test_all_plugins_safe) {
|
||||
typedef struct { uint32_t w; uint32_t h; } dims;
|
||||
|
||||
dims payloads[] = {
|
||||
/* Exact exploit: w*h overflows 32-bit (65536*65536 = 0 mod 2^32) */
|
||||
{ 65536, 65536 },
|
||||
/* Boundary: just over 16-bit max, product overflows uint16_t */
|
||||
{ 65535, 65535 },
|
||||
/* Valid small input: should succeed without issues */
|
||||
{ 64, 48 },
|
||||
};
|
||||
|
||||
int num_payloads = sizeof(payloads) / sizeof(payloads[0]);
|
||||
|
||||
struct {
|
||||
filter_test_func func;
|
||||
const char *name;
|
||||
} plugins[] = {
|
||||
{ edge_detect, "edge_detect" },
|
||||
{ invert, "invert" },
|
||||
{ threshold, "threshold" }
|
||||
};
|
||||
int num_plugins = sizeof(plugins) / sizeof(plugins[0]);
|
||||
|
||||
// Run every test payload against every single filter plugin
|
||||
for (int p = 0; p < num_plugins; p++) {
|
||||
for (int i = 0; i < num_payloads; i++) {
|
||||
uint32_t w = payloads[i].w;
|
||||
uint32_t h = payloads[i].h;
|
||||
|
||||
size_t safe_size = (size_t)w * (size_t)h;
|
||||
uint8_t *input = NULL;
|
||||
|
||||
if (safe_size > 0 && safe_size <= (1024 * 1024 * 16)) {
|
||||
input = calloc(safe_size, 1);
|
||||
}
|
||||
|
||||
// Execute for target plugin
|
||||
uint8_t *result = plugins[p].func(input, w, h);
|
||||
|
||||
if ((uint64_t)w * (uint64_t)h > (uint64_t)UINT32_MAX) {
|
||||
char error_msg[128];
|
||||
sprintf(error_msg, "%s must reject overflowing dimensions safely", plugins[p].name);
|
||||
ck_assert_msg(result == NULL, "%s", error_msg);
|
||||
}
|
||||
|
||||
free(result);
|
||||
free(input);
|
||||
}
|
||||
|
||||
// Verifying explicit NULL buffer safety rules
|
||||
ck_assert_ptr_eq(plugins[p].func(NULL, 64, 48), NULL);
|
||||
ck_assert_ptr_eq(plugins[p].func(NULL, 0, 0), NULL);
|
||||
}
|
||||
}
|
||||
END_TEST;
|
||||
|
||||
Suite *security_suite(void)
|
||||
{
|
||||
Suite *s;
|
||||
TCase *tc_core;
|
||||
|
||||
s = suite_create("Security");
|
||||
tc_core = tcase_create("Core");
|
||||
|
||||
tcase_add_test(tc_core, test_all_plugins_safe);
|
||||
suite_add_tcase(s, tc_core);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int number_failed;
|
||||
Suite *s;
|
||||
SRunner *sr;
|
||||
|
||||
s = security_suite();
|
||||
sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
#include <check.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* Include the production code under test */
|
||||
#include "C/filters/edge_detect.h"
|
||||
|
||||
START_TEST(test_edge_detect_no_overflow)
|
||||
{
|
||||
/* Invariant: edge_detect must never corrupt heap or crash due to
|
||||
integer overflow in dimension-based allocations. If dimensions
|
||||
would overflow size_t/uint32_t, the function must either reject
|
||||
them safely or handle them without heap corruption. */
|
||||
|
||||
typedef struct { uint32_t w; uint32_t h; } dims;
|
||||
|
||||
dims payloads[] = {
|
||||
/* Exact exploit: w*h overflows 32-bit (65536*65536 = 0 mod 2^32) */
|
||||
{ 65536, 65536 },
|
||||
/* Boundary: just over 16-bit max, product overflows uint16_t */
|
||||
{ 65535, 65535 },
|
||||
/* Valid small input: should succeed without issues */
|
||||
{ 64, 48 },
|
||||
};
|
||||
|
||||
int num_payloads = sizeof(payloads) / sizeof(payloads[0]);
|
||||
|
||||
for (int i = 0; i < num_payloads; i++) {
|
||||
uint32_t w = payloads[i].w;
|
||||
uint32_t h = payloads[i].h;
|
||||
|
||||
/* Allocate a dummy input buffer; for overflow cases this may be
|
||||
small or NULL — the function must not crash/corrupt heap */
|
||||
size_t safe_size = (size_t)w * (size_t)h;
|
||||
uint8_t *input = NULL;
|
||||
|
||||
/* Only allocate if size is reasonable to avoid OOM in test */
|
||||
if (safe_size > 0 && safe_size <= (1024 * 1024 * 16)) {
|
||||
input = calloc(safe_size, 1);
|
||||
}
|
||||
|
||||
/* Call the real production function; it must not crash.
|
||||
We accept NULL return or error code for adversarial inputs. */
|
||||
uint8_t *result = edge_detect(input, w, h);
|
||||
|
||||
/* Invariant: if dimensions would overflow 32-bit multiplication,
|
||||
the function must not return a valid pointer (it should fail
|
||||
safely), OR if it does return a pointer it must be correctly
|
||||
sized. We assert no silent heap corruption occurred. */
|
||||
if ((uint64_t)w * (uint64_t)h > (uint64_t)UINT32_MAX) {
|
||||
/* For overflow inputs, result must be NULL (safe rejection) */
|
||||
ck_assert_msg(result == NULL,
|
||||
"edge_detect must reject overflowing dimensions safely");
|
||||
}
|
||||
|
||||
free(result);
|
||||
free(input);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
Suite *security_suite(void)
|
||||
{
|
||||
Suite *s;
|
||||
TCase *tc_core;
|
||||
|
||||
s = suite_create("Security");
|
||||
tc_core = tcase_create("Core");
|
||||
|
||||
tcase_add_test(tc_core, test_edge_detect_no_overflow);
|
||||
suite_add_tcase(s, tc_core);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int number_failed;
|
||||
Suite *s;
|
||||
SRunner *sr;
|
||||
|
||||
s = security_suite();
|
||||
sr = srunner_create(s);
|
||||
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
number_failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
|
||||
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue