Skip to content

AD3030: UseGccCheckedFunctions

Summary

Property Value
ID AD3030
Name UseGccCheckedFunctions
Category Security
Severity Warning
Applies to ELF (Linux/Unix)

Description

GCC provides "fortified" versions of standard C library functions that include runtime buffer overflow checks. When _FORTIFY_SOURCE is enabled, functions like strcpy, memcpy, and sprintf are replaced with checked versions that abort if buffer overflows are detected.

How It Works

The rule checks for the presence of fortified function symbols:

  • __memcpy_chk
  • __strcpy_chk
  • __sprintf_chk
  • And other *_chk variants

These symbols indicate _FORTIFY_SOURCE was enabled during compilation.

Why This Matters

FORTIFY_SOURCE transparently replaces dangerous C library functions with bounds-checked versions. This catches buffer overflows that would otherwise lead to exploitable vulnerabilities, with virtually no source code changes required.

How FORTIFY_SOURCE Works

// Your code:
char dest[10];
strcpy(dest, source);  // Dangerous if source > 10 bytes

// With FORTIFY_SOURCE=2, compiled as:
char dest[10];
__strcpy_chk(dest, source, 10);  // Knows dest is 10 bytes!
                                  // Aborts if source > 10 bytes

Functions Protected

Category Functions
String strcpy, strcat, strncpy, strncat, sprintf, snprintf
Memory memcpy, memmove, memset, mempcpy
Wide char wcscpy, wcscat, wcsncpy, wcsncat
File I/O gets, fgets, fread, read
Varargs printf family, syslog

Protection Levels

Level Behavior Overhead Compiler
_FORTIFY_SOURCE=1 Compile-time checks only Zero GCC 4.0+
_FORTIFY_SOURCE=2 + Runtime checks for known sizes Very low GCC 4.0+
_FORTIFY_SOURCE=3 + Checks for variable sizes Low GCC 12+, Clang 12+

FORTIFY_SOURCE=3 Deep Dive

Level 3 is a significant enhancement introduced in GCC 12 and Clang 12. It uses __builtin_dynamic_object_size() instead of __builtin_object_size(), enabling runtime size determination for dynamically-allocated buffers.

When Level 3 Helps vs Level 2

// Level 2 CAN protect this (size known at compile time):
char buf[100];
strcpy(buf, input);  // Compiler knows buf is 100 bytes

// Level 2 CANNOT protect this (size unknown at compile time):
char *buf = malloc(user_size);
strcpy(buf, input);  // Compiler doesn't know buf's size at compile time!

// Level 3 CAN protect the malloc case:
char *buf = malloc(user_size);
strcpy(buf, input);  // Runtime knows user_size, can check!

How Level 3 Works

// Level 2 uses __builtin_object_size():
//   Returns size if known at COMPILE TIME, else (size_t)-1

// Level 3 uses __builtin_dynamic_object_size():
//   Can determine size at RUNTIME via malloc tracking

void process(size_t n) {
    char *buf = malloc(n);

    // Level 2: __builtin_object_size(buf, 1) returns -1 (unknown)
    //          No protection possible!

    // Level 3: __builtin_dynamic_object_size(buf, 1) returns n
    //          Can check: strlen(input) < n

    strcpy(buf, input);
    free(buf);
}

Level 3 Requirements

Requirement Details
Compiler GCC 12+ or Clang 12+
glibc 2.34+ (for full support)
Optimization -O2 or higher
Allocator Standard malloc/calloc/realloc

Performance Considerations

Level 3 has slightly higher overhead than Level 2 because it: - Tracks allocation sizes at runtime - Performs additional size checks for dynamic allocations

Benchmarks show ~1-2% overhead in allocation-heavy workloads, negligible for most applications.

Adoption

Project Level
Linux Kernel 2 (moving to 3)
systemd 3
Fedora/RHEL packages 3 (default)
Ubuntu 2 (moving to 3)

What Gets Caught

char buf[32];

// Compile-time detection (FORTIFY_SOURCE=1+):
strcpy(buf, "this string is way too long for 32 bytes and will overflow");
// Compiler error: overflow in implicit constant conversion

