A Dangerous Language Feature

How a clever use of C lambdas can force the stack to be executable, and why that’s a security risk.

January 25, 2026 · 3 min

Buffer Overflow Demo Code

This is a simplified demonstration program showing how a basic buffer overflow can hijack control flow. It’s deliberately simplified to illustrate the concept without modern protections. See also: See also: Buffer Overflow Format String Vulnerability Download: bufferoverflow.c Compilation # Disable stack protection for demonstration gcc -fno-stack-protector -o bufferoverflow bufferoverflow.c # You may also need to disable ASLR system-wide echo 0 | sudo tee /proc/sys/kernel/randomize_va_space Source Code /* * Buffer Overflow Vulnerability demonstration * * Copyright (c) 2025 Martin Domig <martin@domig.net> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * This program demonstrates a buffer overflow vulnerability. * The user can control the input size and overflow the buffer. * * EDUCATIONAL PURPOSE ONLY - DO NOT USE IN PRODUCTION */ #include <stdio.h> #include <stdlib.h> #include <string.h> // clang-format off /*********************************************************************** BUFFER OVERFLOW VULNERABILITY This demonstrates a buffer overflow vulnerability: the user controls the input size and can overflow the buffer. Note: Modern systems have multiple protections (stack canaries, NX bit, ASLR). You may need to disable some protections for educational demonstrations: # Disable ASLR echo 0 | sudo tee /proc/sys/kernel/randomize_va_space # Compile without stack protection gcc -fno-stack-protector -o bufferoverflow bufferoverflow.c Stack protection works by adding a canary value between local variables and the return address on the stack. This canary is checked before the function returns; if it has been altered, the program will terminate, preventing exploitation. Also, the compiler might reorder variables on the stack (variable reordering). Attackers cannot expect to know the exact layout of the stack from the source code. Possible exploits: 1. Normal Operation (baseline): First, test with normal input to see expected behavior: ./bufferoverflow "Hello" This should show normal checksums for both secret and data. 2. Small Buffer Overflow (data corruption): Overflow the 16-byte buffer to corrupt adjacent memory: This exactly fills the buffer: ./bufferoverflow "AAAAAAAAAAAAAAAA" This overwrites the function pointer (24 bytes total from start of buffer) and will probably cause a segmentation fault: ./bufferoverflow "AAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC" This overflows beyond the function pointer and will also change the secret: ./bufferoverflow "AAAAAAAAAAAAAAAABBBBBBBBCCCCCCCCD" Compare the checksums - the second should show changed values, indicating memory corruption. 3. Function Pointer Hijacking: The program has a function pointer that can be overwritten to call vulnerable_function. First, find the function addresses: ./bufferoverflow "test" Look for the vulnerable_function address in the output (e.g., 0x555555555283). Then create a payload to overwrite the function pointer at offset 24: python3 -c " import struct vulnerable_function_addr = 0x555555555283 # Use address from program output padding = b'A' * 24 # Exact offset to reach function pointer payload = padding + struct.pack('<Q', vulnerable_function_addr) with open('/tmp/payload', 'wb') as f: f.write(payload) " && cat /tmp/payload | ./bufferoverflow --stdin This should call the vulnerable function instead of the safe function. 4. Shellcode (theoretical): In theory, a buffer overflow could inject and execute arbitrary machine code. However, modern systems have the NX (No eXecute) bit which prevents execution of code on the stack. Even with stack protection disabled, the stack is typically not executable by default. This is why function pointer hijacking (example 3) is more practical - it uses existing executable code rather than injecting new code. Real shellcode exploits would require: - Executable stack (-z execstack) OR - Return-oriented programming techniques (use existing executable code) OR - Heap-based buffer overflows in executable regions For educational purposes, function pointer hijacking demonstrates the core concepts without requiring additional system modifications. ***********************************************************************/ // clang-format on static unsigned int calculate_checksum(const char *str) { unsigned int checksum = 0; while (*str) { checksum += *str++; } return checksum; } // The vulnerable function we want to call - for demonstration purposes static void vulnerable_function() { printf("*** VULNERABLE FUNCTION CALLED - EXPLOIT SUCCESSFUL! ***\n"); } // Safe function that does nothing static void safe_function() { printf("Safe function called - no exploit\n"); } static char stdin_buffer[256] = {0}; static size_t stdin_length = 0; static void fill_user_data(const char *input, const char **user_data, size_t *data_length) { if (strcmp(input, "--stdin") == 0) { // Read from stdin - use binary read to handle null bytes stdin_length = fread(stdin_buffer, 1, sizeof(stdin_buffer) - 1, stdin); if (stdin_length > 0) { *user_data = stdin_buffer; *data_length = stdin_length; } else { fprintf(stderr, "Error reading from stdin\n"); exit(EXIT_FAILURE); } } else { // Use command line argument *user_data = input; *data_length = strlen(input); } } int main(int argc, char *argv[]) { char our_secret[50] = "You are not supposed to know this!"; // Function pointer that can be overwritten by buffer overflow - put it right // after data void (*func_ptr)() = safe_function; char data[16] = {0}; // Buffer to hold the secret message char *secret_ptr = our_secret; // Put a pointer to the secret on the stack char *data_ptr = data; // Put a pointer to the data on the stack // This asm directive ensures consistent stack layout, especially with // optimization asm volatile("" : : "r"(our_secret), "r"(secret_ptr), "r"(data), "r"(data_ptr), "r"(func_ptr) : "memory"); if (argc < 2) { fprintf(stderr, "Usage: %s <data>\n", argv[0]); fprintf(stderr, " or: %s --stdin\n", argv[0]); return EXIT_FAILURE; } const char *user_data; size_t data_length; fill_user_data(argv[1], &user_data, &data_length); printf("Buffer overflow vulnerability demonstration\n"); printf("Will copy %d bytes into the buffer\n", (int)data_length); printf("Data buffer at: %p\n", (void *)data); printf("Function pointer at: %p, points to: %p\n", (void *)&func_ptr, (void *)func_ptr); printf("vulnerable_function at: %p\n", (void *)vulnerable_function); memcpy(data, user_data, data_length); // vulnerable to buffer overflow printf("Secret checksum: %08x\n", calculate_checksum(secret_ptr)); printf("Data checksum: %08x\n", calculate_checksum(data_ptr)); // Call the function pointer - this can be hijacked! printf("Calling function pointer...\n"); func_ptr(); return EXIT_SUCCESS; }

October 8, 2025 · 6 min

Buffer Overflow: The Bug That Refuses to Die

Learning about buffer overflows, why they persist, and what they reveal about software security.

October 8, 2025 · 7 min