Skip to content

ragibasif/watchdog

Watchdog


LinkedIn GitHub

Watchdog is a lightweight, thread-safe memory allocation tracker for C projects. It detects memory leaks, buffer overflows (via canaries), and invalid/double frees with zero external dependencies.

Features

  • Leak Detection: Reports any memory not freed before program exit.
  • Overflow Protection: Uses a 64-byte canary buffer to detect out-of-bounds writes.
  • Double Free Prevention: Tracks allocation states to catch redundant free() calls.
  • Invalid Realloc Detection: Rejects untracked or stale realloc() pointers instead of copying unknown memory.
  • Thread Safe: Uses POSIX mutexes to handle multi-threaded allocations.
  • Automated Verification: Includes a Dockerized test suite and CI/CD pipeline.

Project Structure

watchdog/
├── watchdog.c          # Core implementation (Dynamic Array logic)
├── watchdog.h          # API Macros (Redefines malloc/free)
├── Makefile            # Build system
├── Dockerfile          # Standardized test environment
├── docs/               # Interview prep and resume collateral
├── tests/
│   ├── test.c          # Simulates memory bugs
│   └── test_runner.py  # Automated validation script
└── .github/workflows/  # GitHub Actions (CI)

Quick Start

Watchdog wraps dynamic memory functions (malloc, calloc, realloc, free) and tracks all memory activity at runtime.

#define WATCHDOG_ENABLE
#include "watchdog.h"

#include <stdio.h>
#include <stdlib.h>

int main([[maybe_unused]] int argc, [[maybe_unused]] char **argv) {

    bool enable_verbose_log  = true;
    bool log_to_file         = false;
    bool enable_color_output = true;

    w_init(enable_verbose_log, log_to_file, enable_color_output);

    int *buffer = malloc(sizeof *buffer * 1024);
    free(buffer);

    return EXIT_SUCCESS;
}

./assets/demo_0.gif

Usage

Installation

Include watchdog.h and watchdog.c in your project.

Then #include watchdog.h in a source/header file and pass flag -DWATCHDOG_ENABLE to the CFLAGS of your build system to enable the debugger or add #define WATCHDOG_ENABLE to a file.

Defaults

Verbose logging is on by default, log to file is off by default, and color output is off by default.

static bool verbose_log  = true;
static bool log_to_file  = false;
static bool color_output = false;

To customize the defaults, pass appropriate boolean to w_init.

bool enable_verbose_log  = true;
bool log_to_file         = false;
bool enable_color_output = true;

w_init(enable_verbose_log, log_to_file, enable_color_output);

Enabling log_to_file will direct log output to a log file named watchdog.log. Color output is turned off if log_to_file is enabled regardless of the enable_color_output variable value.

If enable_verbose_log is set to false, only errors will be logged.

Building

The included Makefile handles the compilation of the library and the test suite:

make

Documentation

Running Tests

You can run the automated suite locally or via Docker:

python3 tests/test_runner.py

Docker (Clean Environment):

docker build -t watchdog-tester .
docker run --rm watchdog-tester

Examples

Code samples are located in a dedicated examples/ folder.

Malloc

void malloc_example(void) {

    size_t count  = 5;
    int   *buffer = malloc(sizeof *buffer * 5);
    for (size_t i = 0; i < count; i++) {
        buffer[i] = -(i - count) * count;
    }
    for (size_t i = 0; i < count; i++) {
        printf("%d ", buffer[i]);
    }

    putchar('\n');
    free(buffer);
    buffer = NULL;
}

./assets/malloc.gif

Realloc

void realloc_example(void) {
    short *buffer = malloc(34222);
    buffer        = realloc(buffer, 2342);
    buffer        = realloc(buffer, 2342342);
    buffer        = realloc(buffer, 2);
    buffer        = realloc(buffer, 10);
    buffer        = realloc(buffer, 0);
    free(buffer);
    buffer = NULL;
}

./assets/realloc.gif

Note: Watchdog's realloc wrapper preserves the original allocation when a nonzero resize fails and reports untracked pointers instead of treating them as valid metadata entries.

Calloc

void calloc_example(void) {
    // Demonstrates correct usage of calloc
    size_t count  = 5;
    int   *buffer = calloc(count, sizeof *buffer);
    for (size_t i = 0; i < count; i++) {
        printf("%d ", buffer[i]); // should output 0
    }
    putchar('\n');
    free(buffer);
    buffer = NULL;
}

./assets/calloc.gif

Free

void free_example(void) {
    // Demonstrates correct usage of free
    size_t         count   = 5;
    short int     *buffer0 = malloc(sizeof *buffer0 * count);
    unsigned char *buffer1 = calloc(count, sizeof *buffer1);
    long double   *buffer2 = realloc(NULL, sizeof *buffer2 * count);
    free(buffer1);
    buffer1 = NULL;
    free(buffer2);
    buffer2 = NULL;
    free(buffer0);
    buffer0 = NULL;
}

./assets/free.gif

Leak Check

void leak_example(void) {
    unsigned long long int *buffer = malloc(sizeof *buffer * 20);
    // intentionally not calling free
    // watchdog will detect this and report it
}

./assets/leak_check.gif

Overflow Check

void overflow_example(void) {
    char *buffer = malloc(sizeof *buffer * 10);
    strcpy(buffer, "This will overflow"); // out-of-bounds write
    // of buffer overflows will be detected with using canary values
}

./assets/overflow_check.gif

Double Free Check

void double_free_example(void) {
    char **buffer = malloc(sizeof *buffer * 20);
    free(buffer);
    free(buffer); // triggers a double-free error
}

./assets/double_free_check.gif

Invalid Free Check

void invalid_free_example(void) {
    float value  = 1.0f;
    float *buffer = &value;
    free(buffer); // will trigger an error since buffer wasn't allocated
}

./assets/invalid_free_check.gif

Contributing

Interested in contributing? Please see our Contributing Guide for guidelines on how to help improve Watchdog.

If you are using this project in a portfolio, interview packet, or resume, the materials in docs/ provide project-specific talking points and condensed impact statements.

License

This project is licensed under the MIT License.

FAQ

Does Watchdog work in production builds?

No. It's intended for development/debug builds.

Does it detect stack overflows?

No. It only monitors dynamic memory (heap).

What systems does it support?

POSIX-compliant systems (Linux, macOS). Windows support is limited (for now).