diff --git a/Codes/Cipher/Stream/main.c b/Codes/Cipher/Stream/main.c index ae4d5d7..06c8d75 100644 --- a/Codes/Cipher/Stream/main.c +++ b/Codes/Cipher/Stream/main.c @@ -67,6 +67,8 @@ int main(int argc, char* argv[]) printx("Original", (const uint8_t *)data, length); memset(buffer, 0, sizeof(buffer)); + if (length > sizeof(buffer)) + length = sizeof(buffer); memcpy(buffer, data, length); stream_encrypt(buffer, length, key, nonce, counter); diff --git a/tests/test_invariant_main.c b/tests/test_invariant_main.c new file mode 100644 index 0000000..cd69a8d --- /dev/null +++ b/tests/test_invariant_main.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include + +/* Invariant: memcpy in stream cipher must never copy more bytes than the + * allocated buffer size. Oversized 'length' inputs must be rejected or + * truncated, never cause out-of-bounds writes. */ + +#define BUFFER_SIZE 256 + +static int run_with_length(size_t length) +{ + /* Fork and exec the real binary with a crafted length argument. + * The child process exercises the actual production code path. + * We treat a crash (signal) as a test failure. */ + pid_t pid = fork(); + if (pid == 0) { + /* Child: redirect stdout/stderr to /dev/null */ + int devnull = open("/dev/null", 1); + dup2(devnull, 1); + dup2(devnull, 2); + close(devnull); + + char len_str[32]; + snprintf(len_str, sizeof(len_str), "%zu", length); + /* Pass length as argument; binary reads it as the copy length */ + execl("Codes/Cipher/Stream/main", "main", len_str, NULL); + _exit(127); + } + int status = 0; + waitpid(pid, &status, 0); + return status; +} + +START_TEST(test_buffer_read_never_exceeds_declared_length) +{ + /* Invariant: buffer reads must never exceed allocated buffer size */ + size_t payloads[] = { + BUFFER_SIZE - 1, /* valid: one byte under limit */ + BUFFER_SIZE, /* boundary: exactly at limit */ + BUFFER_SIZE * 2, /* exploit: 2x oversized */ + BUFFER_SIZE * 10, /* extreme: 10x oversized */ + }; + int num_payloads = (int)(sizeof(payloads) / sizeof(payloads[0])); + + for (int i = 0; i < num_payloads; i++) { + int status = run_with_length(payloads[i]); + + /* The process must NOT be killed by a signal (e.g. SIGSEGV, SIGABRT). + * A normal exit (any code) is acceptable; a signal death is not. */ + ck_assert_msg( + !WIFSIGNALED(status), + "Process killed by signal %d for length=%zu — buffer overflow detected", + WTERMSIG(status), payloads[i] + ); + } +} +END_TEST + +Suite *security_suite(void) +{ + Suite *s; + TCase *tc_core; + + s = suite_create("Security"); + tc_core = tcase_create("Core"); + tcase_set_timeout(tc_core, 30); + + tcase_add_test(tc_core, test_buffer_read_never_exceeds_declared_length); + 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