// Runtime detection (FORTIFY_SOURCE=2+):
strcpy(buf, user_input);  // Where user_input > 32 bytes
// Program aborts with: *** buffer overflow detected ***

Detection vs. Prevention

Without FORTIFY_SOURCE:
  Overflow → Memory corruption → Maybe exploit → Maybe detection later

With FORTIFY_SOURCE:
  Overflow → __strcpy_chk detects → Program aborts

Result: Controlled crash instead of exploitation

Optimization Requirement

Build Type FORTIFY Active
-O0 (debug) NO!
-O1 Yes (basic)
-O2 Yes (full)
-O3 Yes (full)
-Os Yes (full)

FORTIFY needs optimization to determine buffer sizes.

Real-World Effectiveness

FORTIFY_SOURCE has prevented countless exploits:

Scenario Without FORTIFY With FORTIFY
Classic strcpy overflow Exploitable Abort
sprintf format attack Varies Abort (if overflow)
Heap via memcpy Exploitable Abort

Verification

# Check for fortified functions
objdump -T binary | grep _chk

# Expected output:
# 0000000000000000  DF *UND* 0000000000000000 GLIBC_2.3.4 __sprintf_chk
# 0000000000000000  DF *UND* 0000000000000000 GLIBC_2.3.4 __strcpy_chk
  • Buffer overflow detection: Runtime checks for common overflows
  • Zero source changes: Works with existing code
  • Low overhead: Optimized checks with minimal impact
  • Standard library hardening: Protects common vulnerability sources

Resolution

GCC/Clang

# Level 1 - checks that don't affect behavior
gcc -D_FORTIFY_SOURCE=1 -O1 source.c -o binary

# Level 2 - stricter checks (recommended)
gcc -D_FORTIFY_SOURCE=2 -O2 source.c -o binary

# Level 3 - strictest (GCC 12+)
gcc -D_FORTIFY_SOURCE=3 -O2 source.c -o binary

Note: _FORTIFY_SOURCE requires optimization (-O1 or higher).

CMake

add_compile_definitions(_FORTIFY_SOURCE=2)
add_compile_options(-O2)

Makefile

CFLAGS += -D_FORTIFY_SOURCE=2 -O2

Autotools

./configure CFLAGS="-D_FORTIFY_SOURCE=2 -O2"

When to Suppress

This rule may be suppressed for:

  • Debug builds: Optimization disabled
  • Performance-critical paths: After measuring overhead
  • Custom allocators: Non-standard memory management
  • Embedded systems: Without glibc fortification

Caveats

  • Requires optimization (-O1 or higher)
  • Only works with glibc (not musl, not bionic)
  • Some functions are not fortified
  • Level 2 may change behavior for incorrect code

Fortification Levels

Level Description When to Use
1 Conservative checks Maximum compatibility
2 Stricter checks Recommended default
3 Variable-length arrays GCC 12+, most strict

Fortified Functions

// Standard function → Fortified version
strcpy()   → __strcpy_chk()
strncpy()  → __strncpy_chk()
strcat()   → __strcat_chk()
strncat()  → __strncat_chk()
sprintf()  → __sprintf_chk()
snprintf() → __snprintf_chk()
vsprintf() → __vsprintf_chk()
memcpy()   → __memcpy_chk()
memmove()  → __memmove_chk()
memset()   → __memset_chk()
gets()     → __gets_chk() (removed in C11)

How Fortification Works

char buf[10];
strcpy(buf, source);  // Original call

// With _FORTIFY_SOURCE, becomes:
__strcpy_chk(buf, source, 10);  // Buffer size known

// At runtime:
if (strlen(source) >= 10) {
    // Buffer overflow detected!
    __chk_fail();  // Aborts program
}

Compiler Requirement

# Correct - optimization enabled
gcc -O2 -D_FORTIFY_SOURCE=2 source.c -o binary

# WRONG - won't work without optimization
gcc -O0 -D_FORTIFY_SOURCE=2 source.c -o binary
# Warning: _FORTIFY_SOURCE requires optimization

References