From 9c717cbd863a4d19db4881bdca92f55a239751d0 Mon Sep 17 00:00:00 2001 From: Harshit-Dhanwalkar Date: Mon, 1 Jun 2026 23:21:32 +0530 Subject: [PATCH] Add unit tests verifying plugin dimension overflow and NULL buffer invariants --- C/Makefile | 23 ++++--- C/filters/edge_detect.c | 7 ++- C/filters/edge_detect.h | 34 +++++++++++ C/filters/invert.c | 5 +- C/filters/invert.h | 31 ++++++++++ C/filters/threshold.c | 4 +- C/filters/threshold.h | 31 ++++++++++ tests/secutrity_test.c | 98 ++++++++++++++++++++++++++++++ tests/test_invariant_edge_detect.c | 91 --------------------------- 9 files changed, 221 insertions(+), 103 deletions(-) create mode 100644 C/filters/edge_detect.h create mode 100644 C/filters/invert.h create mode 100644 C/filters/threshold.h create mode 100644 tests/secutrity_test.c delete mode 100644 tests/test_invariant_edge_detect.c diff --git a/C/Makefile b/C/Makefile index b0e571c..e1d9b74 100644 --- a/C/Makefile +++ b/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) diff --git a/C/filters/edge_detect.c b/C/filters/edge_detect.c index 19653ef..07ac19e 100644 --- a/C/filters/edge_detect.c +++ b/C/filters/edge_detect.c @@ -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 diff --git a/C/filters/edge_detect.h b/C/filters/edge_detect.h new file mode 100644 index 0000000..128cdf3 --- /dev/null +++ b/C/filters/edge_detect.h @@ -0,0 +1,34 @@ +#ifndef EDGE_DETECT_H +#define EDGE_DETECT_H + +#include +#include +#include + +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 diff --git a/C/filters/invert.c b/C/filters/invert.c index de3ed23..b86dfe9 100644 --- a/C/filters/invert.c +++ b/C/filters/invert.c @@ -1,7 +1,7 @@ #include "plugins.h" #include -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 diff --git a/C/filters/invert.h b/C/filters/invert.h new file mode 100644 index 0000000..1cdf576 --- /dev/null +++ b/C/filters/invert.h @@ -0,0 +1,31 @@ +#ifndef INVERT_H +#define INVERT_H + +#include +#include +#include + +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 diff --git a/C/filters/threshold.c b/C/filters/threshold.c index 8caaafb..c7dc481 100644 --- a/C/filters/threshold.c +++ b/C/filters/threshold.c @@ -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 diff --git a/C/filters/threshold.h b/C/filters/threshold.h new file mode 100644 index 0000000..e565f0e --- /dev/null +++ b/C/filters/threshold.h @@ -0,0 +1,31 @@ +#ifndef THRESHOLD_H +#define THRESHOLD_H + +#include +#include +#include + +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 diff --git a/tests/secutrity_test.c b/tests/secutrity_test.c new file mode 100644 index 0000000..11c4d16 --- /dev/null +++ b/tests/secutrity_test.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include + +#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; +} diff --git a/tests/test_invariant_edge_detect.c b/tests/test_invariant_edge_detect.c deleted file mode 100644 index 99d5674..0000000 --- a/tests/test_invariant_edge_detect.c +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include -#include - -/* 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; -} \ No newline at end of file