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
*_chkvariants
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¶
Makefile¶
Autotools¶
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 (
-O1or 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
Related Rules¶
- AD3003: EnableStackProtector
- AD3005: EnableStackClashProtection
- AD5004: UseFortifiedFunctionsMachO - Mach-O equivalent